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