1/*
2 * Copyright (c) 2002-2004 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 "debug.h"
24#include "MidiServerApp.h"
25#include "PortDrivers.h"
26#include "ServerDefs.h"
27#include "protocol.h"
28
29#include <Alert.h>
30
31#include <new>
32
33using std::nothrow;
34
35MidiServerApp::MidiServerApp()
36	: BApplication(MIDI_SERVER_SIGNATURE)
37{
38	TRACE(("Running Haiku MIDI server"))
39
40	nextId = 1;
41	fDeviceWatcher = new(std::nothrow) DeviceWatcher();
42	if (fDeviceWatcher != NULL)
43		fDeviceWatcher->Run();
44}
45
46
47MidiServerApp::~MidiServerApp()
48{
49	if (fDeviceWatcher && fDeviceWatcher->Lock())
50		fDeviceWatcher->Quit();
51
52	for (int32 t = 0; t < CountApps(); ++t) {
53		delete AppAt(t);
54	}
55
56	for (int32 t = 0; t < CountEndpoints(); ++t) {
57		delete EndpointAt(t);
58	}
59}
60
61
62void
63MidiServerApp::AboutRequested()
64{
65	BAlert* alert = new BAlert(0,
66		"Haiku midi_server 1.0.0 alpha\n\n"
67		"notes disguised as bytes\n"
68		"propagating to endpoints,\n"
69		"an aural delight",
70		"OK", 0, 0, B_WIDTH_AS_USUAL,
71		B_INFO_ALERT);
72	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
73	alert->Go();
74}
75
76
77void
78MidiServerApp::MessageReceived(BMessage* msg)
79{
80	#ifdef DEBUG
81	printf("IN "); msg->PrintToStream();
82	#endif
83
84	switch (msg->what) {
85		case MSG_REGISTER_APP:         OnRegisterApp(msg);       break;
86		case MSG_CREATE_ENDPOINT:      OnCreateEndpoint(msg);    break;
87		case MSG_DELETE_ENDPOINT:      OnDeleteEndpoint(msg);    break;
88		case MSG_PURGE_ENDPOINT:       OnPurgeEndpoint(msg);     break;
89		case MSG_CHANGE_ENDPOINT:      OnChangeEndpoint(msg);    break;
90		case MSG_CONNECT_ENDPOINTS:    OnConnectDisconnect(msg); break;
91		case MSG_DISCONNECT_ENDPOINTS: OnConnectDisconnect(msg); break;
92
93		default: super::MessageReceived(msg); break;
94	}
95}
96
97
98void
99MidiServerApp::OnRegisterApp(BMessage* msg)
100{
101	TRACE(("MidiServerApp::OnRegisterApp"))
102
103	// We only send the "app registered" message upon success,
104	// so if anything goes wrong here, we do not let the app
105	// know about it, and we consider it unregistered. (Most
106	// likely, the app is dead. If not, it freezes forever
107	// in anticipation of a message that will never arrive.)
108
109	app_t* app = new app_t;
110
111	if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK) {
112		if (SendAllEndpoints(app)) {
113			if (SendAllConnections(app)) {
114				BMessage reply;
115				reply.what = MSG_APP_REGISTERED;
116
117				if (SendNotification(app, &reply)) {
118					apps.AddItem(app);
119
120					#ifdef DEBUG
121					DumpApps();
122					#endif
123
124					return;
125				}
126			}
127		}
128	}
129
130	delete app;
131}
132
133
134void
135MidiServerApp::OnCreateEndpoint(BMessage* msg)
136{
137	TRACE(("MidiServerApp::OnCreateEndpoint"))
138
139	status_t err;
140	endpoint_t* endp = new endpoint_t;
141
142	endp->app = WhichApp(msg);
143	if (endp->app == NULL) {
144		err = B_ERROR;
145	} else {
146		err = B_BAD_VALUE;
147
148		if (msg->FindBool("midi:consumer", &endp->consumer) == B_OK
149			&& msg->FindBool("midi:registered", &endp->registered) == B_OK
150			&& msg->FindString("midi:name", &endp->name) == B_OK
151			&& msg->FindMessage("midi:properties", &endp->properties) == B_OK) {
152			if (endp->consumer) {
153				if (msg->FindInt32("midi:port", &endp->port) == B_OK
154					&& msg->FindInt64("midi:latency", &endp->latency) == B_OK)
155					err = B_OK;
156			} else
157				err = B_OK;
158		}
159	}
160
161	BMessage reply;
162
163	if (err == B_OK) {
164		endp->id = nextId++;
165		reply.AddInt32("midi:id", endp->id);
166	}
167
168	reply.AddInt32("midi:result", err);
169
170	if (SendReply(endp->app, msg, &reply) && err == B_OK)
171		AddEndpoint(msg, endp);
172	else
173		delete endp;
174}
175
176
177void
178MidiServerApp::OnDeleteEndpoint(BMessage* msg)
179{
180	TRACE(("MidiServerApp::OnDeleteEndpoint"))
181
182	// Clients send the "delete endpoint" message from
183	// the BMidiEndpoint destructor, so there is no point
184	// sending a reply, because the endpoint object will
185	// be destroyed no matter what.
186
187	app_t* app = WhichApp(msg);
188	if (app != NULL) {
189		endpoint_t* endp = WhichEndpoint(msg, app);
190		if (endp != NULL)
191			RemoveEndpoint(app, endp);
192	}
193}
194
195
196void
197MidiServerApp::OnPurgeEndpoint(BMessage* msg)
198{
199	TRACE(("MidiServerApp::OnPurgeEndpoint"))
200
201	// This performs the same task as OnDeleteEndpoint(),
202	// except that this message was send by the midi_server
203	// itself, so we don't check that the app that made the
204	// request really is the owner of the endpoint. (But we
205	// _do_ check that the message came from the server.)
206
207	if (!msg->IsSourceRemote()) {
208		int32 id;
209		if (msg->FindInt32("midi:id", &id) == B_OK) {
210			endpoint_t* endp = FindEndpoint(id);
211			if (endp != NULL)
212				RemoveEndpoint(NULL, endp);
213		}
214	}
215}
216
217
218void
219MidiServerApp::OnChangeEndpoint(BMessage* msg)
220{
221	TRACE(("MidiServerApp::OnChangeEndpoint"))
222
223	endpoint_t* endp = NULL;
224	status_t err;
225
226	app_t* app = WhichApp(msg);
227	if (app == NULL)
228		err = B_ERROR;
229	else {
230		endp = WhichEndpoint(msg, app);
231		if (endp == NULL)
232			err = B_BAD_VALUE;
233		else
234			err = B_OK;
235	}
236
237	BMessage reply;
238	reply.AddInt32("midi:result", err);
239
240	if (SendReply(app, msg, &reply) && err == B_OK) {
241		TRACE(("Endpoint %ld (%p) changed", endp->id, endp))
242
243		BMessage notify;
244		notify.what = MSG_ENDPOINT_CHANGED;
245		notify.AddInt32("midi:id", endp->id);
246
247		bool registered;
248		if (msg->FindBool("midi:registered", &registered) == B_OK) {
249			notify.AddBool("midi:registered", registered);
250			endp->registered = registered;
251		}
252
253		BString name;
254		if (msg->FindString("midi:name", &name) == B_OK) {
255			notify.AddString("midi:name", name);
256			endp->name = name;
257		}
258
259		BMessage properties;
260		if (msg->FindMessage("midi:properties", &properties) == B_OK) {
261			notify.AddMessage("midi:properties", &properties);
262			endp->properties = properties;
263		}
264
265		bigtime_t latency;
266		if (msg->FindInt64("midi:latency", &latency) == B_OK) {
267			notify.AddInt64("midi:latency", latency);
268			endp->latency = latency;
269		}
270
271		NotifyAll(&notify, app);
272
273		#ifdef DEBUG
274		DumpEndpoints();
275		#endif
276	}
277}
278
279
280void
281MidiServerApp::OnConnectDisconnect(BMessage* msg)
282{
283	TRACE(("MidiServerApp::OnConnectDisconnect"))
284
285	bool mustConnect = (msg->what == MSG_CONNECT_ENDPOINTS);
286
287	status_t err;
288	endpoint_t* prod = NULL;
289	endpoint_t* cons = NULL;
290
291	app_t* app = WhichApp(msg);
292	if (app == NULL)
293		err = B_ERROR;
294	else {
295		err = B_BAD_VALUE;
296
297		int32 prodId, consId;
298		if (msg->FindInt32("midi:producer", &prodId) == B_OK
299			&& msg->FindInt32("midi:consumer", &consId) == B_OK) {
300			prod = FindEndpoint(prodId);
301			cons = FindEndpoint(consId);
302
303			if (prod != NULL && !prod->consumer) {
304				if (cons != NULL && cons->consumer) {
305					// It is an error to connect two endpoints that
306					// are already connected, or to disconnect two
307					// endpoints that are not connected at all.
308
309					if (mustConnect == prod->connections.HasItem(cons))
310						err = B_ERROR;
311					else
312						err = B_OK;
313				}
314			}
315		}
316	}
317
318	BMessage reply;
319	reply.AddInt32("midi:result", err);
320
321	if (SendReply(app, msg, &reply) && err == B_OK) {
322		if (mustConnect) {
323			TRACE(("Connection made: %ld ---> %ld", prod->id, cons->id))
324
325			prod->connections.AddItem(cons);
326		} else {
327			TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id))
328
329			prod->connections.RemoveItem(cons);
330		}
331
332		BMessage notify;
333		MakeConnectedNotification(&notify, prod, cons, mustConnect);
334		NotifyAll(&notify, app);
335
336		#ifdef DEBUG
337		DumpEndpoints();
338		#endif
339	}
340}
341
342
343bool
344MidiServerApp::SendAllEndpoints(app_t* app)
345{
346	ASSERT(app != NULL)
347
348	BMessage notify;
349
350	for (int32 t = 0; t < CountEndpoints(); ++t) {
351		endpoint_t* endp = EndpointAt(t);
352
353		MakeCreatedNotification(&notify, endp);
354
355		if (!SendNotification(app, &notify))
356			return false;
357	}
358
359	return true;
360}
361
362
363bool
364MidiServerApp::SendAllConnections(app_t* app)
365{
366	ASSERT(app != NULL)
367
368	BMessage notify;
369
370	for (int32 t = 0; t < CountEndpoints(); ++t) {
371		endpoint_t* prod = EndpointAt(t);
372		if (!prod->consumer) {
373			for (int32 k = 0; k < CountConnections(prod); ++k) {
374				endpoint_t* cons = ConnectionAt(prod, k);
375
376				MakeConnectedNotification(&notify, prod, cons, true);
377
378				if (!SendNotification(app, &notify))
379					return false;
380			}
381		}
382	}
383
384	return true;
385}
386
387
388void
389MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp)
390{
391	ASSERT(msg != NULL)
392	ASSERT(endp != NULL)
393	ASSERT(!endpoints.HasItem(endp))
394
395	TRACE(("Endpoint %ld (%p) added", endp->id, endp))
396
397	endpoints.AddItem(endp);
398
399	BMessage notify;
400	MakeCreatedNotification(&notify, endp);
401	NotifyAll(&notify, endp->app);
402
403	#ifdef DEBUG
404	DumpEndpoints();
405	#endif
406}
407
408
409void
410MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp)
411{
412	ASSERT(endp != NULL)
413	ASSERT(endpoints.HasItem(endp))
414
415	TRACE(("Endpoint %ld (%p) removed", endp->id, endp))
416
417	endpoints.RemoveItem(endp);
418
419	if (endp->consumer)
420		DisconnectDeadConsumer(endp);
421
422	BMessage notify;
423	notify.what = MSG_ENDPOINT_DELETED;
424	notify.AddInt32("midi:id", endp->id);
425	NotifyAll(&notify, app);
426
427	delete endp;
428
429	#ifdef DEBUG
430	DumpEndpoints();
431	#endif
432}
433
434
435void
436MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons)
437{
438	ASSERT(cons != NULL)
439	ASSERT(cons->consumer)
440
441	for (int32 t = 0; t < CountEndpoints(); ++t) {
442		endpoint_t* prod = EndpointAt(t);
443		if (!prod->consumer)
444			prod->connections.RemoveItem(cons);
445	}
446}
447
448
449void
450MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp)
451{
452	ASSERT(msg != NULL)
453	ASSERT(endp != NULL)
454
455	msg->MakeEmpty();
456	msg->what = MSG_ENDPOINT_CREATED;
457	msg->AddInt32("midi:id", endp->id);
458	msg->AddBool("midi:consumer", endp->consumer);
459	msg->AddBool("midi:registered", endp->registered);
460	msg->AddString("midi:name", endp->name);
461	msg->AddMessage("midi:properties", &endp->properties);
462
463	if (endp->consumer) {
464		msg->AddInt32("midi:port", endp->port);
465		msg->AddInt64("midi:latency", endp->latency);
466	}
467}
468
469
470void
471MidiServerApp::MakeConnectedNotification(BMessage* msg, endpoint_t* prod,
472	endpoint_t* cons, bool mustConnect)
473{
474	ASSERT(msg != NULL)
475	ASSERT(prod != NULL)
476	ASSERT(cons != NULL)
477	ASSERT(!prod->consumer)
478	ASSERT(cons->consumer)
479
480	msg->MakeEmpty();
481
482	if (mustConnect)
483		msg->what = MSG_ENDPOINTS_CONNECTED;
484	else
485		msg->what = MSG_ENDPOINTS_DISCONNECTED;
486
487	msg->AddInt32("midi:producer", prod->id);
488	msg->AddInt32("midi:consumer", cons->id);
489}
490
491
492app_t*
493MidiServerApp::WhichApp(BMessage* msg)
494{
495	ASSERT(msg != NULL)
496
497	BMessenger retadr = msg->ReturnAddress();
498
499	for (int32 t = 0; t < CountApps(); ++t) {
500		app_t* app = AppAt(t);
501		if (app->messenger.Team() == retadr.Team())
502			return app;
503	}
504
505	TRACE(("Application %ld is not registered", retadr.Team()))
506
507	return NULL;
508}
509
510
511endpoint_t*
512MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app)
513{
514	ASSERT(msg != NULL)
515	ASSERT(app != NULL)
516
517	int32 id;
518	if (msg->FindInt32("midi:id", &id) == B_OK) {
519		endpoint_t* endp = FindEndpoint(id);
520		if (endp != NULL && endp->app == app)
521			return endp;
522	}
523
524	TRACE(("Endpoint not found or wrong app"))
525	return NULL;
526}
527
528
529endpoint_t*
530MidiServerApp::FindEndpoint(int32 id)
531{
532	if (id > 0) {
533		for (int32 t = 0; t < CountEndpoints(); ++t) {
534			endpoint_t* endp = EndpointAt(t);
535			if (endp->id == id)
536				return endp;
537		}
538	}
539
540	TRACE(("Endpoint %ld not found", id))
541	return NULL;
542}
543
544
545void
546MidiServerApp::NotifyAll(BMessage* msg, app_t* except)
547{
548	ASSERT(msg != NULL)
549
550	for (int32 t = CountApps() - 1; t >= 0; --t) {
551		app_t* app = AppAt(t);
552		if (app != except) {
553			if (!SendNotification(app, msg)) {
554				delete (app_t*)apps.RemoveItem(t);
555
556				#ifdef DEBUG
557				DumpApps();
558				#endif
559			}
560		}
561	}
562}
563
564
565bool
566MidiServerApp::SendNotification(app_t* app, BMessage* msg)
567{
568	ASSERT(app != NULL)
569	ASSERT(msg != NULL)
570
571	status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT);
572
573	if (err != B_OK)
574		DeliveryError(app);
575
576	return err == B_OK;
577}
578
579
580bool
581MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply)
582{
583	ASSERT(msg != NULL)
584	ASSERT(reply != NULL)
585
586	status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
587
588	if (err != B_OK && app != NULL) {
589		DeliveryError(app);
590		apps.RemoveItem(app);
591		delete app;
592
593		#ifdef DEBUG
594		DumpApps();
595		#endif
596	}
597
598	return err == B_OK;
599}
600
601
602void
603MidiServerApp::DeliveryError(app_t* app)
604{
605	ASSERT(app != NULL)
606
607	// We cannot communicate with the app, so we assume it's
608	// dead. We need to remove its endpoints from the roster,
609	// but we cannot do that right away; removing endpoints
610	// triggers a bunch of new notifications and we don't want
611	// those to get in the way of the notifications we are
612	// currently sending out. Instead, we consider the death
613	// of an app as a separate event, and pretend that the
614	// now-dead app sent us delete requests for its endpoints.
615
616	TRACE(("Delivery error; unregistering app (%p)", app))
617
618	BMessage msg;
619
620	for (int32 t = 0; t < CountEndpoints(); ++t) {
621		endpoint_t* endp = EndpointAt(t);
622		if (endp->app == app) {
623			msg.MakeEmpty();
624			msg.what = MSG_PURGE_ENDPOINT;
625			msg.AddInt32("midi:id", endp->id);
626
627			// It is not safe to post a message to your own
628			// looper's message queue, because you risk a
629			// deadlock if the queue is full. The chance of
630			// that happening is fairly small, but just in
631			// case, we catch it with a timeout. Because this
632			// situation is so unlikely, I decided to simply
633			// forget about the whole "purge" message then.
634
635			if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
636					TIMEOUT) != B_OK) {
637				WARN("Could not deliver purge message")
638			}
639		}
640	}
641}
642
643
644int32
645MidiServerApp::CountApps()
646{
647	return apps.CountItems();
648}
649
650
651app_t*
652MidiServerApp::AppAt(int32 index)
653{
654	ASSERT(index >= 0 && index < CountApps())
655
656	return (app_t*)apps.ItemAt(index);
657}
658
659
660int32
661MidiServerApp::CountEndpoints()
662{
663	return endpoints.CountItems();
664}
665
666
667endpoint_t*
668MidiServerApp::EndpointAt(int32 index)
669{
670	ASSERT(index >= 0 && index < CountEndpoints())
671
672	return (endpoint_t*)endpoints.ItemAt(index);
673}
674
675
676int32
677MidiServerApp::CountConnections(endpoint_t* prod)
678{
679	ASSERT(prod != NULL)
680	ASSERT(!prod->consumer)
681
682	return prod->connections.CountItems();
683}
684
685
686endpoint_t*
687MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index)
688{
689	ASSERT(prod != NULL)
690	ASSERT(!prod->consumer)
691	ASSERT(index >= 0 && index < CountConnections(prod))
692
693	return (endpoint_t*)prod->connections.ItemAt(index);
694}
695
696
697#ifdef DEBUG
698void
699MidiServerApp::DumpApps()
700{
701	printf("*** START DumpApps\n");
702
703	for (int32 t = 0; t < CountApps(); ++t) {
704		app_t* app = AppAt(t);
705
706		printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team());
707	}
708
709	printf("*** END DumpApps\n");
710}
711#endif
712
713
714#ifdef DEBUG
715void
716MidiServerApp::DumpEndpoints()
717{
718	printf("*** START DumpEndpoints\n");
719
720	for (int32 t = 0; t < CountEndpoints(); ++t) {
721		endpoint_t* endp = EndpointAt(t);
722
723		printf("\tendpoint %ld (%p):\n", t, endp);
724		printf("\t\tid %ld, name '%s', %s, %s, app %p\n",
725			endp->id, endp->name.String(),
726			endp->consumer ? "consumer" : "producer",
727			endp->registered ? "registered" : "unregistered",
728			endp->app);
729		printf("\t\tproperties: "); endp->properties.PrintToStream();
730
731		if (endp->consumer)
732			printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency);
733		else {
734			printf("\t\tconnections:\n");
735			for (int32 k = 0; k < CountConnections(endp); ++k) {
736				endpoint_t* cons = ConnectionAt(endp, k);
737				printf("\t\t\tid %ld (%p)\n", cons->id, cons);
738			}
739		}
740	}
741
742	printf("*** END DumpEndpoints\n");
743}
744#endif
745
746
747//	#pragma mark -
748
749
750int
751main()
752{
753	MidiServerApp app;
754	app.Run();
755	return 0;
756}
757
758