/* * Copyright (c) 2002, 2003 Marcus Overhagen * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files or portions * thereof (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, subject * to the following conditions: * * * 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 * in the binary, as well as this list of conditions and the following * disclaimer in the documentation and/or other materials provided with * the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ /* to comply with the license above, do not remove the following line */ char __dont_remove_copyright_from_binary[] = "Copyright (c) 2002, 2003 " "Marcus Overhagen "; #include #include #include #include #include #include #include #include #include #include #include #include "AppManager.h" #include "BufferManager.h" #include "DataExchange.h" #include "MediaDebug.h" #include "MediaFilesManager.h" #include "MediaMisc.h" #include "NodeManager.h" #include "NotificationManager.h" #include "ServerInterface.h" #include "media_server.h" AppManager* gAppManager; BufferManager* gBufferManager; MediaFilesManager* gMediaFilesManager; NodeManager* gNodeManager; NotificationManager* gNotificationManager; #define REPLY_TIMEOUT ((bigtime_t)500000) class ServerApp : public BServer { public: ServerApp(status_t& error); virtual ~ServerApp(); protected: virtual void ArgvReceived(int32 argc, char** argv); virtual void ReadyToRun(); virtual bool QuitRequested(); virtual void MessageReceived(BMessage* message); private: void _HandleMessage(int32 code, const void* data, size_t size); void _LaunchAddOnServer(); void _QuitAddOnServer(); private: port_id _ControlPort() const { return fControlPort; } static int32 _ControlThread(void* arg); BLocker fLocker; port_id fControlPort; thread_id fControlThread; }; ServerApp::ServerApp(status_t& error) : BServer(B_MEDIA_SERVER_SIGNATURE, true, &error), fLocker("media server locker") { gNotificationManager = new NotificationManager; gBufferManager = new BufferManager; gAppManager = new AppManager; gNodeManager = new NodeManager; gMediaFilesManager = new MediaFilesManager; fControlPort = create_port(64, MEDIA_SERVER_PORT_NAME); fControlThread = spawn_thread(_ControlThread, "media_server control", 105, this); resume_thread(fControlThread); if (be_roster->StartWatching(BMessenger(this, this), B_REQUEST_QUIT) != B_OK) { TRACE("ServerApp: Can't find the registrar."); } } ServerApp::~ServerApp() { TRACE("ServerApp::~ServerApp()\n"); delete_port(fControlPort); wait_for_thread(fControlThread, NULL); if (be_roster->StopWatching(BMessenger(this, this)) != B_OK) TRACE("ServerApp: Can't unregister roster notifications."); delete gNotificationManager; delete gBufferManager; delete gAppManager; delete gNodeManager; delete gMediaFilesManager; } void ServerApp::ReadyToRun() { gNodeManager->LoadState(); // make sure any previous media_addon_server is gone _QuitAddOnServer(); // and start a new one _LaunchAddOnServer(); } bool ServerApp::QuitRequested() { TRACE("ServerApp::QuitRequested()\n"); gMediaFilesManager->SaveState(); gNodeManager->SaveState(); _QuitAddOnServer(); return true; } void ServerApp::ArgvReceived(int32 argc, char **argv) { for (int arg = 1; arg < argc; arg++) { if (strstr(argv[arg], "dump") != NULL) { gAppManager->Dump(); gNodeManager->Dump(); gBufferManager->Dump(); gNotificationManager->Dump(); gMediaFilesManager->Dump(); } if (strstr(argv[arg], "buffer") != NULL) gBufferManager->Dump(); if (strstr(argv[arg], "node") != NULL) gNodeManager->Dump(); if (strstr(argv[arg], "files") != NULL) gMediaFilesManager->Dump(); if (strstr(argv[arg], "quit") != NULL) PostMessage(B_QUIT_REQUESTED); } } void ServerApp::_LaunchAddOnServer() { // Try to launch media_addon_server by mime signature. // If it fails (for example on the Live CD, where the executable // hasn't yet been mimesetted), try from this application's // directory status_t err = be_roster->Launch(B_MEDIA_ADDON_SERVER_SIGNATURE); if (err == B_OK) return; app_info info; BEntry entry; BDirectory dir; entry_ref ref; err = GetAppInfo(&info); err |= entry.SetTo(&info.ref); err |= entry.GetParent(&entry); err |= dir.SetTo(&entry); err |= entry.SetTo(&dir, "media_addon_server"); err |= entry.GetRef(&ref); if (err == B_OK) be_roster->Launch(&ref); if (err == B_OK) return; BAlert* alert = new BAlert("media_server", "Launching media_addon_server " "failed.\n\nmedia_server will terminate", "OK"); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(); fprintf(stderr, "Launching media_addon_server (%s) failed: %s\n", B_MEDIA_ADDON_SERVER_SIGNATURE, strerror(err)); exit(1); } void ServerApp::_QuitAddOnServer() { // nothing to do if it's already terminated if (!be_roster->IsRunning(B_MEDIA_ADDON_SERVER_SIGNATURE)) return; // send a quit request to the media_addon_server BMessenger msger(B_MEDIA_ADDON_SERVER_SIGNATURE); if (!msger.IsValid()) { ERROR("Trouble terminating media_addon_server. Messenger invalid\n"); } else { BMessage msg(B_QUIT_REQUESTED); status_t err = msger.SendMessage(&msg, (BHandler *)NULL, 2000000); // 2 sec timeout if (err != B_OK) { ERROR("Trouble terminating media_addon_server (2): %s\n", strerror(err)); } } // wait 5 seconds for it to terminate for (int i = 0; i < 50; i++) { if (!be_roster->IsRunning(B_MEDIA_ADDON_SERVER_SIGNATURE)) return; snooze(100000); // 100 ms } // try to kill it (or many of them), up to 10 seconds for (int i = 0; i < 50; i++) { team_id id = be_roster->TeamFor(B_MEDIA_ADDON_SERVER_SIGNATURE); if (id < 0) break; kill_team(id); snooze(200000); // 200 ms } if (be_roster->IsRunning(B_MEDIA_ADDON_SERVER_SIGNATURE)) { ERROR("Trouble terminating media_addon_server, it's still running\n"); } } void ServerApp::_HandleMessage(int32 code, const void* data, size_t size) { TRACE("ServerApp::HandleMessage %#" B_PRIx32 " enter\n", code); switch (code) { case SERVER_CHANGE_FLAVOR_INSTANCES_COUNT: { const server_change_flavor_instances_count_request& request = *static_cast< const server_change_flavor_instances_count_request*>(data); server_change_flavor_instances_count_reply reply; status_t status = B_BAD_VALUE; if (request.delta == 1) { status = gNodeManager->IncrementFlavorInstancesCount( request.add_on_id, request.flavor_id, request.team); } else if (request.delta == -1) { status = gNodeManager->DecrementFlavorInstancesCount( request.add_on_id, request.flavor_id, request.team); } request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_RESCAN_DEFAULTS: { gNodeManager->RescanDefaultNodes(); break; } case SERVER_REGISTER_APP: { const server_register_app_request& request = *static_cast< const server_register_app_request*>(data); server_register_app_reply reply; status_t status = gAppManager->RegisterTeam(request.team, request.messenger); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_UNREGISTER_APP: { const server_unregister_app_request& request = *static_cast< const server_unregister_app_request*>(data); server_unregister_app_reply reply; status_t status = gAppManager->UnregisterTeam(request.team); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_ADD_ON_REF: { const server_get_add_on_ref_request& request = *static_cast< const server_get_add_on_ref_request*>(data); server_get_add_on_ref_reply reply; entry_ref ref; reply.result = gNodeManager->GetAddOnRef(request.add_on_id, &ref); reply.ref = ref; request.SendReply(reply.result, &reply, sizeof(reply)); break; } case SERVER_NODE_ID_FOR: { const server_node_id_for_request& request = *static_cast(data); server_node_id_for_reply reply; status_t status = gNodeManager->FindNodeID(request.port, &reply.node_id); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_LIVE_NODE_INFO: { const server_get_live_node_info_request& request = *static_cast< const server_get_live_node_info_request*>(data); server_get_live_node_info_reply reply; status_t status = gNodeManager->GetLiveNodeInfo(request.node, &reply.live_info); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_LIVE_NODES: { const server_get_live_nodes_request& request = *static_cast(data); server_get_live_nodes_reply reply; LiveNodeList nodes; status_t status = gNodeManager->GetLiveNodes(nodes, request.max_count, request.has_input ? &request.input_format : NULL, request.has_output ? &request.output_format : NULL, request.has_name ? request.name : NULL, request.require_kinds); reply.count = nodes.size(); reply.area = -1; live_node_info* infos = reply.live_info; area_id area = -1; if (reply.count > MAX_LIVE_INFO) { // We create an area here, and transfer it to the client size_t size = (reply.count * sizeof(live_node_info) + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1); area = create_area("get live nodes", (void**)&infos, B_ANY_ADDRESS, size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA); if (area < 0) { reply.area = area; reply.count = 0; } } for (int32 index = 0; index < reply.count; index++) infos[index] = nodes[index]; if (area >= 0) { // transfer the area to the target team reply.area = _kern_transfer_area(area, &reply.address, B_ANY_ADDRESS, request.team); if (reply.area < 0) { delete_area(area); reply.count = 0; } } status = request.SendReply(status, &reply, sizeof(reply)); if (status != B_OK && reply.area >= 0) { // if we couldn't send the message, delete the area delete_area(reply.area); } break; } case SERVER_GET_NODE_FOR: { const server_get_node_for_request& request = *static_cast(data); server_get_node_for_reply reply; status_t status = gNodeManager->GetCloneForID(request.node_id, request.team, &reply.clone); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_RELEASE_NODE: { const server_release_node_request& request = *static_cast(data); server_release_node_reply reply; status_t status = gNodeManager->ReleaseNode(request.node, request.team); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_RELEASE_NODE_ALL: { const server_release_node_request& request = *static_cast(data); server_release_node_reply reply; status_t status = gNodeManager->ReleaseNodeAll(request.node.node); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_REGISTER_NODE: { const server_register_node_request& request = *static_cast(data); server_register_node_reply reply; status_t status = gNodeManager->RegisterNode(request.add_on_id, request.flavor_id, request.name, request.kinds, request.port, request.team, request.timesource_id, &reply.node_id); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_UNREGISTER_NODE: { const server_unregister_node_request& request = *static_cast(data); server_unregister_node_reply reply; status_t status = gNodeManager->UnregisterNode(request.node_id, request.team, &reply.add_on_id, &reply.flavor_id); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_PUBLISH_INPUTS: { const server_publish_inputs_request& request = *static_cast(data); server_publish_inputs_reply reply; status_t status; if (request.count <= MAX_INPUTS) { status = gNodeManager->PublishInputs(request.node, request.inputs, request.count); } else { media_input* inputs; area_id clone; clone = clone_area("media_inputs clone", (void**)&inputs, B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, request.area); if (clone < B_OK) { ERROR("SERVER_PUBLISH_INPUTS: failed to clone area, " "error %#" B_PRIx32 "\n", clone); status = clone; } else { status = gNodeManager->PublishInputs(request.node, inputs, request.count); delete_area(clone); } } request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_PUBLISH_OUTPUTS: { const server_publish_outputs_request& request = *static_cast(data); server_publish_outputs_reply reply; status_t status; if (request.count <= MAX_OUTPUTS) { status = gNodeManager->PublishOutputs(request.node, request.outputs, request.count); } else { media_output* outputs; area_id clone; clone = clone_area("media_outputs clone", (void**)&outputs, B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, request.area); if (clone < B_OK) { ERROR("SERVER_PUBLISH_OUTPUTS: failed to clone area, " "error %#" B_PRIx32 "\n", clone); status = clone; } else { status = gNodeManager->PublishOutputs(request.node, outputs, request.count); delete_area(clone); } } request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_NODE: { const server_get_node_request& request = *static_cast(data); server_get_node_reply reply; status_t status = gNodeManager->GetClone(request.type, request.team, &reply.node, reply.input_name, &reply.input_id); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_SET_NODE: { const server_set_node_request& request = *static_cast(data); server_set_node_reply reply; status_t status = gNodeManager->SetDefaultNode(request.type, request.use_node ? &request.node : NULL, request.use_dni ? &request.dni : NULL, request.use_input ? &request.input : NULL); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_DORMANT_NODE_FOR: { const server_get_dormant_node_for_request& request = *static_cast( data); server_get_dormant_node_for_reply reply; status_t status = gNodeManager->GetDormantNodeInfo(request.node, &reply.node_info); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_INSTANCES_FOR: { const server_get_instances_for_request& request = *static_cast(data); server_get_instances_for_reply reply; status_t status = gNodeManager->GetInstances(request.add_on_id, request.flavor_id, reply.node_id, &reply.count, min_c(request.max_count, MAX_NODE_ID)); if (reply.count == MAX_NODE_ID && request.max_count > MAX_NODE_ID) { // TODO: might be fixed by using an area PRINT(1, "Warning: SERVER_GET_INSTANCES_FOR: returning " "possibly truncated list of node id's\n"); } request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_SET_NODE_TIMESOURCE: { const server_set_node_timesource_request& request = *static_cast(data); server_set_node_timesource_reply reply; status_t result = gNodeManager->SetNodeTimeSource(request.node_id, request.timesource_id); request.SendReply(result, &reply, sizeof(reply)); break; } case SERVER_REGISTER_ADD_ON: { const server_register_add_on_request& request = *static_cast< const server_register_add_on_request*>(data); server_register_add_on_reply reply; gNodeManager->RegisterAddOn(request.ref, &reply.add_on_id); request.SendReply(B_OK, &reply, sizeof(reply)); break; } case SERVER_UNREGISTER_ADD_ON: { const server_unregister_add_on_command& request = *static_cast< const server_unregister_add_on_command*>(data); gNodeManager->UnregisterAddOn(request.add_on_id); break; } case SERVER_REGISTER_DORMANT_NODE: { const server_register_dormant_node_command& command = *static_cast( data); if (command.purge_id > 0) gNodeManager->InvalidateDormantFlavorInfo(command.purge_id); dormant_flavor_info dormantFlavorInfo; status_t status = dormantFlavorInfo.Unflatten(command.type, command.flattened_data, command.flattened_size); if (status == B_OK) gNodeManager->AddDormantFlavorInfo(dormantFlavorInfo); break; } case SERVER_GET_DORMANT_NODES: { const server_get_dormant_nodes_request& request = *static_cast(data); server_get_dormant_nodes_reply reply; reply.count = request.max_count; dormant_node_info* infos = new(std::nothrow) dormant_node_info[reply.count]; if (infos != NULL) { reply.result = gNodeManager->GetDormantNodes(infos, &reply.count, request.has_input ? &request.input_format : NULL, request.has_output ? &request.output_format : NULL, request.has_name ? request.name : NULL, request.require_kinds, request.deny_kinds); } else reply.result = B_NO_MEMORY; if (reply.result != B_OK) reply.count = 0; request.SendReply(reply.result, &reply, sizeof(reply)); if (reply.count > 0) { write_port(request.reply_port, 0, infos, reply.count * sizeof(dormant_node_info)); } delete[] infos; break; } case SERVER_GET_DORMANT_FLAVOR_INFO: { const server_get_dormant_flavor_info_request& request = *static_cast( data); dormant_flavor_info dormantFlavorInfo; status_t status = gNodeManager->GetDormantFlavorInfoFor( request.add_on_id, request.flavor_id, &dormantFlavorInfo); if (status != B_OK) { server_get_dormant_flavor_info_reply reply; reply.result = status; request.SendReply(reply.result, &reply, sizeof(reply)); } else { size_t replySize = sizeof(server_get_dormant_flavor_info_reply) + dormantFlavorInfo.FlattenedSize(); server_get_dormant_flavor_info_reply* reply = (server_get_dormant_flavor_info_reply*)malloc( replySize); if (reply != NULL) { reply->type = dormantFlavorInfo.TypeCode(); reply->flattened_size = dormantFlavorInfo.FlattenedSize(); reply->result = dormantFlavorInfo.Flatten( reply->flattened_data, reply->flattened_size); request.SendReply(reply->result, reply, replySize); free(reply); } else { server_get_dormant_flavor_info_reply reply; reply.result = B_NO_MEMORY; request.SendReply(reply.result, &reply, sizeof(reply)); } } break; } case SERVER_SET_NODE_CREATOR: { const server_set_node_creator_request& request = *static_cast(data); server_set_node_creator_reply reply; status_t status = gNodeManager->SetNodeCreator(request.node, request.creator); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_SHARED_BUFFER_AREA: { const server_get_shared_buffer_area_request& request = *static_cast( data); server_get_shared_buffer_area_reply reply; reply.area = gBufferManager->SharedBufferListArea(); request.SendReply(reply.area >= 0 ? B_OK : reply.area, &reply, sizeof(reply)); break; } case SERVER_REGISTER_BUFFER: { const server_register_buffer_request& request = *static_cast(data); server_register_buffer_reply reply; status_t status; if (request.info.buffer == 0) { reply.info = request.info; // size, offset, flags, area is kept // get a new beuffer id into reply.info.buffer status = gBufferManager->RegisterBuffer(request.team, request.info.size, request.info.flags, request.info.offset, request.info.area, &reply.info.buffer); } else { reply.info = request.info; // buffer id is kept status = gBufferManager->RegisterBuffer(request.team, request.info.buffer, &reply.info.size, &reply.info.flags, &reply.info.offset, &reply.info.area); } request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_UNREGISTER_BUFFER: { const server_unregister_buffer_command& request = *static_cast< const server_unregister_buffer_command*>(data); gBufferManager->UnregisterBuffer(request.team, request.buffer_id); break; } case SERVER_GET_MEDIA_FILE_TYPES: { const server_get_media_types_request& request = *static_cast(data); server_get_media_types_reply reply; area_id area = gMediaFilesManager->GetTypesArea(reply.count); if (area >= 0) { // transfer the area to the target team reply.area = _kern_transfer_area(area, &reply.address, B_ANY_ADDRESS, request.team); if (reply.area < 0) { delete_area(area); reply.area = B_ERROR; reply.count = 0; } } status_t status = request.SendReply( reply.area < 0 ? reply.area : B_OK, &reply, sizeof(reply)); if (status != B_OK) { // if we couldn't send the message, delete the area delete_area(reply.area); } break; } case SERVER_GET_MEDIA_FILE_ITEMS: { const server_get_media_items_request& request = *static_cast(data); server_get_media_items_reply reply; area_id area = gMediaFilesManager->GetItemsArea(request.type, reply.count); if (area >= 0) { // transfer the area to the target team reply.area = _kern_transfer_area(area, &reply.address, B_ANY_ADDRESS, request.team); if (reply.area < 0) { delete_area(area); reply.area = B_ERROR; reply.count = 0; } } else reply.area = area; status_t status = request.SendReply( reply.area < 0 ? reply.area : B_OK, &reply, sizeof(reply)); if (status != B_OK) { // if we couldn't send the message, delete the area delete_area(reply.area); } break; } case SERVER_GET_REF_FOR: { const server_get_ref_for_request& request = *static_cast(data); server_get_ref_for_reply reply; entry_ref* ref; status_t status = gMediaFilesManager->GetRefFor(request.type, request.item, &ref); if (status == B_OK) reply.ref = *ref; request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_SET_REF_FOR: { const server_set_ref_for_request& request = *static_cast(data); server_set_ref_for_reply reply; entry_ref ref = request.ref; status_t status = gMediaFilesManager->SetRefFor(request.type, request.item, ref); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_INVALIDATE_MEDIA_ITEM: { const server_invalidate_item_request& request = *static_cast(data); server_invalidate_item_reply reply; status_t status = gMediaFilesManager->InvalidateItem(request.type, request.item); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_REMOVE_MEDIA_ITEM: { const server_remove_media_item_request& request = *static_cast(data); server_remove_media_item_reply reply; status_t status = gMediaFilesManager->RemoveItem(request.type, request.item); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_GET_ITEM_AUDIO_GAIN: { const server_get_item_audio_gain_request& request = *static_cast(data); server_get_item_audio_gain_reply reply; status_t status = gMediaFilesManager->GetAudioGainFor(request.type, request.item, &reply.gain); request.SendReply(status, &reply, sizeof(reply)); break; } case SERVER_SET_ITEM_AUDIO_GAIN: { const server_set_item_audio_gain_request& request = *static_cast(data); server_set_ref_for_reply reply; status_t status = gMediaFilesManager->SetAudioGainFor(request.type, request.item, request.gain); request.SendReply(status, &reply, sizeof(reply)); break; } default: printf("media_server: received unknown message code %#08" B_PRIx32 "\n", code); } TRACE("ServerApp::HandleMessage %#" B_PRIx32 " leave\n", code); } status_t ServerApp::_ControlThread(void* _server) { ServerApp* server = (ServerApp*)_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 ServerApp::MessageReceived(BMessage* msg) { TRACE("ServerApp::MessageReceived %" B_PRIu32 " enter\n", msg->what); switch (msg->what) { case MEDIA_SERVER_REQUEST_NOTIFICATIONS: case MEDIA_SERVER_CANCEL_NOTIFICATIONS: case MEDIA_SERVER_SEND_NOTIFICATIONS: gNotificationManager->EnqueueMessage(msg); break; case MEDIA_FILES_MANAGER_SAVE_TIMER: gMediaFilesManager->TimerMessage(); break; case MEDIA_SERVER_ADD_SYSTEM_BEEP_EVENT: gMediaFilesManager->HandleAddSystemBeepEvent(msg); break; case MEDIA_SERVER_RESCAN_COMPLETED: gAppManager->NotifyRosters(); break; case B_SOME_APP_QUIT: { BString mimeSig; if (msg->FindString("be:signature", &mimeSig) != B_OK) return; if (mimeSig == B_MEDIA_ADDON_SERVER_SIGNATURE) gNodeManager->CleanupDormantFlavorInfos(); team_id id; if (msg->FindInt32("be:team", &id) == B_OK && gAppManager->HasTeam(id)) { gAppManager->UnregisterTeam(id); } break; } default: BApplication::MessageReceived(msg); TRACE("\nmedia_server: unknown message received!\n"); break; } TRACE("ServerApp::MessageReceived %" B_PRIu32 " leave\n", msg->what); } // #pragma mark - int main() { status_t status; ServerApp app(status); if (status == B_OK) app.Run(); return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE; }