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