xref: /haiku/src/servers/midi/MidiServerApp.cpp (revision 46b7da1f4f40f7157d74fc7fb26ff9ec7f2416f2)
1 /*
2  * Copyright 2002-2015, Haiku, Inc. All rights reserved.
3  * Copyright 2002-2004, Matthijs Hollemans
4  * Copyright 2021, Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
5  * Distributed under the terms of the MIT License.
6  *
7  * Authors:
8  *		Humdinger
9  *		Matthijs Hollemans
10  *		Oliver Tappe
11  *		Panagiotis "Ivory" Vasilopoulos
12  *		Philippe Houdoin
13  */
14 
15 
16 #include "MidiServerApp.h"
17 
18 #include <new>
19 
20 #include <AboutWindow.h>
21 #include <Catalog.h>
22 #include <Locale.h>
23 #include <LocaleRoster.h>
24 
25 #include "debug.h"
26 #include "protocol.h"
27 #include "PortDrivers.h"
28 #include "ServerDefs.h"
29 
30 
31 using std::nothrow;
32 
33 
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "midi_server"
36 
37 
MidiServerApp(status_t & error)38 MidiServerApp::MidiServerApp(status_t& error)
39 	:
40 	BServer(MIDI_SERVER_SIGNATURE, true, &error)
41 {
42 	TRACE(("Running Haiku MIDI server"))
43 
44 	fNextID = 1;
45 	fDeviceWatcher = new(std::nothrow) DeviceWatcher();
46 	if (fDeviceWatcher != NULL)
47 		fDeviceWatcher->Run();
48 }
49 
50 
~MidiServerApp()51 MidiServerApp::~MidiServerApp()
52 {
53 	if (fDeviceWatcher && fDeviceWatcher->Lock())
54 		fDeviceWatcher->Quit();
55 
56 	for (int32 t = 0; t < _CountApps(); ++t) {
57 		delete _AppAt(t);
58 	}
59 
60 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
61 		delete _EndpointAt(t);
62 	}
63 }
64 
65 
66 void
AboutRequested()67 MidiServerApp::AboutRequested()
68 {
69 	BAboutWindow* window = new BAboutWindow(B_TRANSLATE_SYSTEM_NAME(
70 		"Haiku MIDI Server"), MIDI_SERVER_SIGNATURE);
71 	window->AddDescription(B_TRANSLATE(
72 		"Notes disguised as bytes\n"
73 		"propagating to endpoints-\n"
74 		"An aural delight."));
75 
76 	const char* extraCopyrights[] = {
77 		"2002-2004 Matthijs Hollemans",
78 		"2021 Panagiotis \"Ivory\" Vasilopoulos",
79 		NULL
80 	};
81 
82 	const char* authors[] = {
83 		"Humdinger",
84 		"Matthijs Hollemans",
85 		"Oliver Tappe",
86 		"Panagiotis \"Ivory\" Vasilopoulos",
87 		"Philippe Houdoin",
88 		NULL
89 	};
90 
91 	window->AddCopyright(2021, "Haiku, Inc.", extraCopyrights);
92 	window->AddAuthors(authors);
93 
94 	window->Show();
95 }
96 
97 
98 void
MessageReceived(BMessage * msg)99 MidiServerApp::MessageReceived(BMessage* msg)
100 {
101 #ifdef DEBUG
102 	printf("IN "); msg->PrintToStream();
103 #endif
104 
105 	switch (msg->what) {
106 		case MSG_REGISTER_APP:
107 			_OnRegisterApp(msg);
108 			break;
109 		case MSG_CREATE_ENDPOINT:
110 			_OnCreateEndpoint(msg);
111 			break;
112 		case MSG_DELETE_ENDPOINT:
113 			_OnDeleteEndpoint(msg);
114 			break;
115 		case MSG_PURGE_ENDPOINT:
116 			_OnPurgeEndpoint(msg);
117 			break;
118 		case MSG_CHANGE_ENDPOINT:
119 			_OnChangeEndpoint(msg);
120 			break;
121 		case MSG_CONNECT_ENDPOINTS:
122 			_OnConnectDisconnect(msg);
123 			break;
124 		case MSG_DISCONNECT_ENDPOINTS:
125 			_OnConnectDisconnect(msg);
126 			break;
127 
128 		default:
129 			super::MessageReceived(msg);
130 			break;
131 	}
132 }
133 
134 
135 void
_OnRegisterApp(BMessage * msg)136 MidiServerApp::_OnRegisterApp(BMessage* msg)
137 {
138 	TRACE(("MidiServerApp::_OnRegisterApp"))
139 
140 	// We only send the "app registered" message upon success,
141 	// so if anything goes wrong here, we do not let the app
142 	// know about it, and we consider it unregistered. (Most
143 	// likely, the app is dead. If not, it freezes forever
144 	// in anticipation of a message that will never arrive.)
145 
146 	app_t* app = new app_t;
147 
148 	if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK
149 		&& _SendAllEndpoints(app)
150 		&& _SendAllConnections(app)) {
151 		BMessage reply;
152 		reply.what = MSG_APP_REGISTERED;
153 
154 		if (_SendNotification(app, &reply)) {
155 			fApps.AddItem(app);
156 #ifdef DEBUG
157 			_DumpApps();
158 #endif
159 			return;
160 		}
161 	}
162 
163 	delete app;
164 }
165 
166 
167 void
_OnCreateEndpoint(BMessage * msg)168 MidiServerApp::_OnCreateEndpoint(BMessage* msg)
169 {
170 	TRACE(("MidiServerApp::_OnCreateEndpoint"))
171 
172 	status_t status;
173 	endpoint_t* endpoint = new endpoint_t;
174 
175 	endpoint->app = _WhichApp(msg);
176 	if (endpoint->app == NULL) {
177 		status = B_ERROR;
178 	} else {
179 		status = B_BAD_VALUE;
180 
181 		if (msg->FindBool("midi:consumer", &endpoint->consumer) == B_OK
182 			&& msg->FindBool("midi:registered", &endpoint->registered) == B_OK
183 			&& msg->FindString("midi:name", &endpoint->name) == B_OK
184 			&& msg->FindMessage("midi:properties", &endpoint->properties)
185 					== B_OK) {
186 			if (endpoint->consumer) {
187 				if (msg->FindInt32("midi:port", &endpoint->port) == B_OK
188 					&& msg->FindInt64("midi:latency", &endpoint->latency)
189 							== B_OK)
190 					status = B_OK;
191 			} else
192 				status = B_OK;
193 		}
194 	}
195 
196 	BMessage reply;
197 
198 	if (status == B_OK) {
199 		endpoint->id = fNextID++;
200 		reply.AddInt32("midi:id", endpoint->id);
201 	}
202 
203 	reply.AddInt32("midi:result", status);
204 
205 	if (_SendReply(endpoint->app, msg, &reply) && status == B_OK)
206 		_AddEndpoint(msg, endpoint);
207 	else
208 		delete endpoint;
209 }
210 
211 
212 void
_OnDeleteEndpoint(BMessage * msg)213 MidiServerApp::_OnDeleteEndpoint(BMessage* msg)
214 {
215 	TRACE(("MidiServerApp::_OnDeleteEndpoint"))
216 
217 	// Clients send the "delete endpoint" message from
218 	// the BMidiEndpoint destructor, so there is no point
219 	// sending a reply, because the endpoint object will
220 	// be destroyed no matter what.
221 
222 	app_t* app = _WhichApp(msg);
223 	if (app != NULL) {
224 		endpoint_t* endpoint = _WhichEndpoint(msg, app);
225 		if (endpoint != NULL)
226 			_RemoveEndpoint(app, endpoint);
227 	}
228 }
229 
230 
231 void
_OnPurgeEndpoint(BMessage * msg)232 MidiServerApp::_OnPurgeEndpoint(BMessage* msg)
233 {
234 	TRACE(("MidiServerApp::_OnPurgeEndpoint"))
235 
236 	// This performs the same task as OnDeleteEndpoint(),
237 	// except that this message was send by the midi_server
238 	// itself, so we don't check that the app that made the
239 	// request really is the owner of the endpoint. (But we
240 	// _do_ check that the message came from the server.)
241 
242 	if (!msg->IsSourceRemote()) {
243 		int32 id;
244 		if (msg->FindInt32("midi:id", &id) == B_OK) {
245 			endpoint_t* endpoint = _FindEndpoint(id);
246 			if (endpoint != NULL)
247 				_RemoveEndpoint(NULL, endpoint);
248 		}
249 	}
250 }
251 
252 
253 void
_OnChangeEndpoint(BMessage * msg)254 MidiServerApp::_OnChangeEndpoint(BMessage* msg)
255 {
256 	TRACE(("MidiServerApp::_OnChangeEndpoint"))
257 
258 	endpoint_t* endpoint = NULL;
259 	status_t status;
260 
261 	app_t* app = _WhichApp(msg);
262 	if (app == NULL)
263 		status = B_ERROR;
264 	else {
265 		endpoint = _WhichEndpoint(msg, app);
266 		if (endpoint == NULL)
267 			status = B_BAD_VALUE;
268 		else
269 			status = B_OK;
270 	}
271 
272 	BMessage reply;
273 	reply.AddInt32("midi:result", status);
274 
275 	if (_SendReply(app, msg, &reply) && status == B_OK) {
276 		TRACE(("Endpoint %" B_PRId32 " (%p) changed", endpoint->id, endpoint))
277 
278 		BMessage notify;
279 		notify.what = MSG_ENDPOINT_CHANGED;
280 		notify.AddInt32("midi:id", endpoint->id);
281 
282 		bool registered;
283 		if (msg->FindBool("midi:registered", &registered) == B_OK) {
284 			notify.AddBool("midi:registered", registered);
285 			endpoint->registered = registered;
286 		}
287 
288 		BString name;
289 		if (msg->FindString("midi:name", &name) == B_OK) {
290 			notify.AddString("midi:name", name);
291 			endpoint->name = name;
292 		}
293 
294 		BMessage properties;
295 		if (msg->FindMessage("midi:properties", &properties) == B_OK) {
296 			notify.AddMessage("midi:properties", &properties);
297 			endpoint->properties = properties;
298 		}
299 
300 		bigtime_t latency;
301 		if (msg->FindInt64("midi:latency", &latency) == B_OK) {
302 			notify.AddInt64("midi:latency", latency);
303 			endpoint->latency = latency;
304 		}
305 
306 		_NotifyAll(&notify, app);
307 
308 #ifdef DEBUG
309 		_DumpEndpoints();
310 #endif
311 	}
312 }
313 
314 
315 void
_OnConnectDisconnect(BMessage * msg)316 MidiServerApp::_OnConnectDisconnect(BMessage* msg)
317 {
318 	TRACE(("MidiServerApp::_OnConnectDisconnect"))
319 
320 	bool mustConnect = msg->what == MSG_CONNECT_ENDPOINTS;
321 
322 	status_t status;
323 	endpoint_t* producer = NULL;
324 	endpoint_t* consumer = NULL;
325 
326 	app_t* app = _WhichApp(msg);
327 	if (app == NULL)
328 		status = B_ERROR;
329 	else {
330 		status = B_BAD_VALUE;
331 
332 		int32 producerID;
333 		int32 consumerID;
334 		if (msg->FindInt32("midi:producer", &producerID) == B_OK
335 			&& msg->FindInt32("midi:consumer", &consumerID) == B_OK) {
336 			producer = _FindEndpoint(producerID);
337 			consumer = _FindEndpoint(consumerID);
338 
339 			if (producer != NULL && !producer->consumer) {
340 				if (consumer != NULL && consumer->consumer) {
341 					// It is an error to connect two endpoints that
342 					// are already connected, or to disconnect two
343 					// endpoints that are not connected at all.
344 
345 					if (mustConnect == producer->connections.HasItem(consumer))
346 						status = B_ERROR;
347 					else
348 						status = B_OK;
349 				}
350 			}
351 		}
352 	}
353 
354 	BMessage reply;
355 	reply.AddInt32("midi:result", status);
356 
357 	if (_SendReply(app, msg, &reply) && status == B_OK) {
358 		if (mustConnect) {
359 			TRACE(("Connection made: %" B_PRId32 " ---> %" B_PRId32,
360 				producer->id, consumer->id))
361 
362 			producer->connections.AddItem(consumer);
363 		} else {
364 			TRACE(("Connection broken: %" B_PRId32 " -X-> %" B_PRId32,
365 				producer->id, consumer->id))
366 
367 			producer->connections.RemoveItem(consumer);
368 		}
369 
370 		BMessage notify;
371 		_MakeConnectedNotification(&notify, producer, consumer, mustConnect);
372 		_NotifyAll(&notify, app);
373 
374 #ifdef DEBUG
375 		_DumpEndpoints();
376 #endif
377 	}
378 }
379 
380 
381 /*!	Sends an app MSG_ENDPOINT_CREATED notifications for
382 	all current endpoints. Used when the app registers.
383 */
384 bool
_SendAllEndpoints(app_t * app)385 MidiServerApp::_SendAllEndpoints(app_t* app)
386 {
387 	ASSERT(app != NULL)
388 
389 	BMessage notify;
390 
391 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
392 		endpoint_t* endpoint = _EndpointAt(t);
393 
394 		_MakeCreatedNotification(&notify, endpoint);
395 
396 		if (!_SendNotification(app, &notify))
397 			return false;
398 	}
399 
400 	return true;
401 }
402 
403 
404 /*!	Sends an app MSG_ENDPOINTS_CONNECTED notifications for
405 	all current connections. Used when the app registers.
406 */
407 bool
_SendAllConnections(app_t * app)408 MidiServerApp::_SendAllConnections(app_t* app)
409 {
410 	ASSERT(app != NULL)
411 
412 	BMessage notify;
413 
414 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
415 		endpoint_t* producer = _EndpointAt(t);
416 		if (!producer->consumer) {
417 			for (int32 k = 0; k < _CountConnections(producer); ++k) {
418 				endpoint_t* consumer = _ConnectionAt(producer, k);
419 
420 				_MakeConnectedNotification(&notify, producer, consumer, true);
421 
422 				if (!_SendNotification(app, &notify))
423 					return false;
424 			}
425 		}
426 	}
427 
428 	return true;
429 }
430 
431 
432 /*!	Adds the specified endpoint to the roster, and notifies
433 	all other applications about this event.
434 */
435 void
_AddEndpoint(BMessage * msg,endpoint_t * endpoint)436 MidiServerApp::_AddEndpoint(BMessage* msg, endpoint_t* endpoint)
437 {
438 	ASSERT(msg != NULL)
439 	ASSERT(endpoint != NULL)
440 	ASSERT(!fEndpoints.HasItem(endpoint))
441 
442 	TRACE(("Endpoint %" B_PRId32 " (%p) added", endpoint->id, endpoint))
443 
444 	fEndpoints.AddItem(endpoint);
445 
446 	BMessage notify;
447 	_MakeCreatedNotification(&notify, endpoint);
448 	_NotifyAll(&notify, endpoint->app);
449 
450 #ifdef DEBUG
451 	_DumpEndpoints();
452 #endif
453 }
454 
455 
456 /*!	Removes an endpoint from the roster, and notifies all
457 	other apps about this event. "app" is the application
458 	that the endpoint belongs to; if it is NULL, the app
459 	no longer exists and we're purging the endpoint.
460 */
461 void
_RemoveEndpoint(app_t * app,endpoint_t * endpoint)462 MidiServerApp::_RemoveEndpoint(app_t* app, endpoint_t* endpoint)
463 {
464 	ASSERT(endpoint != NULL)
465 	ASSERT(fEndpoints.HasItem(endpoint))
466 
467 	TRACE(("Endpoint %" B_PRId32 " (%p) removed", endpoint->id, endpoint))
468 
469 	fEndpoints.RemoveItem(endpoint);
470 
471 	if (endpoint->consumer)
472 		_DisconnectDeadConsumer(endpoint);
473 
474 	BMessage notify;
475 	notify.what = MSG_ENDPOINT_DELETED;
476 	notify.AddInt32("midi:id", endpoint->id);
477 	_NotifyAll(&notify, app);
478 
479 	delete endpoint;
480 
481 #ifdef DEBUG
482 	_DumpEndpoints();
483 #endif
484 }
485 
486 
487 /*!	Removes a consumer from the list of connections of
488 	all the producers it is connected to, just before
489 	we remove it from the roster.
490 */
491 void
_DisconnectDeadConsumer(endpoint_t * consumer)492 MidiServerApp::_DisconnectDeadConsumer(endpoint_t* consumer)
493 {
494 	ASSERT(consumer != NULL)
495 	ASSERT(consumer->consumer)
496 
497 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
498 		endpoint_t* producer = _EndpointAt(t);
499 		if (!producer->consumer)
500 			producer->connections.RemoveItem(consumer);
501 	}
502 }
503 
504 
505 //! Fills up a MSG_ENDPOINT_CREATED message.
506 void
_MakeCreatedNotification(BMessage * msg,endpoint_t * endpoint)507 MidiServerApp::_MakeCreatedNotification(BMessage* msg, endpoint_t* endpoint)
508 {
509 	ASSERT(msg != NULL)
510 	ASSERT(endpoint != NULL)
511 
512 	msg->MakeEmpty();
513 	msg->what = MSG_ENDPOINT_CREATED;
514 	msg->AddInt32("midi:id", endpoint->id);
515 	msg->AddBool("midi:consumer", endpoint->consumer);
516 	msg->AddBool("midi:registered", endpoint->registered);
517 	msg->AddString("midi:name", endpoint->name);
518 	msg->AddMessage("midi:properties", &endpoint->properties);
519 
520 	if (endpoint->consumer) {
521 		msg->AddInt32("midi:port", endpoint->port);
522 		msg->AddInt64("midi:latency", endpoint->latency);
523 	}
524 }
525 
526 
527 //! Fills up a MSG_ENDPOINTS_(DIS)CONNECTED message.
528 void
_MakeConnectedNotification(BMessage * msg,endpoint_t * producer,endpoint_t * consumer,bool mustConnect)529 MidiServerApp::_MakeConnectedNotification(BMessage* msg, endpoint_t* producer,
530 	endpoint_t* consumer, bool mustConnect)
531 {
532 	ASSERT(msg != NULL)
533 	ASSERT(producer != NULL)
534 	ASSERT(consumer != NULL)
535 	ASSERT(!producer->consumer)
536 	ASSERT(consumer->consumer)
537 
538 	msg->MakeEmpty();
539 
540 	if (mustConnect)
541 		msg->what = MSG_ENDPOINTS_CONNECTED;
542 	else
543 		msg->what = MSG_ENDPOINTS_DISCONNECTED;
544 
545 	msg->AddInt32("midi:producer", producer->id);
546 	msg->AddInt32("midi:consumer", consumer->id);
547 }
548 
549 
550 /*!	Figures out which application a message came from.
551 	Returns NULL if the application is not registered.
552 */
553 app_t*
_WhichApp(BMessage * msg)554 MidiServerApp::_WhichApp(BMessage* msg)
555 {
556 	ASSERT(msg != NULL)
557 
558 	BMessenger retadr = msg->ReturnAddress();
559 
560 	for (int32 t = 0; t < _CountApps(); ++t) {
561 		app_t* app = _AppAt(t);
562 		if (app->messenger.Team() == retadr.Team())
563 			return app;
564 	}
565 
566 	TRACE(("Application %" B_PRId32 " is not registered", retadr.Team()))
567 
568 	return NULL;
569 }
570 
571 
572 /*!	Looks at the "midi:id" field from a message, and returns
573 	the endpoint object that corresponds to that ID. It also
574 	checks whether the application specified by "app" really
575 	owns the endpoint. Returns NULL on error.
576 */
577 endpoint_t*
_WhichEndpoint(BMessage * msg,app_t * app)578 MidiServerApp::_WhichEndpoint(BMessage* msg, app_t* app)
579 {
580 	ASSERT(msg != NULL)
581 	ASSERT(app != NULL)
582 
583 	int32 id;
584 	if (msg->FindInt32("midi:id", &id) == B_OK) {
585 		endpoint_t* endpoint = _FindEndpoint(id);
586 		if (endpoint != NULL && endpoint->app == app)
587 			return endpoint;
588 	}
589 
590 	TRACE(("Endpoint not found or wrong app"))
591 	return NULL;
592 }
593 
594 
595 /*!	Returns the endpoint with the specified ID, or
596 	\c NULL if no such endpoint exists on the roster.
597 */
598 endpoint_t*
_FindEndpoint(int32 id)599 MidiServerApp::_FindEndpoint(int32 id)
600 {
601 	if (id > 0) {
602 		for (int32 t = 0; t < _CountEndpoints(); ++t) {
603 			endpoint_t* endpoint = _EndpointAt(t);
604 			if (endpoint->id == id)
605 				return endpoint;
606 		}
607 	}
608 
609 	TRACE(("Endpoint %" B_PRId32 " not found", id))
610 	return NULL;
611 }
612 
613 
614 /*!	Sends notification messages to all registered apps,
615 	except to the application that triggered the event.
616 	The "except" app is allowed to be NULL.
617 */
618 void
_NotifyAll(BMessage * msg,app_t * except)619 MidiServerApp::_NotifyAll(BMessage* msg, app_t* except)
620 {
621 	ASSERT(msg != NULL)
622 
623 	for (int32 t = _CountApps() - 1; t >= 0; --t) {
624 		app_t* app = _AppAt(t);
625 		if (app != except && !_SendNotification(app, msg)) {
626 			delete (app_t*)fApps.RemoveItem(t);
627 #ifdef DEBUG
628 			_DumpApps();
629 #endif
630 		}
631 	}
632 }
633 
634 
635 /*!	Sends a notification message to an application, which is
636 	not necessarily registered yet. Applications never reply
637 	to such notification messages.
638 */
639 bool
_SendNotification(app_t * app,BMessage * msg)640 MidiServerApp::_SendNotification(app_t* app, BMessage* msg)
641 {
642 	ASSERT(app != NULL)
643 	ASSERT(msg != NULL)
644 
645 	status_t status = app->messenger.SendMessage(msg, (BHandler*) NULL,
646 		TIMEOUT);
647 	if (status != B_OK)
648 		_DeliveryError(app);
649 
650 	return status == B_OK;
651 }
652 
653 
654 /*!	Sends a reply to a request made by an application.
655 	If "app" is NULL, the application is not registered
656 	(and the reply should contain an error code).
657 */
658 bool
_SendReply(app_t * app,BMessage * msg,BMessage * reply)659 MidiServerApp::_SendReply(app_t* app, BMessage* msg, BMessage* reply)
660 {
661 	ASSERT(msg != NULL)
662 	ASSERT(reply != NULL)
663 
664 	status_t status = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
665 	if (status != B_OK && app != NULL) {
666 		_DeliveryError(app);
667 		fApps.RemoveItem(app);
668 		delete app;
669 
670 #ifdef DEBUG
671 		_DumpApps();
672 #endif
673 	}
674 
675 	return status == B_OK;
676 }
677 
678 
679 /*!	Removes an app and all of its endpoints from the roster
680 	if a reply or notification message cannot be delivered.
681 	(Waiting for communications to fail is actually our only
682 	way to get rid of stale endpoints.)
683 */
684 void
_DeliveryError(app_t * app)685 MidiServerApp::_DeliveryError(app_t* app)
686 {
687 	ASSERT(app != NULL)
688 
689 	// We cannot communicate with the app, so we assume it's
690 	// dead. We need to remove its endpoints from the roster,
691 	// but we cannot do that right away; removing endpoints
692 	// triggers a bunch of new notifications and we don't want
693 	// those to get in the way of the notifications we are
694 	// currently sending out. Instead, we consider the death
695 	// of an app as a separate event, and pretend that the
696 	// now-dead app sent us delete requests for its endpoints.
697 
698 	TRACE(("Delivery error; unregistering app (%p)", app))
699 
700 	BMessage msg;
701 
702 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
703 		endpoint_t* endpoint = _EndpointAt(t);
704 		if (endpoint->app == app) {
705 			msg.MakeEmpty();
706 			msg.what = MSG_PURGE_ENDPOINT;
707 			msg.AddInt32("midi:id", endpoint->id);
708 
709 			// It is not safe to post a message to your own
710 			// looper's message queue, because you risk a
711 			// deadlock if the queue is full. The chance of
712 			// that happening is fairly small, but just in
713 			// case, we catch it with a timeout. Because this
714 			// situation is so unlikely, I decided to simply
715 			// forget about the whole "purge" message then.
716 
717 			if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
718 					TIMEOUT) != B_OK) {
719 				WARN("Could not deliver purge message")
720 			}
721 		}
722 	}
723 }
724 
725 
726 int32
_CountApps()727 MidiServerApp::_CountApps()
728 {
729 	return fApps.CountItems();
730 }
731 
732 
733 app_t*
_AppAt(int32 index)734 MidiServerApp::_AppAt(int32 index)
735 {
736 	ASSERT(index >= 0 && index < _CountApps())
737 
738 	return (app_t*)fApps.ItemAt(index);
739 }
740 
741 
742 int32
_CountEndpoints()743 MidiServerApp::_CountEndpoints()
744 {
745 	return fEndpoints.CountItems();
746 }
747 
748 
749 endpoint_t*
_EndpointAt(int32 index)750 MidiServerApp::_EndpointAt(int32 index)
751 {
752 	ASSERT(index >= 0 && index < _CountEndpoints())
753 
754 	return (endpoint_t*)fEndpoints.ItemAt(index);
755 }
756 
757 
758 int32
_CountConnections(endpoint_t * producer)759 MidiServerApp::_CountConnections(endpoint_t* producer)
760 {
761 	ASSERT(producer != NULL)
762 	ASSERT(!producer->consumer)
763 
764 	return producer->connections.CountItems();
765 }
766 
767 
768 endpoint_t*
_ConnectionAt(endpoint_t * producer,int32 index)769 MidiServerApp::_ConnectionAt(endpoint_t* producer, int32 index)
770 {
771 	ASSERT(producer != NULL)
772 	ASSERT(!producer->consumer)
773 	ASSERT(index >= 0 && index < _CountConnections(producer))
774 
775 	return (endpoint_t*)producer->connections.ItemAt(index);
776 }
777 
778 
779 #ifdef DEBUG
780 void
_DumpApps()781 MidiServerApp::_DumpApps()
782 {
783 	printf("*** START DumpApps\n");
784 
785 	for (int32 t = 0; t < _CountApps(); ++t) {
786 		app_t* app = _AppAt(t);
787 
788 		printf("\tapp %" B_PRId32 " (%p): team %" B_PRId32 "\n", t, app,
789 			app->messenger.Team());
790 	}
791 
792 	printf("*** END DumpApps\n");
793 }
794 
795 
796 void
_DumpEndpoints()797 MidiServerApp::_DumpEndpoints()
798 {
799 	printf("*** START DumpEndpoints\n");
800 
801 	for (int32 t = 0; t < _CountEndpoints(); ++t) {
802 		endpoint_t* endpoint = _EndpointAt(t);
803 
804 		printf("\tendpoint %" B_PRId32 " (%p):\n", t, endpoint);
805 		printf("\t\tid %" B_PRId32 ", name '%s', %s, %s, app %p\n",
806 			endpoint->id, endpoint->name.String(),
807 			endpoint->consumer ? "consumer" : "producer",
808 			endpoint->registered ? "registered" : "unregistered",
809 			endpoint->app);
810 		printf("\t\tproperties: "); endpoint->properties.PrintToStream();
811 
812 		if (endpoint->consumer)
813 			printf("\t\tport %" B_PRId32 ", latency %" B_PRIdBIGTIME "\n",
814 				endpoint->port, endpoint->latency);
815 		else {
816 			printf("\t\tconnections:\n");
817 			for (int32 k = 0; k < _CountConnections(endpoint); ++k) {
818 				endpoint_t* consumer = _ConnectionAt(endpoint, k);
819 				printf("\t\t\tid %" B_PRId32 " (%p)\n", consumer->id, consumer);
820 			}
821 		}
822 	}
823 
824 	printf("*** END DumpEndpoints\n");
825 }
826 #endif	// DEBUG
827 
828 
829 //	#pragma mark -
830 
831 
832 int
main()833 main()
834 {
835 	status_t status;
836 	MidiServerApp app(status);
837 
838 	if (status == B_OK)
839 		app.Run();
840 
841 	return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
842 }
843