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