* Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2013 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
* Copyright (c) 2002-2004, Marcus Overhagen <marcus@overhagen.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <map>
#include <stdio.h>
#include <vector>
#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Directory.h>
#include <Entry.h>
#include <MediaAddOn.h>
#include <MediaRoster.h>
#include <MessageRunner.h>
#include <Path.h>
#include <Roster.h>
#include <String.h>
#include <AddOnMonitorHandler.h>
#include <DataExchange.h>
#include <DormantNodeManager.h>
#include <MediaDebug.h>
#include <MediaMisc.h>
#include <MediaRosterEx.h>
#include <MediaSounds.h>
#include <Notifications.h>
#include <ServerInterface.h>
#include "MediaFilePlayer.h"
#include "SystemTimeSource.h"
typedef std::vector<media_node> NodeVector;
struct AddOnInfo {
media_addon_id id;
bool wants_autostart;
int32 flavor_count;
NodeVector active_flavors;
BMediaAddOn* addon;
};
class MediaAddonServer : BApplication {
public:
MediaAddonServer(const char* signature);
virtual ~MediaAddonServer();
virtual void ReadyToRun();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage* message);
private:
class MonitorHandler;
friend class MonitorHandler;
void _AddOnAdded(const char* path, ino_t fileNode);
void _AddOnRemoved(ino_t fileNode);
void _HandleMessage(int32 code, const void* data,
size_t size);
void _PutAddonIfPossible(AddOnInfo& info);
void _InstantiatePhysicalInputsAndOutputs(
AddOnInfo& info);
void _InstantiateAutostartFlavors(AddOnInfo& info);
void _DestroyInstantiatedFlavors(AddOnInfo& info);
void _ScanAddOnFlavors(BMediaAddOn* addOn);
port_id _ControlPort() const { return fControlPort; }
static status_t _ControlThread(void* arg);
private:
typedef std::map<ino_t, media_addon_id> FileMap;
typedef std::map<media_addon_id, AddOnInfo> InfoMap;
FileMap fFileMap;
InfoMap fInfoMap;
BMediaRoster* fMediaRoster;
MonitorHandler* fMonitorHandler;
BMessageRunner* fPulseRunner;
port_id fControlPort;
thread_id fControlThread;
bool fStartup;
bool fStartupSound;
SystemTimeSource* fSystemTimeSource;
};
class MediaAddonServer::MonitorHandler : public AddOnMonitorHandler {
public:
MonitorHandler(MediaAddonServer* server);
virtual void AddOnEnabled(const add_on_entry_info* info);
virtual void AddOnDisabled(const add_on_entry_info* info);
private:
MediaAddonServer* fServer;
};
#if DEBUG >= 2
static void
DumpFlavorInfo(const flavor_info* info)
{
printf(" name = %s\n", info->name);
printf(" info = %s\n", info->info);
printf(" internal_id = %" B_PRId32 "\n", info->internal_id);
printf(" possible_count = %" B_PRId32 "\n", info->possible_count);
printf(" flavor_flags = 0x%" B_PRIx32, info->flavor_flags);
if (info->flavor_flags & B_FLAVOR_IS_GLOBAL) printf(" B_FLAVOR_IS_GLOBAL");
if (info->flavor_flags & B_FLAVOR_IS_LOCAL) printf(" B_FLAVOR_IS_LOCAL");
printf("\n");
printf(" kinds = 0x%" B_PRIx64, info->kinds);
if (info->kinds & B_BUFFER_PRODUCER) printf(" B_BUFFER_PRODUCER");
if (info->kinds & B_BUFFER_CONSUMER) printf(" B_BUFFER_CONSUMER");
if (info->kinds & B_TIME_SOURCE) printf(" B_TIME_SOURCE");
if (info->kinds & B_CONTROLLABLE) printf(" B_CONTROLLABLE");
if (info->kinds & B_FILE_INTERFACE) printf(" B_FILE_INTERFACE");
if (info->kinds & B_ENTITY_INTERFACE) printf(" B_ENTITY_INTERFACE");
if (info->kinds & B_PHYSICAL_INPUT) printf(" B_PHYSICAL_INPUT");
if (info->kinds & B_PHYSICAL_OUTPUT) printf(" B_PHYSICAL_OUTPUT");
if (info->kinds & B_SYSTEM_MIXER) printf(" B_SYSTEM_MIXER");
printf("\n");
printf(" in_format_count = %" B_PRId32 "\n", info->in_format_count);
printf(" out_format_count = %" B_PRId32 "\n", info->out_format_count);
}
#endif
MediaAddonServer::MonitorHandler::MonitorHandler(MediaAddonServer* server)
{
fServer = server;
}
void
MediaAddonServer::MonitorHandler::AddOnEnabled(const add_on_entry_info* info)
{
entry_ref ref;
make_entry_ref(info->dir_nref.device, info->dir_nref.node,
info->name, &ref);
BEntry entry(&ref, true);
if (!entry.IsFile())
return;
BPath path(&ref);
if (path.InitCheck() == B_OK)
fServer->_AddOnAdded(path.Path(), info->nref.node);
}
void
MediaAddonServer::MonitorHandler::AddOnDisabled(const add_on_entry_info* info)
{
fServer->_AddOnRemoved(info->nref.node);
}
MediaAddonServer::MediaAddonServer(const char* signature)
:
BApplication(signature),
fMonitorHandler(NULL),
fPulseRunner(NULL),
fStartup(true),
fStartupSound(true),
fSystemTimeSource(NULL)
{
CALLED();
fMediaRoster = BMediaRoster::Roster();
fControlPort = create_port(64, MEDIA_ADDON_SERVER_PORT_NAME);
fControlThread = spawn_thread(_ControlThread, "media_addon_server control",
B_NORMAL_PRIORITY + 2, this);
resume_thread(fControlThread);
}
MediaAddonServer::~MediaAddonServer()
{
CALLED();
delete_port(fControlPort);
wait_for_thread(fControlThread, NULL);
FileMap::iterator iterator = fFileMap.begin();
for (; iterator != fFileMap.end(); iterator++)
gDormantNodeManager->UnregisterAddOn(iterator->second);
delete fMonitorHandler;
delete fPulseRunner;
}
void
MediaAddonServer::ReadyToRun()
{
if (!be_roster->IsRunning(B_MEDIA_SERVER_SIGNATURE)) {
fprintf(stderr, "The media_server is not running!\n");
Quit();
return;
}
ASSERT(fStartup == true);
fSystemTimeSource = new SystemTimeSource();
status_t result = fMediaRoster->RegisterNode(fSystemTimeSource);
if (result != B_OK) {
fprintf(stderr, "Can't register system time source : %s\n",
strerror(result));
ERROR("Can't register system time source");
}
if (fSystemTimeSource->ID() != NODE_SYSTEM_TIMESOURCE_ID)
ERROR("System time source got wrong node ID");
media_node node = fSystemTimeSource->Node();
result = MediaRosterEx(fMediaRoster)->SetNode(SYSTEM_TIME_SOURCE, &node);
if (result != B_OK)
ERROR("Can't setup system time source as default");
fMonitorHandler = new MonitorHandler(this);
AddHandler(fMonitorHandler);
BMessage pulse(B_PULSE);
fPulseRunner = new BMessageRunner(fMonitorHandler, &pulse, 1000000LL);
result = fPulseRunner->InitCheck();
if (result != B_OK)
ERROR("Can't create the pulse runner");
fMonitorHandler->AddAddOnDirectories("media");
#ifdef USER_ADDON_PATH
node_ref nodeRef;
if (entry.SetTo(USER_ADDON_PATH) == B_OK
&& entry.GetNodeRef(&nodeRef) == B_OK) {
fMonitorHandler->AddDirectory(&nodeRef);
}
#endif
fStartup = false;
InfoMap::iterator iterator = fInfoMap.begin();
for (; iterator != fInfoMap.end(); iterator++)
_InstantiatePhysicalInputsAndOutputs(iterator->second);
for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
_InstantiateAutostartFlavors(iterator->second);
for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
_PutAddonIfPossible(iterator->second);
server_rescan_defaults_command cmd;
SendToServer(SERVER_RESCAN_DEFAULTS, &cmd, sizeof(cmd));
}
bool
MediaAddonServer::QuitRequested()
{
CALLED();
InfoMap::iterator iterator = fInfoMap.begin();
for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
_DestroyInstantiatedFlavors(iterator->second);
if (fSystemTimeSource != NULL &&
be_roster->IsRunning(B_MEDIA_SERVER_SIGNATURE)) {
status_t result = fMediaRoster->UnregisterNode(fSystemTimeSource);
if (result != B_OK) {
fprintf(stderr, "Error removing the system time source : %s\n",
strerror(result));
ERROR("Can't remove the system time source");
}
fSystemTimeSource->Release();
fSystemTimeSource = NULL;
}
for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
_PutAddonIfPossible(iterator->second);
return true;
}
void
MediaAddonServer::MessageReceived(BMessage* message)
{
switch (message->what) {
case MEDIA_ADD_ON_SERVER_PLAY_MEDIA:
{
const char* name;
const char* type;
if (message->FindString(MEDIA_NAME_KEY, &name) != B_OK
|| message->FindString(MEDIA_TYPE_KEY, &type) != B_OK) {
message->SendReply(B_ERROR);
break;
}
PlayMediaFile(type, name);
message->SendReply((uint32)B_OK);
return;
}
default:
BApplication::MessageReceived(message);
break;
}
}
void
MediaAddonServer::_HandleMessage(int32 code, const void* data, size_t size)
{
switch (code) {
case ADD_ON_SERVER_INSTANTIATE_DORMANT_NODE:
{
const add_on_server_instantiate_dormant_node_request* request
= static_cast<
const add_on_server_instantiate_dormant_node_request*>(
data);
add_on_server_instantiate_dormant_node_reply reply;
status_t status
= MediaRosterEx(fMediaRoster)->InstantiateDormantNode(
request->add_on_id, request->flavor_id,
request->creator_team, &reply.node);
request->SendReply(status, &reply, sizeof(reply));
break;
}
case ADD_ON_SERVER_RESCAN_ADD_ON_FLAVORS:
{
const add_on_server_rescan_flavors_command* command = static_cast<
const add_on_server_rescan_flavors_command*>(data);
BMediaAddOn* addOn
= gDormantNodeManager->GetAddOn(command->add_on_id);
if (addOn == NULL) {
ERROR("rescan flavors: Can't find a addon object for id %d\n",
(int)command->add_on_id);
break;
}
_ScanAddOnFlavors(addOn);
gDormantNodeManager->PutAddOn(command->add_on_id);
break;
}
case ADD_ON_SERVER_RESCAN_FINISHED_NOTIFY:
if (fStartupSound) {
BMessage msg(MEDIA_ADD_ON_SERVER_PLAY_MEDIA);
msg.AddString(MEDIA_NAME_KEY, MEDIA_SOUNDS_STARTUP);
msg.AddString(MEDIA_TYPE_KEY, MEDIA_TYPE_SOUNDS);
BMessageRunner::StartSending(this, &msg, 2000000, 1);
fStartupSound = false;
}
break;
default:
ERROR("media_addon_server: received unknown message code %#08"
B_PRIx32 "\n", code);
break;
}
}
status_t
MediaAddonServer::_ControlThread(void* _server)
{
MediaAddonServer* server = (MediaAddonServer*)_server;
char data[B_MEDIA_MESSAGE_SIZE];
ssize_t size;
int32 code;
while ((size = read_port_etc(server->_ControlPort(), &code, data,
sizeof(data), 0, 0)) > 0)
server->_HandleMessage(code, data, size);
return B_OK;
}
void
MediaAddonServer::_ScanAddOnFlavors(BMediaAddOn* addon)
{
ASSERT(addon->AddonID() > 0);
TRACE("MediaAddonServer::_ScanAddOnFlavors: id %" B_PRId32 "\n",
addon->AddonID());
media_addon_id addonID = addon->AddonID();
InfoMap::iterator found = fInfoMap.find(addonID);
ASSERT(found != fInfoMap.end());
AddOnInfo& info = found->second;
int32 oldFlavorCount = info.flavor_count;
int32 newFlavorCount = addon->CountFlavors();
info.flavor_count = newFlavorCount;
TRACE("%" B_PRId32 " old flavors, %" B_PRId32 " new flavors\n",
oldFlavorCount, newFlavorCount);
for (int i = 0; i < newFlavorCount; i++) {
const flavor_info* flavorInfo;
TRACE("flavor %d:\n", i);
if (addon->GetFlavorAt(i, &flavorInfo) != B_OK) {
ERROR("MediaAddonServer::_ScanAddOnFlavors GetFlavorAt failed for "
"index %d!\n", i);
continue;
}
#if DEBUG >= 2
DumpFlavorInfo(flavorInfo);
#endif
dormant_flavor_info dormantFlavorInfo;
dormantFlavorInfo = *flavorInfo;
dormantFlavorInfo.node_info.addon = addonID;
dormantFlavorInfo.node_info.flavor_id = flavorInfo->internal_id;
strlcpy(dormantFlavorInfo.node_info.name, flavorInfo->name,
B_MEDIA_NAME_LENGTH);
size_t flattenedSize = dormantFlavorInfo.FlattenedSize();
size_t messageSize = flattenedSize
+ sizeof(server_register_dormant_node_command);
server_register_dormant_node_command* message
= (server_register_dormant_node_command*)malloc(messageSize);
if (message == NULL)
break;
message->purge_id = i == 0 ? addonID : 0;
message->type = dormantFlavorInfo.TypeCode();
message->flattened_size = flattenedSize;
dormantFlavorInfo.Flatten(message->flattened_data, flattenedSize);
status_t status = SendToServer(SERVER_REGISTER_DORMANT_NODE,
message, messageSize);
if (status != B_OK) {
ERROR("MediaAddonServer::_ScanAddOnFlavors: couldn't register "
"dormant node: %s\n", strerror(status));
}
free(message);
}
BPrivate::media::notifications::FlavorsChanged(addonID, newFlavorCount,
oldFlavorCount);
}
void
MediaAddonServer::_AddOnAdded(const char* path, ino_t fileNode)
{
TRACE("\n\nMediaAddonServer::_AddOnAdded: path %s\n", path);
media_addon_id id = gDormantNodeManager->RegisterAddOn(path);
if (id <= 0) {
ERROR("MediaAddonServer::_AddOnAdded: failed to register add-on %s\n",
path);
return;
}
TRACE("MediaAddonServer::_AddOnAdded: loading addon %" B_PRId32 " now..."
"\n", id);
BMediaAddOn* addon = gDormantNodeManager->GetAddOn(id);
if (addon == NULL) {
ERROR("MediaAddonServer::_AddOnAdded: failed to get add-on %s\n",
path);
gDormantNodeManager->UnregisterAddOn(id);
return;
}
TRACE("MediaAddonServer::_AddOnAdded: loading finished, id %" B_PRId32
"\n", id);
try {
fFileMap.insert(std::make_pair(fileNode, id));
AddOnInfo info = {};
fInfoMap.insert(std::make_pair(id, info));
} catch (std::bad_alloc& exception) {
fFileMap.erase(fileNode);
return;
}
InfoMap::iterator found = fInfoMap.find(id);
AddOnInfo& info = found->second;
info.id = id;
info.wants_autostart = false;
info.flavor_count = 0;
info.addon = addon;
_ScanAddOnFlavors(addon);
info.wants_autostart = addon->WantsAutoStart();
if (info.wants_autostart)
TRACE("add-on %" B_PRId32 " WantsAutoStart!\n", id);
if (!fStartup) {
_InstantiatePhysicalInputsAndOutputs(info);
_InstantiateAutostartFlavors(info);
_PutAddonIfPossible(info);
server_rescan_defaults_command cmd;
SendToServer(SERVER_RESCAN_DEFAULTS, &cmd, sizeof(cmd));
}
}
void
MediaAddonServer::_DestroyInstantiatedFlavors(AddOnInfo& info)
{
printf("MediaAddonServer::_DestroyInstantiatedFlavors addon %" B_PRId32
"\n", info.id);
NodeVector::iterator iterator = info.active_flavors.begin();
for (; iterator != info.active_flavors.end(); iterator++) {
media_node& node = *iterator;
printf("node %" B_PRId32 "\n", node.node);
if ((node.kind & B_TIME_SOURCE) != 0
&& (fMediaRoster->StopTimeSource(node, 0, true) != B_OK)) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't stop "
"timesource\n");
continue;
}
if (fMediaRoster->StopNode(node, 0, true) != B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't stop "
"node\n");
continue;
}
if ((node.kind & B_BUFFER_CONSUMER) != 0) {
media_input inputs[16];
int32 count = 0;
if (fMediaRoster->GetConnectedInputsFor(node, inputs, 16, &count)
!= B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't "
"get connected inputs\n");
continue;
}
for (int32 i = 0; i < count; i++) {
media_node_id sourceNode;
if ((sourceNode = fMediaRoster->NodeIDFor(
inputs[i].source.port)) < 0) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors "
"couldn't get source node id\n");
continue;
}
if (fMediaRoster->Disconnect(sourceNode, inputs[i].source,
node.node, inputs[i].destination) != B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors "
"couldn't disconnect input\n");
continue;
}
}
}
if ((node.kind & B_BUFFER_PRODUCER) != 0) {
media_output outputs[16];
int32 count = 0;
if (fMediaRoster->GetConnectedOutputsFor(node, outputs, 16,
&count) != B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't "
"get connected outputs\n");
continue;
}
for (int32 i = 0; i < count; i++) {
media_node_id destNode;
if ((destNode = fMediaRoster->NodeIDFor(
outputs[i].destination.port)) < 0) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors "
"couldn't get destination node id\n");
continue;
}
if (fMediaRoster->Disconnect(node.node, outputs[i].source,
destNode, outputs[i].destination) != B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors "
"couldn't disconnect output\n");
continue;
}
}
}
if (MediaRosterEx(fMediaRoster)->ReleaseNodeAll(node) != B_OK) {
printf("MediaAddonServer::_DestroyInstantiatedFlavors "
"couldn't release node\n");
}
snooze(50000);
}
info.active_flavors.clear();
}
void
MediaAddonServer::_PutAddonIfPossible(AddOnInfo& info)
{
if (info.addon && info.active_flavors.empty()) {
gDormantNodeManager->PutAddOn(info.id);
info.addon = NULL;
}
}
void
MediaAddonServer::_InstantiatePhysicalInputsAndOutputs(AddOnInfo& info)
{
CALLED();
int32 count = info.addon->CountFlavors();
for (int32 i = 0; i < count; i++) {
const flavor_info* flavorinfo;
if (info.addon->GetFlavorAt(i, &flavorinfo) != B_OK) {
ERROR("MediaAddonServer::InstantiatePhysialInputsAndOutputs "
"GetFlavorAt failed for index %" B_PRId32 "!\n", i);
continue;
}
if ((flavorinfo->kinds & (B_PHYSICAL_INPUT | B_PHYSICAL_OUTPUT)) != 0) {
media_node node;
dormant_node_info dormantNodeInfo;
dormantNodeInfo.addon = info.id;
dormantNodeInfo.flavor_id = flavorinfo->internal_id;
strcpy(dormantNodeInfo.name, flavorinfo->name);
TRACE("MediaAddonServer::InstantiatePhysialInputsAndOutputs: "
"\"%s\" is a physical input/output\n", flavorinfo->name);
status_t status = fMediaRoster->InstantiateDormantNode(
dormantNodeInfo, &node);
if (status != B_OK) {
ERROR("MediaAddonServer::InstantiatePhysialInputsAndOutputs "
"Couldn't instantiate node flavor, internal_id %" B_PRId32
", name %s\n", flavorinfo->internal_id, flavorinfo->name);
} else {
TRACE("Node created!\n");
info.active_flavors.push_back(node);
}
}
}
}
void
MediaAddonServer::_InstantiateAutostartFlavors(AddOnInfo& info)
{
if (!info.wants_autostart)
return;
for (int32 index = 0;; index++) {
TRACE("trying autostart of node %" B_PRId32 ", index %" B_PRId32 "\n",
info.id, index);
BMediaNode* node;
int32 internalID;
bool hasMore;
status_t status = info.addon->AutoStart(index, &node, &internalID,
&hasMore);
if (status == B_MEDIA_ADDON_FAILED && hasMore)
continue;
else if (status != B_OK)
break;
printf("started node %" B_PRId32 "\n", index);
status = MediaRosterEx(fMediaRoster)->RegisterNode(node, info.id,
internalID);
if (status != B_OK) {
ERROR("failed to register node %" B_PRId32 "\n", index);
node->Release();
} else {
MediaRosterEx(fMediaRoster)->IncrementAddonFlavorInstancesCount(
info.id, internalID);
info.active_flavors.push_back(node->Node());
}
if (!hasMore)
return;
}
}
void
MediaAddonServer::_AddOnRemoved(ino_t fileNode)
{
FileMap::iterator foundFile = fFileMap.find(fileNode);
if (foundFile == fFileMap.end()) {
ERROR("MediaAddonServer::_AddOnRemoved: inode %" B_PRIdINO " removed, but no "
"media add-on found\n", fileNode);
return;
}
media_addon_id id = foundFile->second;
fFileMap.erase(foundFile);
int32 oldFlavorCount;
InfoMap::iterator foundInfo = fInfoMap.find(id);
if (foundInfo == fInfoMap.end()) {
ERROR("MediaAddonServer::_AddOnRemoved: couldn't get addon info for "
"add-on %" B_PRId32 "\n", id);
oldFlavorCount = 1000;
} else {
AddOnInfo& info = foundInfo->second;
oldFlavorCount = info.flavor_count;
_DestroyInstantiatedFlavors(info);
_PutAddonIfPossible(info);
if (info.addon) {
ERROR("MediaAddonServer::_AddOnRemoved: couldn't unload addon "
"%" B_PRId32 " since flavors are in use\n", id);
}
fInfoMap.erase(foundInfo);
}
gDormantNodeManager->UnregisterAddOn(id);
BPrivate::media::notifications::FlavorsChanged(id, 0, oldFlavorCount);
}
int
main()
{
new MediaAddonServer(B_MEDIA_ADDON_SERVER_SIGNATURE);
be_app->Run();
delete be_app;
return 0;
}