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