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->Node() != systemClock)
196		{
197			g->setTimeSource(ts->Node());
198			logMsg.AddString("line", "Synced to system clock");
199		}
200		ts->Release();
201	}
202
203	g->addNode(ref);
204
205	m_logTarget.SendMessage(&logMsg);
206}
207
208void RouteAppNodeManager::nodeDeleted(
209	const NodeRef*								ref) {
210
211	// prepare the log message
212	BMessage logMsg(M_LOG);
213	BString title = "Node '";
214	title << ref->name() << "' released";
215	logMsg.AddString("title", title);
216
217	if(ref->kind() & B_TIME_SOURCE) {
218		// notify observers
219		BMessage m(M_TIME_SOURCE_DELETED);
220		m.AddInt32("nodeID", ref->id());
221		notify(&m);
222	}
223
224	m_logTarget.SendMessage(&logMsg);
225}
226
227void RouteAppNodeManager::connectionMade(
228	Connection*										connection) {
229
230	D_HOOK((
231		"@ RouteAppNodeManager::connectionMade()\n"));
232
233	status_t err;
234
235	// prepare the log message
236	BMessage logMsg(M_LOG);
237	BString title = "Connection ";
238	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
239		title << "'" << connection->outputName() << "' ";
240	}
241	title << "made";
242	logMsg.AddString("title", title);
243
244	if(!(connection->flags() & Connection::INTERNAL))
245		// don't react to connection Cortex didn't make
246		return;
247
248	// create or merge groups
249	NodeRef *producer, *consumer;
250	err = getNodeRef(connection->sourceNode(), &producer);
251	if(err < B_OK) {
252		D_HOOK((
253			"!!! RouteAppNodeManager::connectionMade():\n"
254			"    sourceNode (%ld) not found\n",
255			connection->sourceNode()));
256		return;
257	}
258	err = getNodeRef(connection->destinationNode(), &consumer);
259	if(err < B_OK) {
260		D_HOOK((
261			"!!! RouteAppNodeManager::connectionMade():\n"
262			"    destinationNode (%ld) not found\n",
263			connection->destinationNode()));
264		return;
265	}
266
267	// add node names to log messages
268	BString line = "Between:";
269	logMsg.AddString("line", line);
270	line = "    ";
271	line << producer->name() << " and ";
272	line << consumer->name();
273	logMsg.AddString("line", line);
274
275	// add format to log message
276	line = "Negotiated format:";
277	logMsg.AddString("line", line);
278	line = "    ";
279	line << MediaString::getStringFor(connection->format(), false);
280	logMsg.AddString("line", line);
281
282	NodeGroup *group = 0;
283	BString groupName = "Untitled group ";
284	if(_canGroup(producer) && _canGroup(consumer))
285	{
286		if (producer->group() && consumer->group() &&
287			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED) &&
288			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
289		{
290			// merge into consumers group
291			group = consumer->group();
292			mergeGroups(producer->group(), group);
293		}
294		else if (producer->group() &&
295			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
296		{ // add consumer to producers group
297			group = producer->group();
298			group->addNode(consumer);
299		}
300		else if (consumer->group() &&
301			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
302		{ // add producer to consumers group
303			group = consumer->group();
304			group->addNode(producer);
305		}
306		else
307		{ // make new group for both
308			groupName << m_nextGroupNumber++;
309			group = createGroup(groupName.String());
310			group->addNode(producer);
311			group->addNode(consumer);
312		}
313	}
314	else if(_canGroup(producer) && !producer->group())
315	{ // make new group for producer
316		groupName << m_nextGroupNumber++;
317		group = createGroup(groupName.String());
318		group->addNode(producer);
319	}
320	else if(_canGroup(consumer) && !consumer->group())
321	{ // make new group for consumer
322		groupName << m_nextGroupNumber++;
323		group = createGroup(groupName.String());
324		group->addNode(consumer);
325	}
326
327	m_logTarget.SendMessage(&logMsg);
328}
329
330void RouteAppNodeManager::connectionBroken(
331	const Connection*									connection) {
332
333	D_HOOK((
334		"@ RouteAppNodeManager::connectionBroken()\n"));
335
336	// prepare the log message
337	BMessage logMsg(M_LOG);
338	BString title = "Connection ";
339	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
340		title << "'" << connection->outputName() << "' ";
341	}
342	title << "broken";
343	logMsg.AddString("title", title);
344
345	if(!(connection->flags() & Connection::INTERNAL))
346		// don't react to connection Cortex didn't make
347		return;
348
349	status_t err;
350
351	// if the source and destination nodes belong to the same group,
352	// and if no direct or indirect connection remains between the
353	// source and destination nodes, split groups
354
355	NodeRef *producer, *consumer;
356	err = getNodeRef(connection->sourceNode(), &producer);
357	if(err < B_OK) {
358		D_HOOK((
359			"!!! RouteAppNodeManager::connectionMade():\n"
360			"    sourceNode (%ld) not found\n",
361			connection->sourceNode()));
362		return;
363	}
364	err = getNodeRef(connection->destinationNode(), &consumer);
365	if(err < B_OK) {
366		D_HOOK((
367			"!!! RouteAppNodeManager::connectionMade():\n"
368			"    destinationNode (%ld) not found\n",
369			connection->destinationNode()));
370		return;
371	}
372
373	// add node names to log messages
374	BString line = "Between:";
375	logMsg.AddString("line", line);
376	line = "    ";
377	line << producer->name() << " and ";
378	line << consumer->name();
379	logMsg.AddString("line", line);
380
381	if(
382		producer->group() &&
383		producer->group() == consumer->group() &&
384		!findRoute(producer->id(), consumer->id())) {
385
386		NodeGroup *newGroup;
387		splitGroup(producer, consumer, &newGroup);
388	}
389
390	m_logTarget.SendMessage(&logMsg);
391}
392
393void RouteAppNodeManager::connectionFailed(
394	const media_output &							output,
395	const media_input &								input,
396	const media_format &							format,
397	status_t										error) {
398	D_HOOK((
399		"@ RouteAppNodeManager::connectionFailed()\n"));
400
401	status_t err;
402
403	// prepare the log message
404	BMessage logMsg(M_LOG);
405	BString title = "Connection failed";
406	logMsg.AddString("title", title);
407	logMsg.AddInt32("error", error);
408
409	NodeRef *producer, *consumer;
410	err = getNodeRef(output.node.node, &producer);
411	if(err < B_OK) {
412		return;
413	}
414	err = getNodeRef(input.node.node, &consumer);
415	if(err < B_OK) {
416		return;
417	}
418
419	// add node names to log messages
420	BString line = "Between:";
421	logMsg.AddString("line", line);
422	line = "    ";
423	line << producer->name() << " and " << consumer->name();
424	logMsg.AddString("line", line);
425
426	// add format to log message
427	line = "Tried format:";
428	logMsg.AddString("line", line);
429	line = "    ";
430	line << MediaString::getStringFor(format, true);
431	logMsg.AddString("line", line);
432
433	// and send it
434	m_logTarget.SendMessage(&logMsg);
435}
436
437// -------------------------------------------------------- //
438// *** IPersistent
439// -------------------------------------------------------- //
440
441void RouteAppNodeManager::xmlExportBegin(
442	ExportContext&								context) const {
443
444	status_t err;
445
446	try {
447		NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
448		context.beginElement(_NODE_SET_ELEMENT);
449
450		// validate the node set
451		for(int n = set.countNodes()-1; n >= 0; --n) {
452			media_node_id id = set.nodeAt(n);
453			ASSERT(id != media_node::null.node);
454
455			// fetch node
456			NodeRef* ref;
457			err = getNodeRef(id, &ref);
458			if(err < B_OK) {
459				D_SETTINGS((
460					"! RVNM::xmlExportBegin(): node %ld doesn't exist\n", id));
461
462				set.removeNodeAt(n);
463				continue;
464			}
465			// skip unless internal
466			if(!ref->isInternal()) {
467				D_SETTINGS((
468					"! RVNM::xmlExportBegin(): node %ld not internal; skipping.\n", id));
469
470				set.removeNodeAt(n);
471				continue;
472			}
473		}
474	}
475	catch(bad_cast& e) {
476		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
477	}
478}
479
480void RouteAppNodeManager::xmlExportAttributes(
481	ExportContext&								context) const {}
482
483void RouteAppNodeManager::xmlExportContent(
484	ExportContext&								context) const {
485
486	status_t err;
487
488	try {
489		NodeSetIOContext& nodeSet = dynamic_cast<NodeSetIOContext&>(context);
490		context.beginContent();
491
492		// write nodes; enumerate connections
493		typedef map<uint32,Connection> connection_map;
494		connection_map connections;
495		int count = nodeSet.countNodes();
496		for(int n = 0; n < count; ++n) {
497			media_node_id id = nodeSet.nodeAt(n);
498			ASSERT(id != media_node::null.node);
499
500			// fetch node
501			NodeRef* ref;
502			err = getNodeRef(id, &ref);
503			if(err < B_OK) {
504				D_SETTINGS((
505					"! RouteAppNodeManager::xmlExportContent():\n"
506					"  getNodeRef(%ld) failed: '%s'\n",
507					id, strerror(err)));
508				continue;
509			}
510
511			// fetch connections
512			vector<Connection> conSet;
513			ref->getInputConnections(conSet);
514			ref->getOutputConnections(conSet);
515			for(uint32 c = 0; c < conSet.size(); ++c)
516				// non-unique connections will be rejected:
517				connections.insert(
518					connection_map::value_type(conSet[c].id(), conSet[c]));
519
520			// create an IO object for the node & write it
521			DormantNodeIO io(ref, nodeSet.keyAt(n));
522			if(context.writeObject(&io) < B_OK)
523				// abort
524				return;
525		}
526
527		// write connections
528		for(connection_map::const_iterator it = connections.begin();
529			it != connections.end(); ++it) {
530
531			ConnectionIO io(
532				&(*it).second,
533				this,
534				&nodeSet);
535			if(context.writeObject(&io) < B_OK)
536				// abort
537				return;
538
539		}
540
541		// +++++ write groups
542
543		// write UI state
544		{
545			BMessage m;
546			nodeSet.exportUIState(&m);
547			context.beginElement(_UI_STATE_ELEMENT);
548			context.beginContent();
549			MessageIO io(&m);
550			context.writeObject(&io);
551			context.endElement();
552		}
553	}
554	catch(bad_cast& e) {
555		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
556	}
557}
558
559void RouteAppNodeManager::xmlExportEnd(
560	ExportContext&								context) const {
561
562	context.endElement();
563}
564
565// -------------------------------------------------------- //
566// IMPORT
567// -------------------------------------------------------- //
568
569void RouteAppNodeManager::xmlImportBegin(
570	ImportContext&								context) {
571
572}
573
574void RouteAppNodeManager::xmlImportAttribute(
575	const char*										key,
576	const char*										value,
577	ImportContext&								context) {}
578
579void RouteAppNodeManager::xmlImportContent(
580	const char*										data,
581	uint32												length,
582	ImportContext&								context) {}
583
584void RouteAppNodeManager::xmlImportChild(
585	IPersistent*									child,
586	ImportContext&								context) {
587
588	status_t err;
589
590	if(!strcmp(context.element(), _DORMANT_NODE_ELEMENT)) {
591		DormantNodeIO* io = dynamic_cast<DormantNodeIO*>(child);
592		ASSERT(io);
593
594		NodeRef* newRef;
595		err = io->instantiate(this, &newRef);
596		if(err == B_OK) {
597			// created node; add an entry to the set stored in the
598			// ImportContext for later reference
599			try {
600				NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
601				set.addNode(newRef->id(), io->nodeKey());
602			}
603			catch(bad_cast& e) {
604				context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
605			}
606		}
607		else {
608			D_SETTINGS((
609				"!!! RouteAppNodeManager::xmlImportChild():\n"
610				"    DormantNodeIO::instantiate() failed:\n"
611				"    '%s'\n",
612				strerror(err)));
613		}
614	}
615	else if(!strcmp(context.element(), _CONNECTION_ELEMENT)) {
616		ConnectionIO* io = dynamic_cast<ConnectionIO*>(child);
617		ASSERT(io);
618
619		// instantiate the connection
620		Connection con;
621		err = io->instantiate(
622			this,
623			dynamic_cast<NodeSetIOContext*>(&context),
624			&con);
625		if(err < B_OK) {
626			D_SETTINGS((
627				"!!! ConnectionIO::instantiate() failed:\n"
628				"    '%s'\n", strerror(err)));
629		}
630
631		// +++++ group magic?
632
633	}
634	else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) {
635		// +++++
636	}
637	else if(
638		context.parentElement() &&
639		!strcmp(context.parentElement(), _UI_STATE_ELEMENT)) {
640
641		// expect a nested message
642		MessageIO* io = dynamic_cast<MessageIO*>(child);
643		if(!io) {
644			BString err;
645			err <<
646				"RouteAppNodeManager: unexpected child '" <<
647				context.element() << "'\n";
648			context.reportError(err.String());
649		}
650
651		// hand it off via the extended context object
652		try {
653			NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
654			set.importUIState(io->message());
655		}
656		catch(bad_cast& e) {
657			context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
658		}
659	}
660
661}
662
663void RouteAppNodeManager::xmlImportComplete(
664	ImportContext&								context) {
665
666}
667
668// -------------------------------------------------------- //
669
670/*static*/
671void RouteAppNodeManager::AddTo(
672	XML::DocumentType*				docType) {
673
674	// set up the document type
675
676	MessageIO::AddTo(docType);
677	MediaFormatIO::AddTo(docType);
678	ConnectionIO::AddTo(docType);
679	DormantNodeIO::AddTo(docType);
680	LiveNodeIO::AddTo(docType);
681	_add_string_elements(docType);
682}
683
684// -------------------------------------------------------- //
685// implementation
686// -------------------------------------------------------- //
687
688uint64 RouteAppNodeManager::_makeIconKey(
689	media_node_id nodeID, icon_size iconSize) {
690
691	return ((uint64)nodeID) << 32 | iconSize;
692}
693
694void RouteAppNodeManager::_readIconKey(
695	uint64 key, media_node_id& nodeID, icon_size& iconSize) {
696
697	nodeID = key >> 32;
698	iconSize = icon_size(key & 0xffffffff);
699}
700
701void RouteAppNodeManager::_freeIcons() {
702
703	ptr_map_delete(
704		m_iconMap.begin(),
705		m_iconMap.end());
706}
707
708bool RouteAppNodeManager::_canGroup(NodeRef* ref) const {
709
710	// sanity check & easy cases
711	ASSERT(ref);
712	if(ref->isInternal())
713		return true;
714
715	// bar 'touchy' system nodes
716	if(ref == audioMixerNode() || ref == audioOutputNode())
717		return false;
718
719	return true;
720}
721
722// END -- RouteAppNodeManager.cpp --
723