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