1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// RouteAppNodeManager.cpp
33
34#include "RouteAppNodeManager.h"
35
36#include "MediaIcon.h"
37#include "NodeGroup.h"
38#include "NodeRef.h"
39#include "Connection.h"
40
41#include "route_app_io.h"
42#include "ConnectionIO.h"
43#include "DormantNodeIO.h"
44#include "LiveNodeIO.h"
45#include "MediaFormatIO.h"
46#include "MessageIO.h"
47#include "NodeSetIOContext.h"
48#include "StringContent.h"
49#include "MediaString.h"
50
51#include <Autolock.h>
52#include <Debug.h>
53#include <Entry.h>
54#include <Path.h>
55
56#include <TimeSource.h>
57
58#include <cstring>
59#include <cstdlib>
60#include <cstdio>
61#include <typeinfo>
62
63#include "set_tools.h"
64
65using namespace std;
66
67__USE_CORTEX_NAMESPACE
68
69#define D_METHOD(x) //PRINT (x)
70#define D_HOOK(x) //PRINT (x)
71#define D_SETTINGS(x) //PRINT (x)
72
73// -------------------------------------------------------- //
74// *** ctor/dtor
75// -------------------------------------------------------- //
76
77RouteAppNodeManager::~RouteAppNodeManager() {
78
79	_freeIcons();
80}
81
82RouteAppNodeManager::RouteAppNodeManager(
83	bool													useAddOnHost) :
84	NodeManager(useAddOnHost),
85	m_nextGroupNumber(1) {
86
87	// pre-cache icons? +++++
88
89}
90
91// -------------------------------------------------------- //
92// *** group management
93// -------------------------------------------------------- //
94
95// -------------------------------------------------------- //
96// *** icon management
97// -------------------------------------------------------- //
98
99// fetch cached icon for the given live node; the MediaIcon
100// instance is guaranteed to last as long as this object.
101// Returns 0 if the node doesn't exist.
102
103const MediaIcon* RouteAppNodeManager::mediaIconFor(
104	media_node_id									nodeID,
105	icon_size											iconSize) {
106
107	BAutolock _l(this);
108
109	uint64 key = _makeIconKey(nodeID, iconSize);
110
111	icon_map::const_iterator it = m_iconMap.find(key);
112	if(it != m_iconMap.end()) {
113		// already cached
114		return (*it).second;
115	}
116
117	// look up live_node_info
118	NodeRef* ref;
119	status_t err = getNodeRef(nodeID, &ref);
120	if(err < B_OK)
121		return 0;
122
123	return mediaIconFor(ref->nodeInfo(), iconSize);
124}
125
126const MediaIcon* RouteAppNodeManager::mediaIconFor(
127	live_node_info								nodeInfo,
128	icon_size											iconSize) {
129
130	uint64 key = _makeIconKey(nodeInfo.node.node, iconSize);
131
132	icon_map::const_iterator it = m_iconMap.find(key);
133	if(it != m_iconMap.end()) {
134		// already cached
135		return (*it).second;
136	}
137
138	// create & cache icon
139	MediaIcon* icon = new MediaIcon(
140		nodeInfo, iconSize);
141
142	m_iconMap.insert(
143		icon_map::value_type(key, icon));
144
145	return icon;
146}
147
148// -------------------------------------------------------- //
149// *** error handling
150// -------------------------------------------------------- //
151
152status_t RouteAppNodeManager::setLogTarget(
153	const BMessenger&							target) {
154
155	BAutolock _l(this);
156
157	if(!target.IsValid())
158		return B_BAD_VALUE;
159
160	m_logTarget = target;
161	return B_OK;
162}
163
164// -------------------------------------------------------- //
165// NodeManager hook implementations
166// -------------------------------------------------------- //
167
168void RouteAppNodeManager::nodeCreated(
169	NodeRef*											ref) {
170
171	// prepare the log message
172	BMessage logMsg(M_LOG);
173	BString title = "Node '";
174	title << ref->name() << "' created";
175	logMsg.AddString("title", title);
176
177	// create a default group for the node
178	// [em 8feb00]
179	NodeGroup* g = createGroup(ref->name());
180
181	if(ref->kind() & B_TIME_SOURCE) {
182		// notify observers
183		BMessage m(M_TIME_SOURCE_CREATED);
184		m.AddInt32("nodeID", ref->id());
185		notify(&m);
186	}
187
188	// adopt node's time source if it's not the system clock (the default)
189	// [em 20mar00]
190	media_node systemClock;
191	status_t err = roster->GetSystemTimeSource(&systemClock);
192	if(err == B_OK)
193	{
194		BTimeSource* ts = roster->MakeTimeSourceFor(ref->node());
195		if (ts == NULL)
196			return;
197		if(ts->Node() != systemClock)
198		{
199			g->setTimeSource(ts->Node());
200			logMsg.AddString("line", "Synced to system clock");
201		}
202		ts->Release();
203	}
204
205	g->addNode(ref);
206
207	m_logTarget.SendMessage(&logMsg);
208}
209
210void RouteAppNodeManager::nodeDeleted(
211	const NodeRef*								ref) {
212
213	// prepare the log message
214	BMessage logMsg(M_LOG);
215	BString title = "Node '";
216	title << ref->name() << "' released";
217	logMsg.AddString("title", title);
218
219	if(ref->kind() & B_TIME_SOURCE) {
220		// notify observers
221		BMessage m(M_TIME_SOURCE_DELETED);
222		m.AddInt32("nodeID", ref->id());
223		notify(&m);
224	}
225
226	m_logTarget.SendMessage(&logMsg);
227}
228
229void RouteAppNodeManager::connectionMade(
230	Connection*										connection) {
231
232	D_HOOK((
233		"@ RouteAppNodeManager::connectionMade()\n"));
234
235	status_t err;
236
237	// prepare the log message
238	BMessage logMsg(M_LOG);
239	BString title = "Connection ";
240	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
241		title << "'" << connection->outputName() << "' ";
242	}
243	title << "made";
244	logMsg.AddString("title", title);
245
246	if(!(connection->flags() & Connection::INTERNAL))
247		// don't react to connection Cortex didn't make
248		return;
249
250	// create or merge groups
251	NodeRef *producer, *consumer;
252	err = getNodeRef(connection->sourceNode(), &producer);
253	if(err < B_OK) {
254		D_HOOK((
255			"!!! RouteAppNodeManager::connectionMade():\n"
256			"    sourceNode (%ld) not found\n",
257			connection->sourceNode()));
258		return;
259	}
260	err = getNodeRef(connection->destinationNode(), &consumer);
261	if(err < B_OK) {
262		D_HOOK((
263			"!!! RouteAppNodeManager::connectionMade():\n"
264			"    destinationNode (%ld) not found\n",
265			connection->destinationNode()));
266		return;
267	}
268
269	// add node names to log messages
270	BString line = "Between:";
271	logMsg.AddString("line", line);
272	line = "    ";
273	line << producer->name() << " and ";
274	line << consumer->name();
275	logMsg.AddString("line", line);
276
277	// add format to log message
278	line = "Negotiated format:";
279	logMsg.AddString("line", line);
280	line = "    ";
281	line << MediaString::getStringFor(connection->format(), false);
282	logMsg.AddString("line", line);
283
284	NodeGroup *group = 0;
285	BString groupName = "Untitled group ";
286	if(_canGroup(producer) && _canGroup(consumer))
287	{
288		if (producer->group() && consumer->group() &&
289			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED) &&
290			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
291		{
292			// merge into consumers group
293			group = consumer->group();
294			mergeGroups(producer->group(), group);
295		}
296		else if (producer->group() &&
297			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
298		{ // add consumer to producers group
299			group = producer->group();
300			group->addNode(consumer);
301		}
302		else if (consumer->group() &&
303			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
304		{ // add producer to consumers group
305			group = consumer->group();
306			group->addNode(producer);
307		}
308		else
309		{ // make new group for both
310			groupName << m_nextGroupNumber++;
311			group = createGroup(groupName.String());
312			group->addNode(producer);
313			group->addNode(consumer);
314		}
315	}
316	else if(_canGroup(producer) && !producer->group())
317	{ // make new group for producer
318		groupName << m_nextGroupNumber++;
319		group = createGroup(groupName.String());
320		group->addNode(producer);
321	}
322	else if(_canGroup(consumer) && !consumer->group())
323	{ // make new group for consumer
324		groupName << m_nextGroupNumber++;
325		group = createGroup(groupName.String());
326		group->addNode(consumer);
327	}
328
329	m_logTarget.SendMessage(&logMsg);
330}
331
332void RouteAppNodeManager::connectionBroken(
333	const Connection*									connection) {
334
335	D_HOOK((
336		"@ RouteAppNodeManager::connectionBroken()\n"));
337
338	// prepare the log message
339	BMessage logMsg(M_LOG);
340	BString title = "Connection ";
341	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
342		title << "'" << connection->outputName() << "' ";
343	}
344	title << "broken";
345	logMsg.AddString("title", title);
346
347	if(!(connection->flags() & Connection::INTERNAL))
348		// don't react to connection Cortex didn't make
349		return;
350
351	status_t err;
352
353	// if the source and destination nodes belong to the same group,
354	// and if no direct or indirect connection remains between the
355	// source and destination nodes, split groups
356
357	NodeRef *producer, *consumer;
358	err = getNodeRef(connection->sourceNode(), &producer);
359	if(err < B_OK) {
360		D_HOOK((
361			"!!! RouteAppNodeManager::connectionMade():\n"
362			"    sourceNode (%ld) not found\n",
363			connection->sourceNode()));
364		return;
365	}
366	err = getNodeRef(connection->destinationNode(), &consumer);
367	if(err < B_OK) {
368		D_HOOK((
369			"!!! RouteAppNodeManager::connectionMade():\n"
370			"    destinationNode (%ld) not found\n",
371			connection->destinationNode()));
372		return;
373	}
374
375	// add node names to log messages
376	BString line = "Between:";
377	logMsg.AddString("line", line);
378	line = "    ";
379	line << producer->name() << " and ";
380	line << consumer->name();
381	logMsg.AddString("line", line);
382
383	if(
384		producer->group() &&
385		producer->group() == consumer->group() &&
386		!findRoute(producer->id(), consumer->id())) {
387
388		NodeGroup *newGroup;
389		splitGroup(producer, consumer, &newGroup);
390	}
391
392	m_logTarget.SendMessage(&logMsg);
393}
394
395void RouteAppNodeManager::connectionFailed(
396	const media_output &							output,
397	const media_input &								input,
398	const media_format &							format,
399	status_t										error) {
400	D_HOOK((
401		"@ RouteAppNodeManager::connectionFailed()\n"));
402
403	status_t err;
404
405	// prepare the log message
406	BMessage logMsg(M_LOG);
407	BString title = "Connection failed";
408	logMsg.AddString("title", title);
409	logMsg.AddInt32("error", error);
410
411	NodeRef *producer, *consumer;
412	err = getNodeRef(output.node.node, &producer);
413	if(err < B_OK) {
414		return;
415	}
416	err = getNodeRef(input.node.node, &consumer);
417	if(err < B_OK) {
418		return;
419	}
420
421	// add node names to log messages
422	BString line = "Between:";
423	logMsg.AddString("line", line);
424	line = "    ";
425	line << producer->name() << " and " << consumer->name();
426	logMsg.AddString("line", line);
427
428	// add format to log message
429	line = "Tried format:";
430	logMsg.AddString("line", line);
431	line = "    ";
432	line << MediaString::getStringFor(format, true);
433	logMsg.AddString("line", line);
434
435	// and send it
436	m_logTarget.SendMessage(&logMsg);
437}
438
439// -------------------------------------------------------- //
440// *** IPersistent
441// -------------------------------------------------------- //
442
443void RouteAppNodeManager::xmlExportBegin(
444	ExportContext&								context) const {
445
446	status_t err;
447
448	try {
449		NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
450		context.beginElement(_NODE_SET_ELEMENT);
451
452		// validate the node set
453		for(int n = set.countNodes()-1; n >= 0; --n) {
454			media_node_id id = set.nodeAt(n);
455			ASSERT(id != media_node::null.node);
456
457			// fetch node
458			NodeRef* ref;
459			err = getNodeRef(id, &ref);
460			if(err < B_OK) {
461				D_SETTINGS((
462					"! RVNM::xmlExportBegin(): node %ld doesn't exist\n", id));
463
464				set.removeNodeAt(n);
465				continue;
466			}
467			// skip unless internal
468			if(!ref->isInternal()) {
469				D_SETTINGS((
470					"! RVNM::xmlExportBegin(): node %ld not internal; skipping.\n", id));
471
472				set.removeNodeAt(n);
473				continue;
474			}
475		}
476	}
477	catch(bad_cast& e) {
478		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
479	}
480}
481
482void RouteAppNodeManager::xmlExportAttributes(
483	ExportContext&								context) const {}
484
485void RouteAppNodeManager::xmlExportContent(
486	ExportContext&								context) const {
487
488	status_t err;
489
490	try {
491		NodeSetIOContext& nodeSet = dynamic_cast<NodeSetIOContext&>(context);
492		context.beginContent();
493
494		// write nodes; enumerate connections
495		typedef map<uint32,Connection> connection_map;
496		connection_map connections;
497		int count = nodeSet.countNodes();
498		for(int n = 0; n < count; ++n) {
499			media_node_id id = nodeSet.nodeAt(n);
500			ASSERT(id != media_node::null.node);
501
502			// fetch node
503			NodeRef* ref;
504			err = getNodeRef(id, &ref);
505			if(err < B_OK) {
506				D_SETTINGS((
507					"! RouteAppNodeManager::xmlExportContent():\n"
508					"  getNodeRef(%ld) failed: '%s'\n",
509					id, strerror(err)));
510				continue;
511			}
512
513			// fetch connections
514			vector<Connection> conSet;
515			ref->getInputConnections(conSet);
516			ref->getOutputConnections(conSet);
517			for(uint32 c = 0; c < conSet.size(); ++c)
518				// non-unique connections will be rejected:
519				connections.insert(
520					connection_map::value_type(conSet[c].id(), conSet[c]));
521
522			// create an IO object for the node & write it
523			DormantNodeIO io(ref, nodeSet.keyAt(n));
524			if(context.writeObject(&io) < B_OK)
525				// abort
526				return;
527		}
528
529		// write connections
530		for(connection_map::const_iterator it = connections.begin();
531			it != connections.end(); ++it) {
532
533			ConnectionIO io(
534				&(*it).second,
535				this,
536				&nodeSet);
537			if(context.writeObject(&io) < B_OK)
538				// abort
539				return;
540
541		}
542
543		// +++++ write groups
544
545		// write UI state
546		{
547			BMessage m;
548			nodeSet.exportUIState(&m);
549			context.beginElement(_UI_STATE_ELEMENT);
550			context.beginContent();
551			MessageIO io(&m);
552			context.writeObject(&io);
553			context.endElement();
554		}
555	}
556	catch(bad_cast& e) {
557		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
558	}
559}
560
561void RouteAppNodeManager::xmlExportEnd(
562	ExportContext&								context) const {
563
564	context.endElement();
565}
566
567// -------------------------------------------------------- //
568// IMPORT
569// -------------------------------------------------------- //
570
571void RouteAppNodeManager::xmlImportBegin(
572	ImportContext&								context) {
573
574}
575
576void RouteAppNodeManager::xmlImportAttribute(
577	const char*										key,
578	const char*										value,
579	ImportContext&								context) {}
580
581void RouteAppNodeManager::xmlImportContent(
582	const char*										data,
583	uint32												length,
584	ImportContext&								context) {}
585
586void RouteAppNodeManager::xmlImportChild(
587	IPersistent*									child,
588	ImportContext&								context) {
589
590	status_t err;
591
592	if(!strcmp(context.element(), _DORMANT_NODE_ELEMENT)) {
593		DormantNodeIO* io = dynamic_cast<DormantNodeIO*>(child);
594		ASSERT(io);
595
596		NodeRef* newRef;
597		err = io->instantiate(this, &newRef);
598		if(err == B_OK) {
599			// created node; add an entry to the set stored in the
600			// ImportContext for later reference
601			try {
602				NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
603				set.addNode(newRef->id(), io->nodeKey());
604			}
605			catch(bad_cast& e) {
606				context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
607			}
608		}
609		else {
610			D_SETTINGS((
611				"!!! RouteAppNodeManager::xmlImportChild():\n"
612				"    DormantNodeIO::instantiate() failed:\n"
613				"    '%s'\n",
614				strerror(err)));
615		}
616	}
617	else if(!strcmp(context.element(), _CONNECTION_ELEMENT)) {
618		ConnectionIO* io = dynamic_cast<ConnectionIO*>(child);
619		ASSERT(io);
620
621		// instantiate the connection
622		Connection con;
623		err = io->instantiate(
624			this,
625			dynamic_cast<NodeSetIOContext*>(&context),
626			&con);
627		if(err < B_OK) {
628			D_SETTINGS((
629				"!!! ConnectionIO::instantiate() failed:\n"
630				"    '%s'\n", strerror(err)));
631		}
632
633		// +++++ group magic?
634
635	}
636	else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) {
637		// +++++
638	}
639	else if(
640		context.parentElement() &&
641		!strcmp(context.parentElement(), _UI_STATE_ELEMENT)) {
642
643		// expect a nested message
644		MessageIO* io = dynamic_cast<MessageIO*>(child);
645		if(!io) {
646			BString err;
647			err <<
648				"RouteAppNodeManager: unexpected child '" <<
649				context.element() << "'\n";
650			context.reportError(err.String());
651		}
652
653		// hand it off via the extended context object
654		try {
655			NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
656			set.importUIState(io->message());
657		}
658		catch(bad_cast& e) {
659			context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
660		}
661	}
662
663}
664
665void RouteAppNodeManager::xmlImportComplete(
666	ImportContext&								context) {
667
668}
669
670// -------------------------------------------------------- //
671
672/*static*/
673void RouteAppNodeManager::AddTo(
674	XML::DocumentType*				docType) {
675
676	// set up the document type
677
678	MessageIO::AddTo(docType);
679	MediaFormatIO::AddTo(docType);
680	ConnectionIO::AddTo(docType);
681	DormantNodeIO::AddTo(docType);
682	LiveNodeIO::AddTo(docType);
683	_add_string_elements(docType);
684}
685
686// -------------------------------------------------------- //
687// implementation
688// -------------------------------------------------------- //
689
690uint64 RouteAppNodeManager::_makeIconKey(
691	media_node_id nodeID, icon_size iconSize) {
692
693	return ((uint64)nodeID) << 32 | iconSize;
694}
695
696void RouteAppNodeManager::_readIconKey(
697	uint64 key, media_node_id& nodeID, icon_size& iconSize) {
698
699	nodeID = key >> 32;
700	iconSize = icon_size(key & 0xffffffff);
701}
702
703void RouteAppNodeManager::_freeIcons() {
704
705	ptr_map_delete(
706		m_iconMap.begin(),
707		m_iconMap.end());
708}
709
710bool RouteAppNodeManager::_canGroup(NodeRef* ref) const {
711
712	// sanity check & easy cases
713	ASSERT(ref);
714	if(ref->isInternal())
715		return true;
716
717	// bar 'touchy' system nodes
718	if(ref == audioMixerNode() || ref == audioOutputNode())
719		return false;
720
721	return true;
722}
723
724// END -- RouteAppNodeManager.cpp --
725