Files
haiku-beta6/src/servers/midi/MidiServerApp.cpp
T
mahlzeit e0438418fd Haiku be thy name.
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@8154 a95241bf-73f2-0310-859d-f6bbb57e9c96
2004-06-25 08:47:00 +00:00

851 lines
19 KiB
C++

/*
* Copyright (c) 2002-2004 Matthijs Hollemans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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.
*/
#include <Alert.h>
#include "debug.h"
#include "MidiServerApp.h"
#include "PortDrivers.h"
#include "ServerDefs.h"
#include "protocol.h"
//------------------------------------------------------------------------------
MidiServerApp::MidiServerApp()
: BApplication(MIDI_SERVER_SIGNATURE)
{
TRACE(("Running Haiku MIDI server"))
nextId = 1;
devWatcher.Start();
}
//------------------------------------------------------------------------------
MidiServerApp::~MidiServerApp()
{
for (int32 t = 0; t < CountApps(); ++t)
{
delete AppAt(t);
}
for (int32 t = 0; t < CountEndpoints(); ++t)
{
delete EndpointAt(t);
}
}
//------------------------------------------------------------------------------
void MidiServerApp::AboutRequested()
{
(new BAlert(0,
"Haiku midi_server 1.0.0 alpha\n\n"
"notes disguised as bytes\n"
"propagating to endpoints,\n"
"an aural delight",
"Okay", 0, 0, B_WIDTH_AS_USUAL,
B_INFO_ALERT))->Go();
}
//------------------------------------------------------------------------------
void MidiServerApp::MessageReceived(BMessage* msg)
{
#ifdef DEBUG
printf("IN "); msg->PrintToStream();
#endif
// About thread safety inside the midi_server: even though
// multiple apps may be sending requests to the server at
// the same time, the BLooper's thread handles passes them
// to our MessageReceived() one after the other, so there
// is no need to synchronize anything.
switch (msg->what)
{
case MSG_REGISTER_APP: OnRegisterApp(msg); break;
case MSG_CREATE_ENDPOINT: OnCreateEndpoint(msg); break;
case MSG_DELETE_ENDPOINT: OnDeleteEndpoint(msg); break;
case MSG_PURGE_ENDPOINT: OnPurgeEndpoint(msg); break;
case MSG_CHANGE_ENDPOINT: OnChangeEndpoint(msg); break;
case MSG_CONNECT_ENDPOINTS: OnConnectDisconnect(msg); break;
case MSG_DISCONNECT_ENDPOINTS: OnConnectDisconnect(msg); break;
default: super::MessageReceived(msg); break;
}
}
//------------------------------------------------------------------------------
void MidiServerApp::OnRegisterApp(BMessage* msg)
{
TRACE(("MidiServerApp::OnRegisterApp"))
// We only send the "app registered" message upon success,
// so if anything goes wrong here, we do not let the app
// know about it, and we consider it unregistered. (Most
// likely, the app is dead. If not, it freezes forever
// in anticipation of a message that will never arrive.)
app_t* app = new app_t;
if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK)
{
if (SendAllEndpoints(app))
{
if (SendAllConnections(app))
{
BMessage reply;
reply.what = MSG_APP_REGISTERED;
if (SendNotification(app, &reply))
{
apps.AddItem(app);
#ifdef DEBUG
DumpApps();
#endif
return;
}
}
}
}
delete app;
}
//------------------------------------------------------------------------------
void MidiServerApp::OnCreateEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::OnCreateEndpoint"))
status_t err;
endpoint_t* endp = new endpoint_t;
endp->app = WhichApp(msg);
if (endp->app == NULL)
{
err = B_ERROR;
}
else
{
err = B_BAD_VALUE;
if ((msg->FindBool("midi:consumer", &endp->consumer) == B_OK)
&& (msg->FindBool("midi:registered", &endp->registered) == B_OK)
&& (msg->FindString("midi:name", &endp->name) == B_OK)
&& (msg->FindMessage("midi:properties", &endp->properties) == B_OK))
{
if (endp->consumer)
{
if ((msg->FindInt32("midi:port", &endp->port) == B_OK)
&& (msg->FindInt64("midi:latency", &endp->latency) == B_OK))
{
err = B_OK;
}
}
else
{
err = B_OK;
}
}
}
BMessage reply;
if (err == B_OK)
{
endp->id = nextId++;
reply.AddInt32("midi:id", endp->id);
}
reply.AddInt32("midi:result", err);
if (SendReply(endp->app, msg, &reply) && (err == B_OK))
{
AddEndpoint(msg, endp);
}
else
{
delete endp;
}
}
//------------------------------------------------------------------------------
void MidiServerApp::OnDeleteEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::OnDeleteEndpoint"))
// Clients send the "delete endpoint" message from
// the BMidiEndpoint destructor, so there is no point
// sending a reply, because the endpoint object will
// be destroyed no matter what.
app_t* app = WhichApp(msg);
if (app != NULL)
{
endpoint_t* endp = WhichEndpoint(msg, app);
if (endp != NULL)
{
RemoveEndpoint(app, endp);
}
}
}
//------------------------------------------------------------------------------
void MidiServerApp::OnPurgeEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::OnPurgeEndpoint"))
// This performs the same task as OnDeleteEndpoint(),
// except that this message was send by the midi_server
// itself, so we don't check that the app that made the
// request really is the owner of the endpoint. (But we
// _do_ check that the message came from the server.)
if (!msg->IsSourceRemote())
{
int32 id;
if (msg->FindInt32("midi:id", &id) == B_OK)
{
endpoint_t* endp = FindEndpoint(id);
if (endp != NULL)
{
RemoveEndpoint(NULL, endp);
}
}
}
}
//------------------------------------------------------------------------------
void MidiServerApp::OnChangeEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::OnChangeEndpoint"))
endpoint_t* endp = NULL;
status_t err;
app_t* app = WhichApp(msg);
if (app == NULL)
{
err = B_ERROR;
}
else
{
endp = WhichEndpoint(msg, app);
if (endp == NULL)
{
err = B_BAD_VALUE;
}
else
{
err = B_OK;
}
}
BMessage reply;
reply.AddInt32("midi:result", err);
if (SendReply(app, msg, &reply) && (err == B_OK))
{
TRACE(("Endpoint %ld (%p) changed", endp->id, endp))
BMessage notify;
notify.what = MSG_ENDPOINT_CHANGED;
notify.AddInt32("midi:id", endp->id);
bool registered;
if (msg->FindBool("midi:registered", &registered) == B_OK)
{
notify.AddBool("midi:registered", registered);
endp->registered = registered;
}
BString name;
if (msg->FindString("midi:name", &name) == B_OK)
{
notify.AddString("midi:name", name);
endp->name = name;
}
BMessage properties;
if (msg->FindMessage("midi:properties", &properties) == B_OK)
{
notify.AddMessage("midi:properties", &properties);
endp->properties = properties;
}
bigtime_t latency;
if (msg->FindInt64("midi:latency", &latency) == B_OK)
{
notify.AddInt64("midi:latency", latency);
endp->latency = latency;
}
NotifyAll(&notify, app);
#ifdef DEBUG
DumpEndpoints();
#endif
}
}
//------------------------------------------------------------------------------
void MidiServerApp::OnConnectDisconnect(BMessage* msg)
{
TRACE(("MidiServerApp::OnConnectDisconnect"))
bool mustConnect = (msg->what == MSG_CONNECT_ENDPOINTS);
status_t err;
endpoint_t* prod = NULL;
endpoint_t* cons = NULL;
app_t* app = WhichApp(msg);
if (app == NULL)
{
err = B_ERROR;
}
else
{
err = B_BAD_VALUE;
int32 prodId, consId;
if ((msg->FindInt32("midi:producer", &prodId) == B_OK)
&& (msg->FindInt32("midi:consumer", &consId) == B_OK))
{
prod = FindEndpoint(prodId);
cons = FindEndpoint(consId);
if ((prod != NULL) && !prod->consumer)
{
if ((cons != NULL) && cons->consumer)
{
// It is an error to connect two endpoints that
// are already connected, or to disconnect two
// endpoints that are not connected at all.
if (mustConnect == prod->connections.HasItem(cons))
{
err = B_ERROR;
}
else
{
err = B_OK;
}
}
}
}
}
BMessage reply;
reply.AddInt32("midi:result", err);
if (SendReply(app, msg, &reply) && (err == B_OK))
{
if (mustConnect)
{
TRACE(("Connection made: %ld ---> %ld", prod->id, cons->id))
prod->connections.AddItem(cons);
}
else
{
TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id))
prod->connections.RemoveItem(cons);
}
BMessage notify;
MakeConnectedNotification(&notify, prod, cons, mustConnect);
NotifyAll(&notify, app);
#ifdef DEBUG
DumpEndpoints();
#endif
}
}
//------------------------------------------------------------------------------
bool MidiServerApp::SendAllEndpoints(app_t* app)
{
ASSERT(app != NULL)
BMessage notify;
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* endp = EndpointAt(t);
MakeCreatedNotification(&notify, endp);
if (!SendNotification(app, &notify)) { return false; }
}
return true;
}
//------------------------------------------------------------------------------
bool MidiServerApp::SendAllConnections(app_t* app)
{
ASSERT(app != NULL)
BMessage notify;
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* prod = EndpointAt(t);
if (!prod->consumer)
{
for (int32 k = 0; k < CountConnections(prod); ++k)
{
endpoint_t* cons = ConnectionAt(prod, k);
MakeConnectedNotification(&notify, prod, cons, true);
if (!SendNotification(app, &notify)) { return false; }
}
}
}
return true;
}
//------------------------------------------------------------------------------
void MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp)
{
ASSERT(msg != NULL)
ASSERT(endp != NULL)
ASSERT(!endpoints.HasItem(endp))
TRACE(("Endpoint %ld (%p) added", endp->id, endp))
endpoints.AddItem(endp);
BMessage notify;
MakeCreatedNotification(&notify, endp);
NotifyAll(&notify, endp->app);
#ifdef DEBUG
DumpEndpoints();
#endif
}
//------------------------------------------------------------------------------
void MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp)
{
ASSERT(endp != NULL)
ASSERT(endpoints.HasItem(endp))
TRACE(("Endpoint %ld (%p) removed", endp->id, endp))
endpoints.RemoveItem(endp);
if (endp->consumer)
{
DisconnectDeadConsumer(endp);
}
BMessage notify;
notify.what = MSG_ENDPOINT_DELETED;
notify.AddInt32("midi:id", endp->id);
NotifyAll(&notify, app);
delete endp;
#ifdef DEBUG
DumpEndpoints();
#endif
}
//------------------------------------------------------------------------------
void MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons)
{
ASSERT(cons != NULL)
ASSERT(cons->consumer)
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* prod = EndpointAt(t);
if (!prod->consumer)
{
prod->connections.RemoveItem(cons);
}
}
}
//------------------------------------------------------------------------------
void MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp)
{
ASSERT(msg != NULL)
ASSERT(endp != NULL)
msg->MakeEmpty();
msg->what = MSG_ENDPOINT_CREATED;
msg->AddInt32("midi:id", endp->id);
msg->AddBool("midi:consumer", endp->consumer);
msg->AddBool("midi:registered", endp->registered);
msg->AddString("midi:name", endp->name);
msg->AddMessage("midi:properties", &endp->properties);
if (endp->consumer)
{
msg->AddInt32("midi:port", endp->port);
msg->AddInt64("midi:latency", endp->latency);
}
}
//------------------------------------------------------------------------------
void MidiServerApp::MakeConnectedNotification(
BMessage* msg, endpoint_t* prod, endpoint_t* cons, bool mustConnect)
{
ASSERT(msg != NULL)
ASSERT(prod != NULL)
ASSERT(cons != NULL)
ASSERT(!prod->consumer)
ASSERT(cons->consumer)
msg->MakeEmpty();
if (mustConnect)
{
msg->what = MSG_ENDPOINTS_CONNECTED;
}
else
{
msg->what = MSG_ENDPOINTS_DISCONNECTED;
}
msg->AddInt32("midi:producer", prod->id);
msg->AddInt32("midi:consumer", cons->id);
}
//------------------------------------------------------------------------------
app_t* MidiServerApp::WhichApp(BMessage* msg)
{
ASSERT(msg != NULL)
BMessenger retadr = msg->ReturnAddress();
for (int32 t = 0; t < CountApps(); ++t)
{
app_t* app = AppAt(t);
if (app->messenger.Team() == retadr.Team())
{
return app;
}
}
TRACE(("Application %ld is not registered", retadr.Team()))
return NULL;
}
//------------------------------------------------------------------------------
endpoint_t* MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app)
{
ASSERT(msg != NULL)
ASSERT(app != NULL)
int32 id;
if (msg->FindInt32("midi:id", &id) == B_OK)
{
endpoint_t* endp = FindEndpoint(id);
if ((endp != NULL) && (endp->app == app))
{
return endp;
}
}
TRACE(("Endpoint not found or wrong app"))
return NULL;
}
//------------------------------------------------------------------------------
endpoint_t* MidiServerApp::FindEndpoint(int32 id)
{
if (id > 0)
{
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* endp = EndpointAt(t);
if (endp->id == id)
{
return endp;
}
}
}
TRACE(("Endpoint %ld not found", id))
return NULL;
}
//------------------------------------------------------------------------------
void MidiServerApp::NotifyAll(BMessage* msg, app_t* except)
{
ASSERT(msg != NULL)
for (int32 t = CountApps() - 1; t >= 0; --t)
{
app_t* app = AppAt(t);
if (app != except)
{
if (!SendNotification(app, msg))
{
delete (app_t*) apps.RemoveItem(t);
#ifdef DEBUG
DumpApps();
#endif
}
}
}
}
//------------------------------------------------------------------------------
bool MidiServerApp::SendNotification(app_t* app, BMessage* msg)
{
ASSERT(app != NULL)
ASSERT(msg != NULL)
status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT);
if (err != B_OK)
{
DeliveryError(app);
}
return (err == B_OK);
}
//------------------------------------------------------------------------------
bool MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply)
{
ASSERT(msg != NULL)
ASSERT(reply != NULL)
status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
if ((err != B_OK) && (app != NULL))
{
DeliveryError(app);
apps.RemoveItem(app);
delete app;
#ifdef DEBUG
DumpApps();
#endif
}
return (err == B_OK);
}
//------------------------------------------------------------------------------
void MidiServerApp::DeliveryError(app_t* app)
{
ASSERT(app != NULL)
// We cannot communicate with the app, so we assume it's
// dead. We need to remove its endpoints from the roster,
// but we cannot do that right away; removing endpoints
// triggers a bunch of new notifications and we don't want
// those to get in the way of the notifications we are
// currently sending out. Instead, we consider the death
// of an app as a separate event, and pretend that the
// now-dead app sent us delete requests for its endpoints.
TRACE(("Delivery error; unregistering app (%p)", app))
BMessage msg;
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* endp = EndpointAt(t);
if (endp->app == app)
{
msg.MakeEmpty();
msg.what = MSG_PURGE_ENDPOINT;
msg.AddInt32("midi:id", endp->id);
// It is not safe to post a message to your own
// looper's message queue, because you risk a
// deadlock if the queue is full. The chance of
// that happening is fairly small, but just in
// case, we catch it with a timeout. Because this
// situation is so unlikely, I decided to simply
// forget about the whole "purge" message then.
if (be_app_messenger.SendMessage(
&msg, (BHandler*) NULL, TIMEOUT) != B_OK)
{
WARN("Could not deliver purge message")
}
}
}
}
//------------------------------------------------------------------------------
int32 MidiServerApp::CountApps()
{
return apps.CountItems();
}
//------------------------------------------------------------------------------
app_t* MidiServerApp::AppAt(int32 index)
{
ASSERT(index >= 0 && index < CountApps())
return (app_t*) apps.ItemAt(index);
}
//------------------------------------------------------------------------------
int32 MidiServerApp::CountEndpoints()
{
return endpoints.CountItems();
}
//------------------------------------------------------------------------------
endpoint_t* MidiServerApp::EndpointAt(int32 index)
{
ASSERT(index >= 0 && index < CountEndpoints())
return (endpoint_t*) endpoints.ItemAt(index);
}
//------------------------------------------------------------------------------
int32 MidiServerApp::CountConnections(endpoint_t* prod)
{
ASSERT(prod != NULL)
ASSERT(!prod->consumer)
return prod->connections.CountItems();
}
//------------------------------------------------------------------------------
endpoint_t* MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index)
{
ASSERT(prod != NULL)
ASSERT(!prod->consumer)
ASSERT(index >= 0 && index < CountConnections(prod))
return (endpoint_t*) prod->connections.ItemAt(index);
}
//------------------------------------------------------------------------------
#ifdef DEBUG
void MidiServerApp::DumpApps()
{
printf("*** START DumpApps\n");
for (int32 t = 0; t < CountApps(); ++t)
{
app_t* app = AppAt(t);
printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team());
}
printf("*** END DumpApps\n");
}
#endif
//------------------------------------------------------------------------------
#ifdef DEBUG
void MidiServerApp::DumpEndpoints()
{
printf("*** START DumpEndpoints\n");
for (int32 t = 0; t < CountEndpoints(); ++t)
{
endpoint_t* endp = EndpointAt(t);
printf("\tendpoint %ld (%p):\n", t, endp);
printf("\t\tid %ld, name '%s', %s, %s, app %p\n",
endp->id, endp->name.String(),
endp->consumer ? "consumer" : "producer",
endp->registered ? "registered" : "unregistered",
endp->app);
printf("\t\tproperties: "); endp->properties.PrintToStream();
if (endp->consumer)
{
printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency);
}
else
{
printf("\t\tconnections:\n");
for (int32 k = 0; k < CountConnections(endp); ++k)
{
endpoint_t* cons = ConnectionAt(endp, k);
printf("\t\t\tid %ld (%p)\n", cons->id, cons);
}
}
}
printf("*** END DumpEndpoints\n");
}
#endif
//------------------------------------------------------------------------------
int main()
{
MidiServerApp app;
app.Run();
return 0;
}
//------------------------------------------------------------------------------