1/*
2 * Copyright (c) 2015 Dario Casalinuovo
3 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files or portions
7 * thereof (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so, subject
11 * to the following conditions:
12 *
13 *  * Redistributions of source code must retain the above copyright notice,
14 *    this list of conditions and the following disclaimer.
15 *
16 *  * Redistributions in binary form must reproduce the above copyright notice
17 *    in the  binary, as well as this list of conditions and the following
18 *    disclaimer in the documentation and/or other materials provided with
19 *    the distribution.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 */
30
31
32#include "NodeManager.h"
33
34#include <Application.h>
35#include <Autolock.h>
36#include <Entry.h>
37#include <MediaAddOn.h>
38#include <MediaDefs.h>
39#include <Message.h>
40#include <Messenger.h>
41#include <OS.h>
42#include <Path.h>
43
44#include <MediaDebug.h>
45#include <MediaMisc.h>
46#include <Notifications.h>
47
48#include "AppManager.h"
49#include "DefaultManager.h"
50#include "media_server.h"
51
52
53const char*
54get_node_type(node_type type)
55{
56#define CASE(c) case c: return #c;
57	switch (type) {
58		CASE(VIDEO_INPUT)
59		CASE(AUDIO_INPUT)
60		CASE(VIDEO_OUTPUT)
61		CASE(AUDIO_MIXER)
62		CASE(AUDIO_OUTPUT)
63		CASE(AUDIO_OUTPUT_EX)
64		CASE(TIME_SOURCE)
65		CASE(SYSTEM_TIME_SOURCE)
66
67		default:
68			return "unknown";
69	}
70#undef CASE
71}
72
73
74// #pragma mark -
75
76
77NodeManager::NodeManager()
78	:
79	BLocker("node manager"),
80	fNextAddOnID(1),
81	fNextNodeID(1),
82	fDefaultManager(new DefaultManager)
83{
84}
85
86
87NodeManager::~NodeManager()
88{
89	delete fDefaultManager;
90}
91
92
93// #pragma mark - Default node management
94
95
96status_t
97NodeManager::SetDefaultNode(node_type type, const media_node* node,
98	const dormant_node_info* info, const media_input* input)
99{
100	BAutolock _(this);
101
102	status_t status = B_BAD_VALUE;
103	if (node != NULL)
104		status = fDefaultManager->Set(node->node, NULL, 0, type);
105	else if (input != NULL) {
106		status = fDefaultManager->Set(input->node.node, input->name,
107			input->destination.id, type);
108	} else if (info != NULL) {
109		media_node_id nodeID;
110		int32 count = 1;
111		status = GetInstances(info->addon, info->flavor_id, &nodeID, &count,
112			count);
113		if (status == B_OK)
114			status = fDefaultManager->Set(nodeID, NULL, 0, type);
115	}
116
117	if (status == B_OK && (type == VIDEO_INPUT || type == VIDEO_OUTPUT
118			|| type == AUDIO_OUTPUT || type == AUDIO_INPUT)) {
119		fDefaultManager->SaveState(this);
120		Dump();
121	}
122	return status;
123}
124
125
126status_t
127NodeManager::GetDefaultNode(node_type type, media_node_id* _nodeID,
128	char* inputName, int32* _inputID)
129{
130	BAutolock _(this);
131	return fDefaultManager->Get(_nodeID, inputName, _inputID, type);
132}
133
134
135status_t
136NodeManager::RescanDefaultNodes()
137{
138	BAutolock _(this);
139	return fDefaultManager->Rescan();
140}
141
142
143// #pragma mark - Live node management
144
145
146status_t
147NodeManager::RegisterNode(media_addon_id addOnID, int32 flavorID,
148	const char* name, uint64 kinds, port_id port, team_id team,
149	media_node_id timesource, media_node_id* _nodeID)
150{
151	BAutolock _(this);
152
153	registered_node node;
154	node.timesource_id = timesource;
155	node.add_on_id = addOnID;
156	node.flavor_id = flavorID;
157	strlcpy(node.name, name, sizeof(node.name));
158	node.kinds = kinds;
159	node.port = port;
160	node.containing_team = team;
161	node.creator = -1; // will be set later
162	node.ref_count = 1;
163
164	if ((node.kinds & B_TIME_SOURCE) != 0
165			&& strcmp(node.name, "System clock") == 0) {
166		// This may happen when media_addon_server crash,
167		// we will replace the old timesource.
168		node.node_id = NODE_SYSTEM_TIMESOURCE_ID;
169
170		NodeMap::iterator found = fNodeMap.find(node.node_id);
171		if (found != fNodeMap.end())
172			fNodeMap.erase(node.node_id);
173
174		*_nodeID = node.node_id;
175	} else {
176		node.node_id = fNextNodeID;
177		*_nodeID = node.node_id;
178	}
179
180	try {
181		node.team_ref_count.insert(std::make_pair(team, 1));
182		fNodeMap.insert(std::make_pair(node.node_id, node));
183	} catch (std::bad_alloc& exception) {
184		return B_NO_MEMORY;
185	}
186
187	fNextNodeID++;
188
189	TRACE("NodeManager::RegisterNode: node %" B_PRId32 ", addon_id %" B_PRId32
190		", flavor_id %" B_PRId32 ", name \"%s\", kinds %#" B_PRIx64", port %"
191		B_PRId32 ", team %" B_PRId32 "\n", *_nodeID, addOnID, flavorID, name,
192		kinds, port, team);
193	return B_OK;
194}
195
196
197status_t
198NodeManager::UnregisterNode(media_node_id id, team_id team,
199	media_addon_id* _addOnID, int32* _flavorID)
200{
201	TRACE("NodeManager::UnregisterNode enter: node %" B_PRId32 ", team %"
202		B_PRId32 "\n", id, team);
203
204	BAutolock _(this);
205
206	NodeMap::iterator found = fNodeMap.find(id);
207	if (found == fNodeMap.end()) {
208		ERROR("NodeManager::UnregisterNode: couldn't find node %" B_PRId32
209			" (team %" B_PRId32 ")\n", id, team);
210		return B_ERROR;
211	}
212
213	registered_node& node = found->second;
214
215	if (node.containing_team != team) {
216		ERROR("NodeManager::UnregisterNode: team %" B_PRId32 " tried to "
217			"unregister node %" B_PRId32 ", but it was instantiated by team %"
218			B_PRId32 "\n", team, id, node.containing_team);
219		return B_ERROR;
220	}
221	if (node.ref_count != 1) {
222		ERROR("NodeManager::UnregisterNode: node %" B_PRId32 ", team %"
223			B_PRId32 " has ref count %" B_PRId32 " (should be 1)\n", id, team,
224			node.ref_count);
225		//return B_ERROR;
226	}
227
228	if (_addOnID != NULL)
229		*_addOnID = node.add_on_id;
230
231	if (_flavorID != NULL)
232		*_flavorID = node.flavor_id;
233
234	fNodeMap.erase(found);
235
236	TRACE("NodeManager::UnregisterNode leave: node %" B_PRId32 ", addon_id %"
237		B_PRId32 ", flavor_id %" B_PRId32 " team %" B_PRId32 "\n", id,
238		*_addOnID, *_flavorID, team);
239	return B_OK;
240}
241
242
243status_t
244NodeManager::ReleaseNodeReference(media_node_id id, team_id team)
245{
246	TRACE("NodeManager::ReleaseNodeReference enter: node %" B_PRId32 ", team %"
247		B_PRId32 "\n", id, team);
248
249	BAutolock _(this);
250
251	NodeMap::iterator found = fNodeMap.find(id);
252	if (found == fNodeMap.end()) {
253		ERROR("NodeManager::ReleaseNodeReference: node %" B_PRId32 " not "
254			"found\n", id);
255		return B_ERROR;
256	}
257
258	registered_node& node = found->second;
259
260	TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
261	if (teamRef == node.team_ref_count.end()) {
262		// Normally it is an error to release a node in another team. But we
263		// make one exception: if the node is global, and the creator team
264		// tries to release it, we will release it in the the
265		// media_addon_server.
266		team_id addOnServer = gAppManager->AddOnServerTeam();
267		teamRef = node.team_ref_count.find(addOnServer);
268
269		if (node.creator == team && teamRef != node.team_ref_count.end()) {
270			PRINT(1, "!!! NodeManager::ReleaseNodeReference doing global "
271				"release!\n");
272			node.creator = -1; // invalidate!
273			team = addOnServer;
274		} else {
275			ERROR("NodeManager::ReleaseNodeReference: node %" B_PRId32 " has "
276				"no team %" B_PRId32 " references\n", id, team);
277			return B_ERROR;
278		}
279	}
280
281#if DEBUG
282	int32 teamCount = teamRef->second - 1;
283	(void)teamCount;
284#endif
285
286	if (--teamRef->second == 0)
287		node.team_ref_count.erase(teamRef);
288
289	if (--node.ref_count == 0) {
290		PRINT(1, "NodeManager::ReleaseNodeReference: detected released node is"
291			" now unused, node %" B_PRId32 "\n", id);
292
293		// TODO: remove!
294		node_final_release_command command;
295		status_t status = SendToPort(node.port, NODE_FINAL_RELEASE, &command,
296			sizeof(command));
297		if (status != B_OK) {
298			ERROR("NodeManager::ReleaseNodeReference: can't send command to "
299				"node %" B_PRId32 "\n", id);
300			// ignore error
301		}
302	}
303
304	TRACE("NodeManager::ReleaseNodeReference leave: node %" B_PRId32 ", team %"
305		B_PRId32 ", ref %" B_PRId32 ", team ref %" B_PRId32 "\n", id, team,
306		node.ref_count, teamCount);
307	return B_OK;
308}
309
310
311status_t
312NodeManager::ReleaseNodeAll(media_node_id id)
313{
314	TRACE("NodeManager::ReleaseNodeAll enter: node %" B_PRId32 "\n", id);
315
316	BAutolock _(this);
317
318	NodeMap::iterator found = fNodeMap.find(id);
319	if (found == fNodeMap.end()) {
320		ERROR("NodeManager::ReleaseNodeAll: node %" B_PRId32 " not found\n",
321			id);
322		return B_ERROR;
323	}
324
325	registered_node& node = found->second;
326	node.team_ref_count.clear();
327	node.ref_count = 0;
328
329	node_final_release_command command;
330	status_t status = SendToPort(node.port, NODE_FINAL_RELEASE, &command,
331		sizeof(command));
332	if (status != B_OK) {
333		ERROR("NodeManager::ReleaseNodeAll: can't send command to "
334			"node %" B_PRId32 "\n", id);
335		// ignore error
336	}
337
338	TRACE("NodeManager::ReleaseNodeAll leave: node %" B_PRId32 "\n", id);
339	return B_OK;
340}
341
342
343status_t
344NodeManager::SetNodeCreator(media_node_id id, team_id creator)
345{
346	TRACE("NodeManager::SetNodeCreator node %" B_PRId32 ", creator %" B_PRId32
347		"\n", id, creator);
348
349	BAutolock _(this);
350
351	NodeMap::iterator found = fNodeMap.find(id);
352	if (found == fNodeMap.end()) {
353		ERROR("NodeManager::SetNodeCreator: node %" B_PRId32 " not found\n",
354			id);
355		return B_ERROR;
356	}
357
358	registered_node& node = found->second;
359
360	if (node.creator != -1) {
361		ERROR("NodeManager::SetNodeCreator: node %" B_PRId32 " is already"
362			" assigned creator %" B_PRId32 "\n", id, node.creator);
363		return B_ERROR;
364	}
365
366	node.creator = creator;
367	return B_OK;
368}
369
370
371status_t
372NodeManager::GetCloneForID(media_node_id id, team_id team, media_node* node)
373{
374	TRACE("NodeManager::GetCloneForID enter: node %" B_PRId32 " team %"
375		B_PRId32 "\n", id, team);
376
377	BAutolock _(this);
378
379	status_t status = _AcquireNodeReference(id, team);
380	if (status != B_OK) {
381		ERROR("NodeManager::GetCloneForID: couldn't increment ref count, "
382			"node %" B_PRId32 " team %" B_PRId32 "\n", id, team);
383		return status;
384	}
385
386	NodeMap::iterator found = fNodeMap.find(id);
387	if (found == fNodeMap.end()) {
388		ERROR("NodeManager::GetCloneForID: node %" B_PRId32 " not found\n",
389			id);
390		return B_ERROR;
391	}
392
393	registered_node& registeredNode = found->second;
394
395	node->node = registeredNode.node_id;
396	node->port = registeredNode.port;
397	node->kind = registeredNode.kinds;
398
399	TRACE("NodeManager::GetCloneForID leave: node %" B_PRId32 " team %"
400		B_PRId32 "\n", id, team);
401	return B_OK;
402}
403
404
405/*!	This function locates the default "node" for the requested "type" and
406	returns a clone.
407	If the requested type is AUDIO_OUTPUT_EX, also "input_name" and "input_id"
408	need to be set and returned, as this is required by
409	BMediaRoster::GetAudioOutput(media_node *out_node, int32 *out_input_id,
410		BString *out_input_name).
411*/
412status_t
413NodeManager::GetClone(node_type type, team_id team, media_node* node,
414	char* inputName, int32* _inputID)
415{
416	BAutolock _(this);
417
418	TRACE("NodeManager::GetClone enter: team %" B_PRId32 ", type %d (%s)\n",
419		team, type, get_node_type(type));
420
421	media_node_id id;
422	status_t status = GetDefaultNode(type, &id, inputName, _inputID);
423	if (status != B_OK) {
424		ERROR("NodeManager::GetClone: couldn't GetDefaultNode, team %" B_PRId32
425			", type %d (%s)\n", team, type, get_node_type(type));
426		*node = media_node::null;
427		return status;
428	}
429	ASSERT(id > 0);
430
431	status = GetCloneForID(id, team, node);
432	if (status != B_OK) {
433		ERROR("NodeManager::GetClone: couldn't GetCloneForID, id %" B_PRId32
434			", team %" B_PRId32 ", type %d (%s)\n", id, team, type,
435			get_node_type(type));
436		*node = media_node::null;
437		return status;
438	}
439	ASSERT(id == node->node);
440
441	TRACE("NodeManager::GetClone leave: node id %" B_PRId32 ", node port %"
442		B_PRId32 ", node kind %#" B_PRIx64 "\n", node->node, node->port,
443		node->kind);
444	return B_OK;
445}
446
447
448status_t
449NodeManager::ReleaseNode(const media_node& node, team_id team)
450{
451	TRACE("NodeManager::ReleaseNode enter: node %" B_PRId32 " team %" B_PRId32
452		"\n", node.node, team);
453
454	if (ReleaseNodeReference(node.node, team) != B_OK) {
455		ERROR("NodeManager::ReleaseNode: couldn't decrement node %" B_PRId32
456			" team %" B_PRId32 " ref count\n", node.node, team);
457	}
458
459	return B_OK;
460}
461
462
463status_t
464NodeManager::PublishInputs(const media_node& node, const media_input* inputs,
465	int32 count)
466{
467	BAutolock _(this);
468
469	NodeMap::iterator found = fNodeMap.find(node.node);
470	if (found == fNodeMap.end()) {
471		ERROR("NodeManager::PublishInputs: node %" B_PRId32 " not found\n",
472			node.node);
473		return B_ERROR;
474	}
475
476	registered_node& registeredNode = found->second;
477
478	registeredNode.input_list.clear();
479
480	try {
481		for (int32 i = 0; i < count; i++)
482			registeredNode.input_list.push_back(inputs[i]);
483	} catch (std::bad_alloc& exception) {
484		return B_NO_MEMORY;
485	}
486
487	return B_OK;
488}
489
490
491status_t
492NodeManager::PublishOutputs(const media_node &node, const media_output* outputs,
493	int32 count)
494{
495	BAutolock _(this);
496
497	NodeMap::iterator found = fNodeMap.find(node.node);
498	if (found == fNodeMap.end()) {
499		ERROR("NodeManager::PublishOutputs: node %" B_PRId32 " not found\n",
500			node.node);
501		return B_ERROR;
502	}
503
504	registered_node& registeredNode = found->second;
505
506	registeredNode.output_list.clear();
507
508	try {
509		for (int32 i = 0; i < count; i++)
510			registeredNode.output_list.push_back(outputs[i]);
511	} catch (std::bad_alloc& exception) {
512		return B_NO_MEMORY;
513	}
514
515	return B_OK;
516}
517
518
519status_t
520NodeManager::FindNodeID(port_id port, media_node_id* _id)
521{
522	BAutolock _(this);
523
524	NodeMap::iterator iterator = fNodeMap.begin();
525	for (; iterator != fNodeMap.end(); iterator++) {
526		registered_node& node = iterator->second;
527
528		if (node.port == port) {
529			*_id = node.node_id;
530			TRACE("NodeManager::FindNodeID found port %" B_PRId32 ", node %"
531				B_PRId32 "\n", port, node.node_id);
532			return B_OK;
533		}
534
535		OutputList::iterator outIterator = node.output_list.begin();
536		for (; outIterator != node.output_list.end(); outIterator++) {
537			if (outIterator->source.port == port) {
538				*_id = node.node_id;
539				TRACE("NodeManager::FindNodeID found output port %" B_PRId32
540					", node %" B_PRId32 "\n", port, node.node_id);
541				return B_OK;
542			}
543		}
544
545		InputList::iterator inIterator = node.input_list.begin();
546		for (; inIterator != node.input_list.end(); inIterator++) {
547			if (inIterator->destination.port == port) {
548				*_id = node.node_id;
549				TRACE("NodeManager::FindNodeID found input port %" B_PRId32
550					", node %" B_PRId32 "\n", port, node.node_id);
551				return B_OK;
552			}
553		}
554	}
555
556	ERROR("NodeManager::FindNodeID failed, port %" B_PRId32 "\n", port);
557	return B_ERROR;
558}
559
560
561status_t
562NodeManager::GetDormantNodeInfo(const media_node& node,
563	dormant_node_info* nodeInfo)
564{
565	// TODO: not sure if this is correct
566	BAutolock _(this);
567
568	NodeMap::iterator found = fNodeMap.find(node.node);
569	if (found == fNodeMap.end()) {
570		ERROR("NodeManager::GetDormantNodeInfo: node %" B_PRId32 " not found"
571			"\n", node.node);
572		return B_ERROR;
573	}
574
575	registered_node& registeredNode = found->second;
576
577	if (registeredNode.add_on_id == -1
578		&& node.node != NODE_SYSTEM_TIMESOURCE_ID) {
579		// This function must return an error if the node is application owned
580		TRACE("NodeManager::GetDormantNodeInfo NODE IS APPLICATION OWNED! "
581			"node %" B_PRId32 ", add_on_id %" B_PRId32 ", flavor_id %" B_PRId32
582			", name \"%s\"\n", node.node, registeredNode.add_on_id,
583			registeredNode.flavor_id, registeredNode.name);
584		return B_ERROR;
585	}
586
587	ASSERT(node.port == registeredNode.port);
588	ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
589		== (registeredNode.kinds & NODE_KIND_COMPARE_MASK));
590
591	nodeInfo->addon = registeredNode.add_on_id;
592	nodeInfo->flavor_id = registeredNode.flavor_id;
593	strlcpy(nodeInfo->name, registeredNode.name, sizeof(nodeInfo->name));
594
595	TRACE("NodeManager::GetDormantNodeInfo node %" B_PRId32 ", add_on_id %"
596		B_PRId32 ", flavor_id %" B_PRId32 ", name \"%s\"\n", node.node,
597		registeredNode.add_on_id, registeredNode.flavor_id,
598		registeredNode.name);
599	return B_OK;
600}
601
602
603status_t
604NodeManager::GetLiveNodeInfo(const media_node& node, live_node_info* liveInfo)
605{
606	BAutolock _(this);
607
608	NodeMap::iterator found = fNodeMap.find(node.node);
609	if (found == fNodeMap.end()) {
610		ERROR("NodeManager::GetLiveNodeInfo: node %" B_PRId32 " not found\n",
611			node.node);
612		return B_ERROR;
613	}
614
615	registered_node& registeredNode = found->second;
616
617	ASSERT(node.port == registeredNode.port);
618	ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
619		== (registeredNode.kinds & NODE_KIND_COMPARE_MASK));
620
621	liveInfo->node = node;
622	liveInfo->hint_point = BPoint(0, 0);
623	strlcpy(liveInfo->name, registeredNode.name, sizeof(liveInfo->name));
624
625	TRACE("NodeManager::GetLiveNodeInfo node %" B_PRId32 ", name = \"%s\"\n",
626		node.node, registeredNode.name);
627	return B_OK;
628}
629
630
631status_t
632NodeManager::GetInstances(media_addon_id addOnID, int32 flavorID,
633	media_node_id* ids, int32* _count, int32 maxCount)
634{
635	BAutolock _(this);
636
637	NodeMap::iterator iterator = fNodeMap.begin();
638	int32 count = 0;
639	for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
640		registered_node& node = iterator->second;
641
642		if (node.add_on_id == addOnID && node.flavor_id == flavorID)
643			ids[count++] = node.node_id;
644	}
645
646	TRACE("NodeManager::GetInstances found %" B_PRId32 " instances for "
647		"addon_id %" B_PRId32 ", flavor_id %" B_PRId32 "\n", count, addOnID,
648		flavorID);
649	*_count = count;
650	return B_OK;
651}
652
653
654status_t
655NodeManager::GetLiveNodes(LiveNodeList& liveNodes, int32 maxCount,
656	const media_format* inputFormat, const media_format* outputFormat,
657	const char* name, uint64 requireKinds)
658{
659	TRACE("NodeManager::GetLiveNodes: maxCount %" B_PRId32 ", in-format %p, "
660		"out-format %p, name %s, require kinds 0x%" B_PRIx64 "\n", maxCount,
661		inputFormat, outputFormat, name != NULL ? name : "NULL", requireKinds);
662
663	BAutolock _(this);
664
665	// Determine the count of byte to compare when checking for a name with
666	// or without wildcard
667	size_t nameLength = 0;
668	if (name != NULL) {
669		nameLength = strlen(name);
670		if (nameLength > 0 && name[nameLength - 1] == '*')
671			nameLength--;
672	}
673
674	NodeMap::iterator iterator = fNodeMap.begin();
675	int32 count = 0;
676	for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
677		registered_node& node = iterator->second;
678
679		if ((node.kinds & requireKinds) != requireKinds)
680			continue;
681
682		if (nameLength != 0) {
683			if (strncmp(name, node.name, nameLength) != 0)
684				continue;
685		}
686
687		if (inputFormat != NULL) {
688			bool found = false;
689
690			for (InputList::iterator inIterator = node.input_list.begin();
691					inIterator != node.input_list.end(); inIterator++) {
692				media_input& input = *inIterator;
693
694				if (format_is_compatible(*inputFormat, input.format)) {
695					found = true;
696					break;
697				}
698			}
699
700			if (!found)
701				continue;
702		}
703
704		if (outputFormat != NULL) {
705			bool found = false;
706
707			for (OutputList::iterator outIterator = node.output_list.begin();
708					outIterator != node.output_list.end(); outIterator++) {
709				media_output& output = *outIterator;
710
711				if (format_is_compatible(*outputFormat, output.format)) {
712					found = true;
713					break;
714				}
715			}
716
717			if (!found)
718				continue;
719		}
720
721		live_node_info info;
722		info.node.node = node.node_id;
723		info.node.port = node.port;
724		info.node.kind = node.kinds;
725		info.hint_point = BPoint(0, 0);
726		strlcpy(info.name, node.name, sizeof(info.name));
727
728		try {
729			liveNodes.push_back(info);
730		} catch (std::bad_alloc& exception) {
731			return B_NO_MEMORY;
732		}
733
734		count++;
735	}
736
737	TRACE("NodeManager::GetLiveNodes found %" B_PRId32 "\n", count);
738	return B_OK;
739}
740
741
742/*!	Add media_node_id of all live nodes to the message
743	int32 "media_node_id" (multiple items)
744*/
745status_t
746NodeManager::GetLiveNodes(BMessage* message)
747{
748	BAutolock _(this);
749
750	NodeMap::iterator iterator = fNodeMap.begin();
751	for (; iterator != fNodeMap.end(); iterator++) {
752		registered_node& node = iterator->second;
753
754		if (message->AddInt32("media_node_id", node.node_id) != B_OK)
755			return B_NO_MEMORY;
756	}
757
758	return B_OK;
759}
760
761
762// #pragma mark - Registration of BMediaAddOns
763
764
765void
766NodeManager::RegisterAddOn(const entry_ref& ref, media_addon_id* _newID)
767{
768	BAutolock _(this);
769
770	media_addon_id id = fNextAddOnID++;
771
772//	printf("NodeManager::RegisterAddOn: ref-name \"%s\", assigning id %"
773//		B_PRId32 "\n", ref.name, id);
774
775	try {
776		fPathMap.insert(std::make_pair(id, ref));
777		*_newID = id;
778	} catch (std::bad_alloc& exception) {
779		*_newID = -1;
780	}
781}
782
783
784void
785NodeManager::UnregisterAddOn(media_addon_id addOnID)
786{
787	PRINT(1, "NodeManager::UnregisterAddOn: id %" B_PRId32 "\n", addOnID);
788
789	BAutolock _(this);
790
791	RemoveDormantFlavorInfo(addOnID);
792	fPathMap.erase(addOnID);
793}
794
795
796status_t
797NodeManager::GetAddOnRef(media_addon_id addOnID, entry_ref* ref)
798{
799	BAutolock _(this);
800
801	PathMap::iterator found = fPathMap.find(addOnID);
802	if (found == fPathMap.end())
803		return B_ERROR;
804
805	*ref = found->second;
806	return B_OK;
807}
808
809
810// #pragma mark - Registration of node flavors, published by BMediaAddOns
811
812
813//!	This function is only used (indirectly) by the media_addon_server.
814status_t
815NodeManager::AddDormantFlavorInfo(const dormant_flavor_info& flavorInfo)
816{
817	PRINT(1, "NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
818		"flavor-id %" B_PRId32 ", name \"%s\", flavor-name \"%s\", flavor-info"
819		" \"%s\"\n", flavorInfo.node_info.addon,
820		flavorInfo.node_info.flavor_id, flavorInfo.node_info.name,
821		flavorInfo.name, flavorInfo.info);
822
823	BAutolock _(this);
824
825	// Try to find the addon-id/flavor-id in the list.
826	// If it already exists, update the info, but don't change its instance
827	// count.
828
829	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
830			iterator != fDormantFlavors.end(); iterator++) {
831		dormant_add_on_flavor_info& info = *iterator;
832
833		if (info.add_on_id != flavorInfo.node_info.addon
834			|| info.flavor_id != flavorInfo.node_info.flavor_id)
835			continue;
836
837		if (info.info_valid) {
838			ERROR("NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
839				"flavor-id %" B_PRId32 " does already exist\n",
840				info.info.node_info.addon, info.info.node_info.flavor_id);
841		}
842
843		TRACE("NodeManager::AddDormantFlavorInfo, updating addon-id %" B_PRId32
844			", flavor-id %" B_PRId32 "\n", info.info.node_info.addon,
845			info.info.node_info.flavor_id);
846
847		info.max_instances_count = flavorInfo.possible_count > 0
848			? flavorInfo.possible_count : INT32_MAX;
849		info.info_valid = true;
850		info.info = flavorInfo;
851		return B_OK;
852	}
853
854	// Insert information into the list
855
856	dormant_add_on_flavor_info info;
857	info.add_on_id = flavorInfo.node_info.addon;
858	info.flavor_id = flavorInfo.node_info.flavor_id;
859	info.max_instances_count = flavorInfo.possible_count > 0
860		? flavorInfo.possible_count : INT32_MAX;
861	info.instances_count = 0;
862	info.info_valid = true;
863	info.info = flavorInfo;
864
865	try {
866		fDormantFlavors.push_back(info);
867	} catch (std::bad_alloc& exception) {
868		return B_NO_MEMORY;
869	}
870
871	return B_OK;
872}
873
874
875//!	This function is only used (indirectly) by the media_addon_server
876void
877NodeManager::InvalidateDormantFlavorInfo(media_addon_id addOnID)
878{
879	BAutolock _(this);
880
881	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
882			iterator != fDormantFlavors.end(); iterator++) {
883		dormant_add_on_flavor_info& info = *iterator;
884
885		if (info.add_on_id == addOnID && info.info_valid) {
886			PRINT(1, "NodeManager::InvalidateDormantFlavorInfo, addon-id %"
887				B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
888				"\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
889				info.info.node_info.flavor_id, info.info.node_info.name,
890				info.info.name, info.info.info);
891
892			info.info_valid = false;
893		}
894	}
895}
896
897
898//!	This function is only used (indirectly) by the media_addon_server
899void
900NodeManager::RemoveDormantFlavorInfo(media_addon_id addOnID)
901{
902	BAutolock _(this);
903
904	for (size_t index = 0; index < fDormantFlavors.size(); index++) {
905		dormant_add_on_flavor_info& info = fDormantFlavors[index];
906
907		if (info.add_on_id == addOnID) {
908			PRINT(1, "NodeManager::RemoveDormantFlavorInfo, addon-id %"
909				B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
910				"\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
911				info.info.node_info.flavor_id, info.info.node_info.name,
912				info.info.name, info.info.info);
913			fDormantFlavors.erase(fDormantFlavors.begin() + index--);
914		}
915	}
916}
917
918
919status_t
920NodeManager::IncrementFlavorInstancesCount(media_addon_id addOnID,
921	int32 flavorID, team_id team)
922{
923	BAutolock _(this);
924
925	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
926			iterator != fDormantFlavors.end(); iterator++) {
927		dormant_add_on_flavor_info& info = *iterator;
928
929		if (info.add_on_id != addOnID || info.flavor_id != flavorID)
930			continue;
931
932		if (info.instances_count >= info.max_instances_count) {
933			// maximum (or more) instances already exist
934			ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %"
935				B_PRId32 ", flavor-id %" B_PRId32 " maximum (or more) "
936				"instances already exist\n", addOnID, flavorID);
937			return B_ERROR;
938		}
939
940		TeamCountMap::iterator teamInstance
941			= info.team_instances_count.find(team);
942		if (teamInstance == info.team_instances_count.end()) {
943			// This is the team's first instance
944			try {
945				info.team_instances_count.insert(std::make_pair(team, 1));
946			} catch (std::bad_alloc& exception) {
947				return B_NO_MEMORY;
948			}
949		} else {
950			// Just increase its ref count
951			teamInstance->second++;
952		}
953
954		info.instances_count++;
955		return B_OK;
956	}
957
958	ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %" B_PRId32 ", "
959		"flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
960	return B_ERROR;
961}
962
963
964status_t
965NodeManager::DecrementFlavorInstancesCount(media_addon_id addOnID,
966	int32 flavorID, team_id team)
967{
968	BAutolock _(this);
969
970	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
971			iterator != fDormantFlavors.end(); iterator++) {
972		dormant_add_on_flavor_info& info = *iterator;
973
974		if (info.add_on_id != addOnID || info.flavor_id != flavorID)
975			continue;
976
977		TeamCountMap::iterator teamInstance
978			= info.team_instances_count.find(team);
979		if (teamInstance == info.team_instances_count.end()) {
980			ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %"
981				B_PRId32 ", flavor-id %" B_PRId32 " team %" B_PRId32 " has no "
982				"references\n", addOnID, flavorID, team);
983			return B_ERROR;
984		}
985		if (--teamInstance->second == 0)
986			info.team_instances_count.erase(teamInstance);
987
988		info.instances_count--;
989		return B_OK;
990	}
991
992	ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %" B_PRId32 ", "
993		"flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
994	return B_ERROR;
995}
996
997
998//!	This function is called when the media_addon_server has crashed
999void
1000NodeManager::CleanupDormantFlavorInfos()
1001{
1002	PRINT(1, "NodeManager::CleanupDormantFlavorInfos\n");
1003
1004	BAutolock _(this);
1005
1006	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1007			iterator != fDormantFlavors.end(); iterator++) {
1008		dormant_add_on_flavor_info& info = *iterator;
1009
1010		// Current instance count is zero since the media_addon_server crashed.
1011		BPrivate::media::notifications::FlavorsChanged(info.add_on_id,
1012			0, info.instances_count);
1013	}
1014
1015	fDormantFlavors.clear();
1016
1017	PRINT(1, "NodeManager::CleanupDormantFlavorInfos done\n");
1018}
1019
1020
1021status_t
1022NodeManager::GetDormantNodes(dormant_node_info* infos, int32* _count,
1023	const media_format* input, const media_format* output, const char* name,
1024	uint64 requireKinds, uint64 denyKinds)
1025{
1026	BAutolock _(this);
1027
1028	// Determine the count of byte to compare when checking for a name with
1029	// or without wildcard
1030	size_t nameLength = 0;
1031	if (name != NULL) {
1032		nameLength = strlen(name);
1033		if (nameLength > 0 && name[nameLength - 1] == '*')
1034			nameLength--;
1035	}
1036
1037	int32 maxCount = *_count;
1038	int32 count = 0;
1039
1040	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1041			iterator != fDormantFlavors.end() && count < maxCount; iterator++) {
1042		dormant_add_on_flavor_info& info = *iterator;
1043
1044		if (!info.info_valid)
1045			continue;
1046
1047		if ((info.info.kinds & requireKinds) != requireKinds
1048			|| (info.info.kinds & denyKinds) != 0)
1049			continue;
1050
1051		if (nameLength != 0) {
1052			if (strncmp(name, info.info.name, nameLength) != 0)
1053				continue;
1054		}
1055
1056		if (input != NULL) {
1057			bool found = false;
1058
1059			for (int32 i = 0; i < info.info.in_format_count; i++) {
1060				if (format_is_compatible(*input, info.info.in_formats[i])) {
1061					found = true;
1062					break;
1063				}
1064			}
1065
1066			if (!found)
1067				continue;
1068		}
1069
1070		if (output != NULL) {
1071			bool found = false;
1072
1073			for (int32 i = 0; i < info.info.out_format_count; i++) {
1074				if (format_is_compatible(*output, info.info.out_formats[i])) {
1075					found = true;
1076					break;
1077				}
1078			}
1079
1080			if (!found)
1081				continue;
1082		}
1083
1084		infos[count++] = info.info.node_info;
1085	}
1086
1087	*_count = count;
1088	return B_OK;
1089}
1090
1091
1092status_t
1093NodeManager::GetDormantFlavorInfoFor(media_addon_id addOnID, int32 flavorID,
1094	dormant_flavor_info* flavorInfo)
1095{
1096	BAutolock _(this);
1097
1098	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1099			iterator != fDormantFlavors.end(); iterator++) {
1100		dormant_add_on_flavor_info& info = *iterator;
1101
1102		if (info.add_on_id == addOnID && info.flavor_id == flavorID
1103			&& info.info_valid) {
1104			*flavorInfo = info.info;
1105			return B_OK;
1106		}
1107	}
1108
1109	return B_ERROR;
1110}
1111
1112
1113// #pragma mark - Misc.
1114
1115
1116status_t
1117NodeManager::SetNodeTimeSource(media_node_id node,
1118	media_node_id timesource)
1119{
1120	BAutolock _(this);
1121
1122	NodeMap::iterator found = fNodeMap.find(node);
1123	if (found == fNodeMap.end()) {
1124		ERROR("NodeManager::SetNodeTimeSource: node %"
1125			B_PRId32 " not found\n", node);
1126		return B_ERROR;
1127	}
1128	registered_node& registeredNode = found->second;
1129	registeredNode.timesource_id = timesource;
1130	return B_OK;
1131}
1132
1133
1134void
1135NodeManager::CleanupTeam(team_id team)
1136{
1137	BAutolock _(this);
1138
1139	fDefaultManager->CleanupTeam(team);
1140
1141	PRINT(1, "NodeManager::CleanupTeam: team %" B_PRId32 "\n", team);
1142
1143	// Cleanup node references
1144
1145	for (NodeMap::iterator iterator = fNodeMap.begin();
1146			iterator != fNodeMap.end();) {
1147		registered_node& node = iterator->second;
1148		NodeMap::iterator remove = iterator++;
1149
1150		// If the gone team was the creator of some global dormant node
1151		// instance, we now invalidate that we may want to remove that
1152		// global node, but I'm not sure
1153		if (node.creator == team) {
1154			node.creator = -1;
1155			// fall through
1156		}
1157
1158		// If the team hosting this node is gone, remove node from database
1159		if (node.containing_team == team) {
1160			PRINT(1, "NodeManager::CleanupTeam: removing node id %" B_PRId32
1161				", team %" B_PRId32 "\n", node.node_id, team);
1162			// Ensure the slave node is removed from it's timesource
1163			_NotifyTimeSource(node);
1164			fNodeMap.erase(remove);
1165			BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
1166			continue;
1167		}
1168
1169		// Check the list of teams that have references to this node, and
1170		// remove the team
1171		TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
1172		if (teamRef != node.team_ref_count.end()) {
1173			PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " refs "
1174				"from node id %" B_PRId32 ", team %" B_PRId32 "\n",
1175				teamRef->second, node.node_id, team);
1176			node.ref_count -= teamRef->second;
1177			if (node.ref_count == 0) {
1178				PRINT(1, "NodeManager::CleanupTeam: removing node id %"
1179					B_PRId32 " that has no teams\n", node.node_id);
1180
1181				// Ensure the slave node is removed from it's timesource
1182				_NotifyTimeSource(node);
1183				fNodeMap.erase(remove);
1184				BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
1185			} else
1186				node.team_ref_count.erase(teamRef);
1187		}
1188	}
1189
1190	// Cleanup add-on references
1191
1192	for (size_t index = 0; index < fDormantFlavors.size(); index++) {
1193		dormant_add_on_flavor_info& flavorInfo = fDormantFlavors[index];
1194
1195		TeamCountMap::iterator instanceCount
1196			= flavorInfo.team_instances_count.find(team);
1197		if (instanceCount != flavorInfo.team_instances_count.end()) {
1198			PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " "
1199				"instances from addon %" B_PRId32 ", flavor %" B_PRId32 "\n",
1200				instanceCount->second, flavorInfo.add_on_id,
1201				flavorInfo.flavor_id);
1202
1203			int32 count = flavorInfo.instances_count;
1204			flavorInfo.instances_count -= instanceCount->second;
1205			if (flavorInfo.instances_count <= 0) {
1206				fDormantFlavors.erase(fDormantFlavors.begin() + index--);
1207				BPrivate::media::notifications::FlavorsChanged(
1208					flavorInfo.add_on_id, 0, count);
1209			} else
1210				flavorInfo.team_instances_count.erase(team);
1211		}
1212	}
1213}
1214
1215
1216status_t
1217NodeManager::LoadState()
1218{
1219	BAutolock _(this);
1220	return fDefaultManager->LoadState();
1221}
1222
1223status_t
1224NodeManager::SaveState()
1225{
1226	BAutolock _(this);
1227	return fDefaultManager->SaveState(this);
1228}
1229
1230
1231void
1232NodeManager::Dump()
1233{
1234	BAutolock _(this);
1235
1236	// for each addon-id, the add-on path map contains an entry_ref
1237
1238	printf("\nNodeManager: addon path map follows:\n");
1239
1240	for (PathMap::iterator iterator = fPathMap.begin();
1241			iterator != fPathMap.end(); iterator++) {
1242		BPath path(&iterator->second);
1243		printf(" addon-id %" B_PRId32 ", path \"%s\"\n", iterator->first,
1244			path.InitCheck() == B_OK ? path.Path() : "INVALID");
1245	}
1246
1247	printf("NodeManager: list end\n\n");
1248
1249	// for each node-id, the registered node map contians information about
1250	// source of the node, users, etc.
1251
1252	printf("NodeManager: registered nodes map follows:\n");
1253	for (NodeMap::iterator iterator = fNodeMap.begin();
1254			iterator != fNodeMap.end(); iterator++) {
1255		registered_node& node = iterator->second;
1256
1257		printf("  node-id %" B_PRId32 ", addon-id %" B_PRId32 ", addon-flavor-"
1258			"id %" B_PRId32 ", port %" B_PRId32 ", creator %" B_PRId32 ", "
1259			"team %" B_PRId32 ", kinds %#08" B_PRIx64 ", name \"%s\", "
1260			"ref_count %" B_PRId32 "\n", node.node_id, node.add_on_id,
1261			node.flavor_id, node.port, node.creator, node.containing_team,
1262			node.kinds, node.name, node.ref_count);
1263
1264		printf("    teams (refcount): ");
1265		for (TeamCountMap::iterator refsIterator = node.team_ref_count.begin();
1266				refsIterator != node.team_ref_count.end(); refsIterator++) {
1267			printf("%" B_PRId32 " (%" B_PRId32 "), ", refsIterator->first,
1268				refsIterator->second);
1269		}
1270		printf("\n");
1271
1272		for (InputList::iterator inIterator = node.input_list.begin();
1273				inIterator != node.input_list.end(); inIterator++) {
1274			media_input& input = *inIterator;
1275			printf("    media_input: node-id %" B_PRId32 ", node-port %"
1276				B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
1277				", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
1278				"\"%s\"\n", input.node.node, input.node.port, input.source.port,
1279				input.source.id, input.destination.port, input.destination.id,
1280				input.name);
1281		}
1282		if (node.input_list.empty())
1283			printf("    media_input: none\n");
1284
1285		for (OutputList::iterator outIterator = node.output_list.begin();
1286				outIterator != node.output_list.end(); outIterator++) {
1287			media_output& output = *outIterator;
1288			printf("    media_output: node-id %" B_PRId32 ", node-port %"
1289				B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
1290				", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
1291				"\"%s\"\n", output.node.node, output.node.port,
1292				output.source.port, output.source.id, output.destination.port,
1293				output.destination.id, output.name);
1294		}
1295		if (node.output_list.empty())
1296			printf("    media_output: none\n");
1297	}
1298
1299	printf("NodeManager: list end\n");
1300	printf("\n");
1301
1302	// Dormant add-on flavors
1303
1304	printf("NodeManager: dormant flavor list follows:\n");
1305
1306	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1307			iterator != fDormantFlavors.end(); iterator++) {
1308		dormant_add_on_flavor_info& flavorInfo = *iterator;
1309
1310		printf("  addon-id %" B_PRId32 ", flavor-id %" B_PRId32 ", max "
1311			"instances count %" B_PRId32 ", instances count %" B_PRId32 ", "
1312			"info valid %s\n", flavorInfo.add_on_id, flavorInfo.flavor_id,
1313			flavorInfo.max_instances_count, flavorInfo.instances_count,
1314			flavorInfo.info_valid ? "yes" : "no");
1315		printf("    teams (instances): ");
1316		for (TeamCountMap::iterator countIterator
1317					= flavorInfo.team_instances_count.begin();
1318				countIterator != flavorInfo.team_instances_count.end();
1319				countIterator++) {
1320			printf("%" B_PRId32 " (%" B_PRId32 "), ", countIterator->first,
1321				countIterator->second);
1322		}
1323		printf("\n");
1324		if (!flavorInfo.info_valid)
1325			continue;
1326
1327		printf("    addon-id %" B_PRId32 ", addon-flavor-id %" B_PRId32 ", "
1328			"addon-name \"%s\"\n", flavorInfo.info.node_info.addon,
1329			flavorInfo.info.node_info.flavor_id,
1330			flavorInfo.info.node_info.name);
1331		printf("    flavor-kinds %#08" B_PRIx64 ", flavor_flags %#08" B_PRIx32
1332			", internal_id %" B_PRId32 ", possible_count %" B_PRId32 ", "
1333			"in_format_count %" B_PRId32 ", out_format_count %" B_PRId32 "\n",
1334			flavorInfo.info.kinds, flavorInfo.info.flavor_flags,
1335			flavorInfo.info.internal_id, flavorInfo.info.possible_count,
1336			flavorInfo.info.in_format_count, flavorInfo.info.out_format_count);
1337		printf("    flavor-name \"%s\"\n", flavorInfo.info.name);
1338		printf("    flavor-info \"%s\"\n", flavorInfo.info.info);
1339	}
1340	printf("NodeManager: list end\n");
1341
1342	fDefaultManager->Dump();
1343}
1344
1345
1346// #pragma mark - private methods
1347
1348
1349status_t
1350NodeManager::_AcquireNodeReference(media_node_id id, team_id team)
1351{
1352	TRACE("NodeManager::_AcquireNodeReference enter: node %" B_PRId32 ", team "
1353		"%" B_PRId32 "\n", id, team);
1354
1355	BAutolock _(this);
1356
1357	NodeMap::iterator found = fNodeMap.find(id);
1358	if (found == fNodeMap.end()) {
1359		ERROR("NodeManager::_AcquireNodeReference: node %" B_PRId32 " not "
1360			"found\n", id);
1361		return B_ERROR;
1362	}
1363
1364	registered_node& node = found->second;
1365
1366	TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
1367	if (teamRef == node.team_ref_count.end()) {
1368		// This is the team's first reference
1369		try {
1370			node.team_ref_count.insert(std::make_pair(team, 1));
1371		} catch (std::bad_alloc& exception) {
1372			return B_NO_MEMORY;
1373		}
1374	} else {
1375		// Just increase its ref count
1376		teamRef->second++;
1377	}
1378
1379	node.ref_count++;
1380
1381	TRACE("NodeManager::_AcquireNodeReference leave: node %" B_PRId32 ", team "
1382		"%" B_PRId32 ", ref %" B_PRId32 ", team ref %" B_PRId32 "\n", id, team,
1383		node.ref_count, node.team_ref_count.find(team)->second);
1384	return B_OK;
1385}
1386
1387
1388void
1389NodeManager::_NotifyTimeSource(registered_node& node)
1390{
1391	team_id team = be_app->Team();
1392	media_node timeSource;
1393	// Ensure the timesource ensure still exists
1394	if (GetCloneForID(node.timesource_id, team, &timeSource) != B_OK)
1395		return;
1396
1397	media_node currentNode;
1398	if (GetCloneForID(node.node_id, team,
1399		&currentNode) == B_OK) {
1400		timesource_remove_slave_node_command cmd;
1401		cmd.node = currentNode;
1402		// Notify slave node removal to owner timesource
1403		SendToPort(timeSource.port, TIMESOURCE_REMOVE_SLAVE_NODE,
1404			&cmd, sizeof(cmd));
1405		ReleaseNode(timeSource, team);
1406	}
1407	ReleaseNode(currentNode, team);
1408}
1409