* Copyright (c) 1999-2000, Eric Moon.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "RouteAppNodeManager.h"
#include "MediaIcon.h"
#include "NodeGroup.h"
#include "NodeRef.h"
#include "Connection.h"
#include "route_app_io.h"
#include "ConnectionIO.h"
#include "DormantNodeIO.h"
#include "LiveNodeIO.h"
#include "MediaFormatIO.h"
#include "MessageIO.h"
#include "NodeSetIOContext.h"
#include "StringContent.h"
#include "MediaString.h"
#include <Autolock.h>
#include <Debug.h>
#include <Entry.h>
#include <Path.h>
#include <TimeSource.h>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <typeinfo>
#include "set_tools.h"
#include <Catalog.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CortexRouteApp"
using namespace std;
__USE_CORTEX_NAMESPACE
#define D_METHOD(x)
#define D_HOOK(x)
#define D_SETTINGS(x)
RouteAppNodeManager::~RouteAppNodeManager() {
_freeIcons();
}
RouteAppNodeManager::RouteAppNodeManager(
bool useAddOnHost) :
NodeManager(useAddOnHost),
m_nextGroupNumber(1) {
}
const MediaIcon* RouteAppNodeManager::mediaIconFor(
media_node_id nodeID,
icon_size iconSize) {
BAutolock _l(this);
uint64 key = _makeIconKey(nodeID, iconSize);
icon_map::const_iterator it = m_iconMap.find(key);
if(it != m_iconMap.end()) {
return (*it).second;
}
NodeRef* ref;
status_t err = getNodeRef(nodeID, &ref);
if(err < B_OK)
return 0;
return mediaIconFor(ref->nodeInfo(), iconSize);
}
const MediaIcon* RouteAppNodeManager::mediaIconFor(
live_node_info nodeInfo,
icon_size iconSize) {
uint64 key = _makeIconKey(nodeInfo.node.node, iconSize);
icon_map::const_iterator it = m_iconMap.find(key);
if(it != m_iconMap.end()) {
return (*it).second;
}
MediaIcon* icon = new MediaIcon(
nodeInfo, iconSize);
m_iconMap.insert(
icon_map::value_type(key, icon));
return icon;
}
status_t RouteAppNodeManager::setLogTarget(
const BMessenger& target) {
BAutolock _l(this);
if(!target.IsValid())
return B_BAD_VALUE;
m_logTarget = target;
return B_OK;
}
void RouteAppNodeManager::nodeCreated(
NodeRef* ref) {
BMessage logMsg(M_LOG);
BString title = B_TRANSLATE("Node '%name%' created");
title.ReplaceFirst("%name%", ref->name());
logMsg.AddString("title", title);
NodeGroup* g = createGroup(ref->name());
if(ref->kind() & B_TIME_SOURCE) {
BMessage m(M_TIME_SOURCE_CREATED);
m.AddInt32("nodeID", ref->id());
notify(&m);
}
media_node systemClock;
status_t err = roster->GetSystemTimeSource(&systemClock);
if(err == B_OK)
{
BTimeSource* ts = roster->MakeTimeSourceFor(ref->node());
if (ts == NULL)
return;
if(ts->Node() != systemClock)
{
g->setTimeSource(ts->Node());
logMsg.AddString("line", "Synced to system clock");
}
ts->Release();
}
g->addNode(ref);
m_logTarget.SendMessage(&logMsg);
}
void RouteAppNodeManager::nodeDeleted(
const NodeRef* ref) {
BMessage logMsg(M_LOG);
BString title = B_TRANSLATE("Node '%name%' released");
title.ReplaceFirst("%name%", ref->name());
logMsg.AddString("title", title);
if(ref->kind() & B_TIME_SOURCE) {
BMessage m(M_TIME_SOURCE_DELETED);
m.AddInt32("nodeID", ref->id());
notify(&m);
}
m_logTarget.SendMessage(&logMsg);
}
void RouteAppNodeManager::connectionMade(
Connection* connection) {
D_HOOK((
"@ RouteAppNodeManager::connectionMade()\n"));
status_t err;
BMessage logMsg(M_LOG);
BString title = B_TRANSLATE("Connection made");
if (strcmp(connection->outputName(), connection->inputName()) == 0) {
title = B_TRANSLATE("Connection '%name%' made");
title.ReplaceFirst("%name%", connection->outputName());
}
logMsg.AddString("title", title);
if(!(connection->flags() & Connection::INTERNAL))
return;
NodeRef *producer, *consumer;
err = getNodeRef(connection->sourceNode(), &producer);
if(err < B_OK) {
D_HOOK((
"!!! RouteAppNodeManager::connectionMade():\n"
" sourceNode (%ld) not found\n",
connection->sourceNode()));
return;
}
err = getNodeRef(connection->destinationNode(), &consumer);
if(err < B_OK) {
D_HOOK((
"!!! RouteAppNodeManager::connectionMade():\n"
" destinationNode (%ld) not found\n",
connection->destinationNode()));
return;
}
BString line = B_TRANSLATE("Between:");
logMsg.AddString("line", line);
line = " ";
line += B_TRANSLATE("%producer% and %consumer%");
line.ReplaceFirst("%producer%", producer->name());
line.ReplaceFirst("%consumer%", consumer->name());
logMsg.AddString("line", line);
line = B_TRANSLATE("Negotiated format:");
logMsg.AddString("line", line);
line = " ";
line << MediaString::getStringFor(connection->format(), false);
logMsg.AddString("line", line);
NodeGroup *group = 0;
BString groupName = B_TRANSLATE("Untitled group");
groupName += " ";
if(_canGroup(producer) && _canGroup(consumer))
{
if (producer->group() && consumer->group() &&
!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED) &&
!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
{
group = consumer->group();
mergeGroups(producer->group(), group);
}
else if (producer->group() &&
!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
{
group = producer->group();
group->addNode(consumer);
}
else if (consumer->group() &&
!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
{
group = consumer->group();
group->addNode(producer);
}
else
{
groupName << m_nextGroupNumber++;
group = createGroup(groupName.String());
group->addNode(producer);
group->addNode(consumer);
}
}
else if(_canGroup(producer) && !producer->group())
{
groupName << m_nextGroupNumber++;
group = createGroup(groupName.String());
group->addNode(producer);
}
else if(_canGroup(consumer) && !consumer->group())
{
groupName << m_nextGroupNumber++;
group = createGroup(groupName.String());
group->addNode(consumer);
}
m_logTarget.SendMessage(&logMsg);
}
void RouteAppNodeManager::connectionBroken(
const Connection* connection) {
D_HOOK((
"@ RouteAppNodeManager::connectionBroken()\n"));
BMessage logMsg(M_LOG);
BString title = B_TRANSLATE("Connection broken");
if (strcmp(connection->outputName(), connection->inputName()) == 0) {
title = B_TRANSLATE("Connection '%name%' broken");
title.ReplaceFirst("%name%", connection->outputName());
}
logMsg.AddString("title", title);
if(!(connection->flags() & Connection::INTERNAL))
return;
status_t err;
NodeRef *producer, *consumer;
err = getNodeRef(connection->sourceNode(), &producer);
if(err < B_OK) {
D_HOOK((
"!!! RouteAppNodeManager::connectionMade():\n"
" sourceNode (%ld) not found\n",
connection->sourceNode()));
return;
}
err = getNodeRef(connection->destinationNode(), &consumer);
if(err < B_OK) {
D_HOOK((
"!!! RouteAppNodeManager::connectionMade():\n"
" destinationNode (%ld) not found\n",
connection->destinationNode()));
return;
}
BString line = B_TRANSLATE("Between:");
logMsg.AddString("line", line);
line = " ";
line += B_TRANSLATE("%producer% and %consumer%");
line.ReplaceFirst("%producer%", producer->name());
line.ReplaceFirst("%consumer%", consumer->name());
logMsg.AddString("line", line);
if(
producer->group() &&
producer->group() == consumer->group() &&
!findRoute(producer->id(), consumer->id())) {
NodeGroup *newGroup;
splitGroup(producer, consumer, &newGroup);
}
m_logTarget.SendMessage(&logMsg);
}
void RouteAppNodeManager::connectionFailed(
const media_output & output,
const media_input & input,
const media_format & format,
status_t error) {
D_HOOK((
"@ RouteAppNodeManager::connectionFailed()\n"));
status_t err;
BMessage logMsg(M_LOG);
BString title = B_TRANSLATE("Connection failed");
logMsg.AddString("title", title);
logMsg.AddInt32("error", error);
NodeRef *producer, *consumer;
err = getNodeRef(output.node.node, &producer);
if(err < B_OK) {
return;
}
err = getNodeRef(input.node.node, &consumer);
if(err < B_OK) {
return;
}
BString line = B_TRANSLATE("Between:");
logMsg.AddString("line", line);
line = " ";
line += B_TRANSLATE("%producer% and %consumer%");
line.ReplaceFirst("%producer%", producer->name());
line.ReplaceFirst("%consumer%", consumer->name());
logMsg.AddString("line", line);
line = B_TRANSLATE("Tried format:");
logMsg.AddString("line", line);
line = " ";
line << MediaString::getStringFor(format, true);
logMsg.AddString("line", line);
m_logTarget.SendMessage(&logMsg);
}
void RouteAppNodeManager::xmlExportBegin(
ExportContext& context) const {
status_t err;
try {
NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
context.beginElement(_NODE_SET_ELEMENT);
for(int n = set.countNodes()-1; n >= 0; --n) {
media_node_id id = set.nodeAt(n);
ASSERT(id != media_node::null.node);
NodeRef* ref;
err = getNodeRef(id, &ref);
if(err < B_OK) {
D_SETTINGS((
"! RVNM::xmlExportBegin(): node %ld doesn't exist\n", id));
set.removeNodeAt(n);
continue;
}
if(!ref->isInternal()) {
D_SETTINGS((
"! RVNM::xmlExportBegin(): node %ld not internal; skipping.\n", id));
set.removeNodeAt(n);
continue;
}
}
}
catch(bad_cast& e) {
context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
}
}
void RouteAppNodeManager::xmlExportAttributes(
ExportContext& context) const {}
void RouteAppNodeManager::xmlExportContent(
ExportContext& context) const {
status_t err;
try {
NodeSetIOContext& nodeSet = dynamic_cast<NodeSetIOContext&>(context);
context.beginContent();
typedef map<uint32,Connection> connection_map;
connection_map connections;
int count = nodeSet.countNodes();
for(int n = 0; n < count; ++n) {
media_node_id id = nodeSet.nodeAt(n);
ASSERT(id != media_node::null.node);
NodeRef* ref;
err = getNodeRef(id, &ref);
if(err < B_OK) {
D_SETTINGS((
"! RouteAppNodeManager::xmlExportContent():\n"
" getNodeRef(%ld) failed: '%s'\n",
id, strerror(err)));
continue;
}
vector<Connection> conSet;
ref->getInputConnections(conSet);
ref->getOutputConnections(conSet);
for(uint32 c = 0; c < conSet.size(); ++c)
connections.insert(
connection_map::value_type(conSet[c].id(), conSet[c]));
DormantNodeIO io(ref, nodeSet.keyAt(n));
if(context.writeObject(&io) < B_OK)
return;
}
for(connection_map::const_iterator it = connections.begin();
it != connections.end(); ++it) {
ConnectionIO io(
&(*it).second,
this,
&nodeSet);
if(context.writeObject(&io) < B_OK)
return;
}
{
BMessage m;
nodeSet.exportUIState(&m);
context.beginElement(_UI_STATE_ELEMENT);
context.beginContent();
MessageIO io(&m);
context.writeObject(&io);
context.endElement();
}
}
catch(bad_cast& e) {
context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
}
}
void RouteAppNodeManager::xmlExportEnd(
ExportContext& context) const {
context.endElement();
}
void RouteAppNodeManager::xmlImportBegin(
ImportContext& context) {
}
void RouteAppNodeManager::xmlImportAttribute(
const char* key,
const char* value,
ImportContext& context) {}
void RouteAppNodeManager::xmlImportContent(
const char* data,
uint32 length,
ImportContext& context) {}
void RouteAppNodeManager::xmlImportChild(
IPersistent* child,
ImportContext& context) {
status_t err;
if(!strcmp(context.element(), _DORMANT_NODE_ELEMENT)) {
DormantNodeIO* io = dynamic_cast<DormantNodeIO*>(child);
ASSERT(io);
NodeRef* newRef;
err = io->instantiate(this, &newRef);
if(err == B_OK) {
try {
NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
set.addNode(newRef->id(), io->nodeKey());
}
catch(bad_cast& e) {
context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
}
}
else {
D_SETTINGS((
"!!! RouteAppNodeManager::xmlImportChild():\n"
" DormantNodeIO::instantiate() failed:\n"
" '%s'\n",
strerror(err)));
}
}
else if(!strcmp(context.element(), _CONNECTION_ELEMENT)) {
ConnectionIO* io = dynamic_cast<ConnectionIO*>(child);
ASSERT(io);
Connection con;
err = io->instantiate(
this,
dynamic_cast<NodeSetIOContext*>(&context),
&con);
if(err < B_OK) {
D_SETTINGS((
"!!! ConnectionIO::instantiate() failed:\n"
" '%s'\n", strerror(err)));
}
}
else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) {
}
else if(
context.parentElement() &&
!strcmp(context.parentElement(), _UI_STATE_ELEMENT)) {
MessageIO* io = dynamic_cast<MessageIO*>(child);
if(!io) {
BString err;
err <<
"RouteAppNodeManager: unexpected child '" <<
context.element() << "'\n";
context.reportError(err.String());
}
try {
NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
set.importUIState(io->message());
}
catch(bad_cast& e) {
context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
}
}
}
void RouteAppNodeManager::xmlImportComplete(
ImportContext& context) {
}
void RouteAppNodeManager::AddTo(
XML::DocumentType* docType) {
MessageIO::AddTo(docType);
MediaFormatIO::AddTo(docType);
ConnectionIO::AddTo(docType);
DormantNodeIO::AddTo(docType);
LiveNodeIO::AddTo(docType);
_add_string_elements(docType);
}
uint64 RouteAppNodeManager::_makeIconKey(
media_node_id nodeID, icon_size iconSize) {
return ((uint64)nodeID) << 32 | iconSize;
}
void RouteAppNodeManager::_readIconKey(
uint64 key, media_node_id& nodeID, icon_size& iconSize) {
nodeID = key >> 32;
iconSize = icon_size(key & 0xffffffff);
}
void RouteAppNodeManager::_freeIcons() {
ptr_map_delete(
m_iconMap.begin(),
m_iconMap.end());
}
bool RouteAppNodeManager::_canGroup(NodeRef* ref) const {
ASSERT(ref);
if(ref->isInternal())
return true;
if(ref == audioMixerNode() || ref == audioOutputNode())
return false;
return true;
}