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 %#Lx, port %" B_PRId32
191		", team %" B_PRId32 "\n", *_nodeID, addOnID, flavorID, name, kinds,
192		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 %#lx\n", node->node, node->port, node->kind);
443	return B_OK;
444}
445
446
447status_t
448NodeManager::ReleaseNode(const media_node& node, team_id team)
449{
450	TRACE("NodeManager::ReleaseNode enter: node %" B_PRId32 " team %" B_PRId32
451		"\n", node.node, team);
452
453	if (ReleaseNodeReference(node.node, team) != B_OK) {
454		ERROR("NodeManager::ReleaseNode: couldn't decrement node %" B_PRId32
455			" team %" B_PRId32 " ref count\n", node.node, team);
456	}
457
458	return B_OK;
459}
460
461
462status_t
463NodeManager::PublishInputs(const media_node& node, const media_input* inputs,
464	int32 count)
465{
466	BAutolock _(this);
467
468	NodeMap::iterator found = fNodeMap.find(node.node);
469	if (found == fNodeMap.end()) {
470		ERROR("NodeManager::PublishInputs: node %" B_PRId32 " not found\n",
471			node.node);
472		return B_ERROR;
473	}
474
475	registered_node& registeredNode = found->second;
476
477	registeredNode.input_list.clear();
478
479	try {
480		for (int32 i = 0; i < count; i++)
481			registeredNode.input_list.push_back(inputs[i]);
482	} catch (std::bad_alloc& exception) {
483		return B_NO_MEMORY;
484	}
485
486	return B_OK;
487}
488
489
490status_t
491NodeManager::PublishOutputs(const media_node &node, const media_output* outputs,
492	int32 count)
493{
494	BAutolock _(this);
495
496	NodeMap::iterator found = fNodeMap.find(node.node);
497	if (found == fNodeMap.end()) {
498		ERROR("NodeManager::PublishOutputs: node %" B_PRId32 " not found\n",
499			node.node);
500		return B_ERROR;
501	}
502
503	registered_node& registeredNode = found->second;
504
505	registeredNode.output_list.clear();
506
507	try {
508		for (int32 i = 0; i < count; i++)
509			registeredNode.output_list.push_back(outputs[i]);
510	} catch (std::bad_alloc& exception) {
511		return B_NO_MEMORY;
512	}
513
514	return B_OK;
515}
516
517
518status_t
519NodeManager::FindNodeID(port_id port, media_node_id* _id)
520{
521	BAutolock _(this);
522
523	NodeMap::iterator iterator = fNodeMap.begin();
524	for (; iterator != fNodeMap.end(); iterator++) {
525		registered_node& node = iterator->second;
526
527		if (node.port == port) {
528			*_id = node.node_id;
529			TRACE("NodeManager::FindNodeID found port %" B_PRId32 ", node %"
530				B_PRId32 "\n", port, node.node_id);
531			return B_OK;
532		}
533
534		OutputList::iterator outIterator = node.output_list.begin();
535		for (; outIterator != node.output_list.end(); outIterator++) {
536			if (outIterator->source.port == port) {
537				*_id = node.node_id;
538				TRACE("NodeManager::FindNodeID found output port %" B_PRId32
539					", node %" B_PRId32 "\n", port, node.node_id);
540				return B_OK;
541			}
542		}
543
544		InputList::iterator inIterator = node.input_list.begin();
545		for (; inIterator != node.input_list.end(); inIterator++) {
546			if (inIterator->destination.port == port) {
547				*_id = node.node_id;
548				TRACE("NodeManager::FindNodeID found input port %" B_PRId32
549					", node %" B_PRId32 "\n", port, node.node_id);
550				return B_OK;
551			}
552		}
553	}
554
555	ERROR("NodeManager::FindNodeID failed, port %" B_PRId32 "\n", port);
556	return B_ERROR;
557}
558
559
560status_t
561NodeManager::GetDormantNodeInfo(const media_node& node,
562	dormant_node_info* nodeInfo)
563{
564	// TODO: not sure if this is correct
565	BAutolock _(this);
566
567	NodeMap::iterator found = fNodeMap.find(node.node);
568	if (found == fNodeMap.end()) {
569		ERROR("NodeManager::GetDormantNodeInfo: node %" B_PRId32 " not found"
570			"\n", node.node);
571		return B_ERROR;
572	}
573
574	registered_node& registeredNode = found->second;
575
576	if (registeredNode.add_on_id == -1
577		&& node.node != NODE_SYSTEM_TIMESOURCE_ID) {
578		// This function must return an error if the node is application owned
579		TRACE("NodeManager::GetDormantNodeInfo NODE IS APPLICATION OWNED! "
580			"node %" B_PRId32 ", add_on_id %" B_PRId32 ", flavor_id %" B_PRId32
581			", name \"%s\"\n", node.node, registeredNode.add_on_id,
582			registeredNode.flavor_id, registeredNode.name);
583		return B_ERROR;
584	}
585
586	ASSERT(node.port == registeredNode.port);
587	ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
588		== (registeredNode.kinds & NODE_KIND_COMPARE_MASK));
589
590	nodeInfo->addon = registeredNode.add_on_id;
591	nodeInfo->flavor_id = registeredNode.flavor_id;
592	strlcpy(nodeInfo->name, registeredNode.name, sizeof(nodeInfo->name));
593
594	TRACE("NodeManager::GetDormantNodeInfo node %" B_PRId32 ", add_on_id %"
595		B_PRId32 ", flavor_id %" B_PRId32 ", name \"%s\"\n", node.node,
596		registeredNode.add_on_id, registeredNode.flavor_id,
597		registeredNode.name);
598	return B_OK;
599}
600
601
602status_t
603NodeManager::GetLiveNodeInfo(const media_node& node, live_node_info* liveInfo)
604{
605	BAutolock _(this);
606
607	NodeMap::iterator found = fNodeMap.find(node.node);
608	if (found == fNodeMap.end()) {
609		ERROR("NodeManager::GetLiveNodeInfo: node %" B_PRId32 " not found\n",
610			node.node);
611		return B_ERROR;
612	}
613
614	registered_node& registeredNode = found->second;
615
616	ASSERT(node.port == registeredNode.port);
617	ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
618		== (registeredNode.kinds & NODE_KIND_COMPARE_MASK));
619
620	liveInfo->node = node;
621	liveInfo->hint_point = BPoint(0, 0);
622	strlcpy(liveInfo->name, registeredNode.name, sizeof(liveInfo->name));
623
624	TRACE("NodeManager::GetLiveNodeInfo node %" B_PRId32 ", name = \"%s\"\n",
625		node.node, registeredNode.name);
626	return B_OK;
627}
628
629
630status_t
631NodeManager::GetInstances(media_addon_id addOnID, int32 flavorID,
632	media_node_id* ids, int32* _count, int32 maxCount)
633{
634	BAutolock _(this);
635
636	NodeMap::iterator iterator = fNodeMap.begin();
637	int32 count = 0;
638	for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
639		registered_node& node = iterator->second;
640
641		if (node.add_on_id == addOnID && node.flavor_id == flavorID)
642			ids[count++] = node.node_id;
643	}
644
645	TRACE("NodeManager::GetInstances found %" B_PRId32 " instances for "
646		"addon_id %" B_PRId32 ", flavor_id %" B_PRId32 "\n", count, addOnID,
647		flavorID);
648	*_count = count;
649	return B_OK;
650}
651
652
653status_t
654NodeManager::GetLiveNodes(LiveNodeList& liveNodes, int32 maxCount,
655	const media_format* inputFormat, const media_format* outputFormat,
656	const char* name, uint64 requireKinds)
657{
658	TRACE("NodeManager::GetLiveNodes: maxCount %" B_PRId32 ", in-format %p, "
659		"out-format %p, name %s, require kinds 0x%" B_PRIx64 "\n", maxCount,
660		inputFormat, outputFormat, name != NULL ? name : "NULL", requireKinds);
661
662	BAutolock _(this);
663
664	// Determine the count of byte to compare when checking for a name with
665	// or without wildcard
666	size_t nameLength = 0;
667	if (name != NULL) {
668		nameLength = strlen(name);
669		if (nameLength > 0 && name[nameLength - 1] == '*')
670			nameLength--;
671	}
672
673	NodeMap::iterator iterator = fNodeMap.begin();
674	int32 count = 0;
675	for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
676		registered_node& node = iterator->second;
677
678		if ((node.kinds & requireKinds) != requireKinds)
679			continue;
680
681		if (nameLength != 0) {
682			if (strncmp(name, node.name, nameLength) != 0)
683				continue;
684		}
685
686		if (inputFormat != NULL) {
687			bool found = false;
688
689			for (InputList::iterator inIterator = node.input_list.begin();
690					inIterator != node.input_list.end(); inIterator++) {
691				media_input& input = *inIterator;
692
693				if (format_is_compatible(*inputFormat, input.format)) {
694					found = true;
695					break;
696				}
697			}
698
699			if (!found)
700				continue;
701		}
702
703		if (outputFormat != NULL) {
704			bool found = false;
705
706			for (OutputList::iterator outIterator = node.output_list.begin();
707					outIterator != node.output_list.end(); outIterator++) {
708				media_output& output = *outIterator;
709
710				if (format_is_compatible(*outputFormat, output.format)) {
711					found = true;
712					break;
713				}
714			}
715
716			if (!found)
717				continue;
718		}
719
720		live_node_info info;
721		info.node.node = node.node_id;
722		info.node.port = node.port;
723		info.node.kind = node.kinds;
724		info.hint_point = BPoint(0, 0);
725		strlcpy(info.name, node.name, sizeof(info.name));
726
727		try {
728			liveNodes.push_back(info);
729		} catch (std::bad_alloc& exception) {
730			return B_NO_MEMORY;
731		}
732
733		count++;
734	}
735
736	TRACE("NodeManager::GetLiveNodes found %" B_PRId32 "\n", count);
737	return B_OK;
738}
739
740
741/*!	Add media_node_id of all live nodes to the message
742	int32 "media_node_id" (multiple items)
743*/
744status_t
745NodeManager::GetLiveNodes(BMessage* message)
746{
747	BAutolock _(this);
748
749	NodeMap::iterator iterator = fNodeMap.begin();
750	for (; iterator != fNodeMap.end(); iterator++) {
751		registered_node& node = iterator->second;
752
753		if (message->AddInt32("media_node_id", node.node_id) != B_OK)
754			return B_NO_MEMORY;
755	}
756
757	return B_OK;
758}
759
760
761// #pragma mark - Registration of BMediaAddOns
762
763
764void
765NodeManager::RegisterAddOn(const entry_ref& ref, media_addon_id* _newID)
766{
767	BAutolock _(this);
768
769	media_addon_id id = fNextAddOnID++;
770
771//	printf("NodeManager::RegisterAddOn: ref-name \"%s\", assigning id %"
772//		B_PRId32 "\n", ref.name, id);
773
774	try {
775		fPathMap.insert(std::make_pair(id, ref));
776		*_newID = id;
777	} catch (std::bad_alloc& exception) {
778		*_newID = -1;
779	}
780}
781
782
783void
784NodeManager::UnregisterAddOn(media_addon_id addOnID)
785{
786	PRINT(1, "NodeManager::UnregisterAddOn: id %" B_PRId32 "\n", addOnID);
787
788	BAutolock _(this);
789
790	RemoveDormantFlavorInfo(addOnID);
791	fPathMap.erase(addOnID);
792}
793
794
795status_t
796NodeManager::GetAddOnRef(media_addon_id addOnID, entry_ref* ref)
797{
798	BAutolock _(this);
799
800	PathMap::iterator found = fPathMap.find(addOnID);
801	if (found == fPathMap.end())
802		return B_ERROR;
803
804	*ref = found->second;
805	return B_OK;
806}
807
808
809// #pragma mark - Registration of node flavors, published by BMediaAddOns
810
811
812//!	This function is only used (indirectly) by the media_addon_server.
813status_t
814NodeManager::AddDormantFlavorInfo(const dormant_flavor_info& flavorInfo)
815{
816	PRINT(1, "NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
817		"flavor-id %" B_PRId32 ", name \"%s\", flavor-name \"%s\", flavor-info"
818		" \"%s\"\n", flavorInfo.node_info.addon,
819		flavorInfo.node_info.flavor_id, flavorInfo.node_info.name,
820		flavorInfo.name, flavorInfo.info);
821
822	BAutolock _(this);
823
824	// Try to find the addon-id/flavor-id in the list.
825	// If it already exists, update the info, but don't change its instance
826	// count.
827
828	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
829			iterator != fDormantFlavors.end(); iterator++) {
830		dormant_add_on_flavor_info& info = *iterator;
831
832		if (info.add_on_id != flavorInfo.node_info.addon
833			|| info.flavor_id != flavorInfo.node_info.flavor_id)
834			continue;
835
836		if (info.info_valid) {
837			ERROR("NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
838				"flavor-id %" B_PRId32 " does already exist\n",
839				info.info.node_info.addon, info.info.node_info.flavor_id);
840		}
841
842		TRACE("NodeManager::AddDormantFlavorInfo, updating addon-id %" B_PRId32
843			", flavor-id %" B_PRId32 "\n", info.info.node_info.addon,
844			info.info.node_info.flavor_id);
845
846		info.max_instances_count = flavorInfo.possible_count > 0
847			? flavorInfo.possible_count : INT32_MAX;
848		info.info_valid = true;
849		info.info = flavorInfo;
850		return B_OK;
851	}
852
853	// Insert information into the list
854
855	dormant_add_on_flavor_info info;
856	info.add_on_id = flavorInfo.node_info.addon;
857	info.flavor_id = flavorInfo.node_info.flavor_id;
858	info.max_instances_count = flavorInfo.possible_count > 0
859		? flavorInfo.possible_count : INT32_MAX;
860	info.instances_count = 0;
861	info.info_valid = true;
862	info.info = flavorInfo;
863
864	try {
865		fDormantFlavors.push_back(info);
866	} catch (std::bad_alloc& exception) {
867		return B_NO_MEMORY;
868	}
869
870	return B_OK;
871}
872
873
874//!	This function is only used (indirectly) by the media_addon_server
875void
876NodeManager::InvalidateDormantFlavorInfo(media_addon_id addOnID)
877{
878	BAutolock _(this);
879
880	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
881			iterator != fDormantFlavors.end(); iterator++) {
882		dormant_add_on_flavor_info& info = *iterator;
883
884		if (info.add_on_id == addOnID && info.info_valid) {
885			PRINT(1, "NodeManager::InvalidateDormantFlavorInfo, addon-id %"
886				B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
887				"\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
888				info.info.node_info.flavor_id, info.info.node_info.name,
889				info.info.name, info.info.info);
890
891			info.info_valid = false;
892		}
893	}
894}
895
896
897//!	This function is only used (indirectly) by the media_addon_server
898void
899NodeManager::RemoveDormantFlavorInfo(media_addon_id addOnID)
900{
901	BAutolock _(this);
902
903	for (size_t index = 0; index < fDormantFlavors.size(); index++) {
904		dormant_add_on_flavor_info& info = fDormantFlavors[index];
905
906		if (info.add_on_id == addOnID) {
907			PRINT(1, "NodeManager::RemoveDormantFlavorInfo, addon-id %"
908				B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
909				"\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
910				info.info.node_info.flavor_id, info.info.node_info.name,
911				info.info.name, info.info.info);
912			fDormantFlavors.erase(fDormantFlavors.begin() + index--);
913		}
914	}
915}
916
917
918status_t
919NodeManager::IncrementFlavorInstancesCount(media_addon_id addOnID,
920	int32 flavorID, team_id team)
921{
922	BAutolock _(this);
923
924	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
925			iterator != fDormantFlavors.end(); iterator++) {
926		dormant_add_on_flavor_info& info = *iterator;
927
928		if (info.add_on_id != addOnID || info.flavor_id != flavorID)
929			continue;
930
931		if (info.instances_count >= info.max_instances_count) {
932			// maximum (or more) instances already exist
933			ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %"
934				B_PRId32 ", flavor-id %" B_PRId32 " maximum (or more) "
935				"instances already exist\n", addOnID, flavorID);
936			return B_ERROR;
937		}
938
939		TeamCountMap::iterator teamInstance
940			= info.team_instances_count.find(team);
941		if (teamInstance == info.team_instances_count.end()) {
942			// This is the team's first instance
943			try {
944				info.team_instances_count.insert(std::make_pair(team, 1));
945			} catch (std::bad_alloc& exception) {
946				return B_NO_MEMORY;
947			}
948		} else {
949			// Just increase its ref count
950			teamInstance->second++;
951		}
952
953		info.instances_count++;
954		return B_OK;
955	}
956
957	ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %" B_PRId32 ", "
958		"flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
959	return B_ERROR;
960}
961
962
963status_t
964NodeManager::DecrementFlavorInstancesCount(media_addon_id addOnID,
965	int32 flavorID, team_id team)
966{
967	BAutolock _(this);
968
969	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
970			iterator != fDormantFlavors.end(); iterator++) {
971		dormant_add_on_flavor_info& info = *iterator;
972
973		if (info.add_on_id != addOnID || info.flavor_id != flavorID)
974			continue;
975
976		TeamCountMap::iterator teamInstance
977			= info.team_instances_count.find(team);
978		if (teamInstance == info.team_instances_count.end()) {
979			ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %"
980				B_PRId32 ", flavor-id %" B_PRId32 " team %" B_PRId32 " has no "
981				"references\n", addOnID, flavorID, team);
982			return B_ERROR;
983		}
984		if (--teamInstance->second == 0)
985			info.team_instances_count.erase(teamInstance);
986
987		info.instances_count--;
988		return B_OK;
989	}
990
991	ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %" B_PRId32 ", "
992		"flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
993	return B_ERROR;
994}
995
996
997//!	This function is called when the media_addon_server has crashed
998void
999NodeManager::CleanupDormantFlavorInfos()
1000{
1001	PRINT(1, "NodeManager::CleanupDormantFlavorInfos\n");
1002
1003	BAutolock _(this);
1004
1005	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1006			iterator != fDormantFlavors.end(); iterator++) {
1007		dormant_add_on_flavor_info& info = *iterator;
1008
1009		// Current instance count is zero since the media_addon_server crashed.
1010		BPrivate::media::notifications::FlavorsChanged(info.add_on_id,
1011			0, info.instances_count);
1012	}
1013
1014	fDormantFlavors.clear();
1015
1016	PRINT(1, "NodeManager::CleanupDormantFlavorInfos done\n");
1017}
1018
1019
1020status_t
1021NodeManager::GetDormantNodes(dormant_node_info* infos, int32* _count,
1022	const media_format* input, const media_format* output, const char* name,
1023	uint64 requireKinds, uint64 denyKinds)
1024{
1025	BAutolock _(this);
1026
1027	// Determine the count of byte to compare when checking for a name with
1028	// or without wildcard
1029	size_t nameLength = 0;
1030	if (name != NULL) {
1031		nameLength = strlen(name);
1032		if (nameLength > 0 && name[nameLength - 1] == '*')
1033			nameLength--;
1034	}
1035
1036	int32 maxCount = *_count;
1037	int32 count = 0;
1038
1039	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1040			iterator != fDormantFlavors.end() && count < maxCount; iterator++) {
1041		dormant_add_on_flavor_info& info = *iterator;
1042
1043		if (!info.info_valid)
1044			continue;
1045
1046		if ((info.info.kinds & requireKinds) != requireKinds
1047			|| (info.info.kinds & denyKinds) != 0)
1048			continue;
1049
1050		if (nameLength != 0) {
1051			if (strncmp(name, info.info.name, nameLength) != 0)
1052				continue;
1053		}
1054
1055		if (input != NULL) {
1056			bool found = false;
1057
1058			for (int32 i = 0; i < info.info.in_format_count; i++) {
1059				if (format_is_compatible(*input, info.info.in_formats[i])) {
1060					found = true;
1061					break;
1062				}
1063			}
1064
1065			if (!found)
1066				continue;
1067		}
1068
1069		if (output != NULL) {
1070			bool found = false;
1071
1072			for (int32 i = 0; i < info.info.out_format_count; i++) {
1073				if (format_is_compatible(*output, info.info.out_formats[i])) {
1074					found = true;
1075					break;
1076				}
1077			}
1078
1079			if (!found)
1080				continue;
1081		}
1082
1083		infos[count++] = info.info.node_info;
1084	}
1085
1086	*_count = count;
1087	return B_OK;
1088}
1089
1090
1091status_t
1092NodeManager::GetDormantFlavorInfoFor(media_addon_id addOnID, int32 flavorID,
1093	dormant_flavor_info* flavorInfo)
1094{
1095	BAutolock _(this);
1096
1097	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1098			iterator != fDormantFlavors.end(); iterator++) {
1099		dormant_add_on_flavor_info& info = *iterator;
1100
1101		if (info.add_on_id == addOnID && info.flavor_id == flavorID
1102			&& info.info_valid) {
1103			*flavorInfo = info.info;
1104			return B_OK;
1105		}
1106	}
1107
1108	return B_ERROR;
1109}
1110
1111
1112// #pragma mark - Misc.
1113
1114
1115status_t
1116NodeManager::SetNodeTimeSource(media_node_id node,
1117	media_node_id timesource)
1118{
1119	BAutolock _(this);
1120
1121	NodeMap::iterator found = fNodeMap.find(node);
1122	if (found == fNodeMap.end()) {
1123		ERROR("NodeManager::SetNodeTimeSource: node %"
1124			B_PRId32 " not found\n", node);
1125		return B_ERROR;
1126	}
1127	registered_node& registeredNode = found->second;
1128	registeredNode.timesource_id = timesource;
1129	return B_OK;
1130}
1131
1132
1133void
1134NodeManager::CleanupTeam(team_id team)
1135{
1136	BAutolock _(this);
1137
1138	fDefaultManager->CleanupTeam(team);
1139
1140	PRINT(1, "NodeManager::CleanupTeam: team %" B_PRId32 "\n", team);
1141
1142	// Cleanup node references
1143
1144	for (NodeMap::iterator iterator = fNodeMap.begin();
1145			iterator != fNodeMap.end();) {
1146		registered_node& node = iterator->second;
1147		NodeMap::iterator remove = iterator++;
1148
1149		// If the gone team was the creator of some global dormant node
1150		// instance, we now invalidate that we may want to remove that
1151		// global node, but I'm not sure
1152		if (node.creator == team) {
1153			node.creator = -1;
1154			// fall through
1155		}
1156
1157		// If the team hosting this node is gone, remove node from database
1158		if (node.containing_team == team) {
1159			PRINT(1, "NodeManager::CleanupTeam: removing node id %" B_PRId32
1160				", team %" B_PRId32 "\n", node.node_id, team);
1161			// Ensure the slave node is removed from it's timesource
1162			_NotifyTimeSource(node);
1163			fNodeMap.erase(remove);
1164			BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
1165			continue;
1166		}
1167
1168		// Check the list of teams that have references to this node, and
1169		// remove the team
1170		TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
1171		if (teamRef != node.team_ref_count.end()) {
1172			PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " refs "
1173				"from node id %" B_PRId32 ", team %" B_PRId32 "\n",
1174				teamRef->second, node.node_id, team);
1175			node.ref_count -= teamRef->second;
1176			if (node.ref_count == 0) {
1177				PRINT(1, "NodeManager::CleanupTeam: removing node id %"
1178					B_PRId32 " that has no teams\n", node.node_id);
1179
1180				// Ensure the slave node is removed from it's timesource
1181				_NotifyTimeSource(node);
1182				fNodeMap.erase(remove);
1183				BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
1184			} else
1185				node.team_ref_count.erase(teamRef);
1186		}
1187	}
1188
1189	// Cleanup add-on references
1190
1191	for (size_t index = 0; index < fDormantFlavors.size(); index++) {
1192		dormant_add_on_flavor_info& flavorInfo = fDormantFlavors[index];
1193
1194		TeamCountMap::iterator instanceCount
1195			= flavorInfo.team_instances_count.find(team);
1196		if (instanceCount != flavorInfo.team_instances_count.end()) {
1197			PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " "
1198				"instances from addon %" B_PRId32 ", flavor %" B_PRId32 "\n",
1199				instanceCount->second, flavorInfo.add_on_id,
1200				flavorInfo.flavor_id);
1201
1202			int32 count = flavorInfo.instances_count;
1203			flavorInfo.instances_count -= instanceCount->second;
1204			if (flavorInfo.instances_count <= 0) {
1205				fDormantFlavors.erase(fDormantFlavors.begin() + index--);
1206				BPrivate::media::notifications::FlavorsChanged(
1207					flavorInfo.add_on_id, 0, count);
1208			} else
1209				flavorInfo.team_instances_count.erase(team);
1210		}
1211	}
1212}
1213
1214
1215status_t
1216NodeManager::LoadState()
1217{
1218	BAutolock _(this);
1219	return fDefaultManager->LoadState();
1220}
1221
1222status_t
1223NodeManager::SaveState()
1224{
1225	BAutolock _(this);
1226	return fDefaultManager->SaveState(this);
1227}
1228
1229
1230void
1231NodeManager::Dump()
1232{
1233	BAutolock _(this);
1234
1235	// for each addon-id, the add-on path map contains an entry_ref
1236
1237	printf("\nNodeManager: addon path map follows:\n");
1238
1239	for (PathMap::iterator iterator = fPathMap.begin();
1240			iterator != fPathMap.end(); iterator++) {
1241		BPath path(&iterator->second);
1242		printf(" addon-id %" B_PRId32 ", path \"%s\"\n", iterator->first,
1243			path.InitCheck() == B_OK ? path.Path() : "INVALID");
1244	}
1245
1246	printf("NodeManager: list end\n\n");
1247
1248	// for each node-id, the registered node map contians information about
1249	// source of the node, users, etc.
1250
1251	printf("NodeManager: registered nodes map follows:\n");
1252	for (NodeMap::iterator iterator = fNodeMap.begin();
1253			iterator != fNodeMap.end(); iterator++) {
1254		registered_node& node = iterator->second;
1255
1256		printf("  node-id %" B_PRId32 ", addon-id %" B_PRId32 ", addon-flavor-"
1257			"id %" B_PRId32 ", port %" B_PRId32 ", creator %" B_PRId32 ", "
1258			"team %" B_PRId32 ", kinds %#08" B_PRIx64 ", name \"%s\", "
1259			"ref_count %" B_PRId32 "\n", node.node_id, node.add_on_id,
1260			node.flavor_id, node.port, node.creator, node.containing_team,
1261			node.kinds, node.name, node.ref_count);
1262
1263		printf("    teams (refcount): ");
1264		for (TeamCountMap::iterator refsIterator = node.team_ref_count.begin();
1265				refsIterator != node.team_ref_count.end(); refsIterator++) {
1266			printf("%" B_PRId32 " (%" B_PRId32 "), ", refsIterator->first,
1267				refsIterator->second);
1268		}
1269		printf("\n");
1270
1271		for (InputList::iterator inIterator = node.input_list.begin();
1272				inIterator != node.input_list.end(); inIterator++) {
1273			media_input& input = *inIterator;
1274			printf("    media_input: node-id %" B_PRId32 ", node-port %"
1275				B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
1276				", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
1277				"\"%s\"\n", input.node.node, input.node.port, input.source.port,
1278				input.source.id, input.destination.port, input.destination.id,
1279				input.name);
1280		}
1281		if (node.input_list.empty())
1282			printf("    media_input: none\n");
1283
1284		for (OutputList::iterator outIterator = node.output_list.begin();
1285				outIterator != node.output_list.end(); outIterator++) {
1286			media_output& output = *outIterator;
1287			printf("    media_output: node-id %" B_PRId32 ", node-port %"
1288				B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
1289				", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
1290				"\"%s\"\n", output.node.node, output.node.port,
1291				output.source.port, output.source.id, output.destination.port,
1292				output.destination.id, output.name);
1293		}
1294		if (node.output_list.empty())
1295			printf("    media_output: none\n");
1296	}
1297
1298	printf("NodeManager: list end\n");
1299	printf("\n");
1300
1301	// Dormant add-on flavors
1302
1303	printf("NodeManager: dormant flavor list follows:\n");
1304
1305	for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
1306			iterator != fDormantFlavors.end(); iterator++) {
1307		dormant_add_on_flavor_info& flavorInfo = *iterator;
1308
1309		printf("  addon-id %" B_PRId32 ", flavor-id %" B_PRId32 ", max "
1310			"instances count %" B_PRId32 ", instances count %" B_PRId32 ", "
1311			"info valid %s\n", flavorInfo.add_on_id, flavorInfo.flavor_id,
1312			flavorInfo.max_instances_count, flavorInfo.instances_count,
1313			flavorInfo.info_valid ? "yes" : "no");
1314		printf("    teams (instances): ");
1315		for (TeamCountMap::iterator countIterator
1316					= flavorInfo.team_instances_count.begin();
1317				countIterator != flavorInfo.team_instances_count.end();
1318				countIterator++) {
1319			printf("%" B_PRId32 " (%" B_PRId32 "), ", countIterator->first,
1320				countIterator->second);
1321		}
1322		printf("\n");
1323		if (!flavorInfo.info_valid)
1324			continue;
1325
1326		printf("    addon-id %" B_PRId32 ", addon-flavor-id %" B_PRId32 ", "
1327			"addon-name \"%s\"\n", flavorInfo.info.node_info.addon,
1328			flavorInfo.info.node_info.flavor_id,
1329			flavorInfo.info.node_info.name);
1330		printf("    flavor-kinds %#08" B_PRIx64 ", flavor_flags %#08" B_PRIx32
1331			", internal_id %" B_PRId32 ", possible_count %" B_PRId32 ", "
1332			"in_format_count %" B_PRId32 ", out_format_count %" B_PRId32 "\n",
1333			flavorInfo.info.kinds, flavorInfo.info.flavor_flags,
1334			flavorInfo.info.internal_id, flavorInfo.info.possible_count,
1335			flavorInfo.info.in_format_count, flavorInfo.info.out_format_count);
1336		printf("    flavor-name \"%s\"\n", flavorInfo.info.name);
1337		printf("    flavor-info \"%s\"\n", flavorInfo.info.info);
1338	}
1339	printf("NodeManager: list end\n");
1340
1341	fDefaultManager->Dump();
1342}
1343
1344
1345// #pragma mark - private methods
1346
1347
1348status_t
1349NodeManager::_AcquireNodeReference(media_node_id id, team_id team)
1350{
1351	TRACE("NodeManager::_AcquireNodeReference enter: node %" B_PRId32 ", team "
1352		"%" B_PRId32 "\n", id, team);
1353
1354	BAutolock _(this);
1355
1356	NodeMap::iterator found = fNodeMap.find(id);
1357	if (found == fNodeMap.end()) {
1358		ERROR("NodeManager::_AcquireNodeReference: node %" B_PRId32 " not "
1359			"found\n", id);
1360		return B_ERROR;
1361	}
1362
1363	registered_node& node = found->second;
1364
1365	TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
1366	if (teamRef == node.team_ref_count.end()) {
1367		// This is the team's first reference
1368		try {
1369			node.team_ref_count.insert(std::make_pair(team, 1));
1370		} catch (std::bad_alloc& exception) {
1371			return B_NO_MEMORY;
1372		}
1373	} else {
1374		// Just increase its ref count
1375		teamRef->second++;
1376	}
1377
1378	node.ref_count++;
1379
1380	TRACE("NodeManager::_AcquireNodeReference leave: node %" B_PRId32 ", team "
1381		"%" B_PRId32 ", ref %" B_PRId32 ", team ref %" B_PRId32 "\n", id, team,
1382		node.ref_count, node.team_ref_count.find(team)->second);
1383	return B_OK;
1384}
1385
1386
1387void
1388NodeManager::_NotifyTimeSource(registered_node& node)
1389{
1390	team_id team = be_app->Team();
1391	media_node timeSource;
1392	// Ensure the timesource ensure still exists
1393	if (GetCloneForID(node.timesource_id, team, &timeSource) != B_OK)
1394		return;
1395
1396	media_node currentNode;
1397	if (GetCloneForID(node.node_id, team,
1398		&currentNode) == B_OK) {
1399		timesource_remove_slave_node_command cmd;
1400		cmd.node = currentNode;
1401		// Notify slave node removal to owner timesource
1402		SendToPort(timeSource.port, TIMESOURCE_REMOVE_SLAVE_NODE,
1403			&cmd, sizeof(cmd));
1404		ReleaseNode(timeSource, team);
1405	}
1406	ReleaseNode(currentNode, team);
1407}
1408