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