⛏️ index : haiku.git

/*
 * 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.
 */


// RouteAppNodeManager.cpp

#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"

// Locale Kit
#include <Catalog.h>

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CortexRouteApp"

using namespace std;

__USE_CORTEX_NAMESPACE

#define D_METHOD(x) //PRINT (x)
#define D_HOOK(x) //PRINT (x)
#define D_SETTINGS(x) //PRINT (x)

// -------------------------------------------------------- //
// *** ctor/dtor
// -------------------------------------------------------- //

RouteAppNodeManager::~RouteAppNodeManager() {

	_freeIcons();
}

RouteAppNodeManager::RouteAppNodeManager(
	bool													useAddOnHost) :
	NodeManager(useAddOnHost),
	m_nextGroupNumber(1) {
	
	// pre-cache icons? +++++
	
}

// -------------------------------------------------------- //
// *** group management
// -------------------------------------------------------- //

// -------------------------------------------------------- //
// *** icon management
// -------------------------------------------------------- //

// fetch cached icon for the given live node; the MediaIcon
// instance is guaranteed to last as long as this object.
// Returns 0 if the node doesn't exist.

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()) {
		// already cached
		return (*it).second;
	}

	// look up live_node_info
	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()) {
		// already cached
		return (*it).second;
	}

	// create & cache icon
	MediaIcon* icon = new MediaIcon(
		nodeInfo, iconSize);

	m_iconMap.insert(
		icon_map::value_type(key, icon));

	return icon;
}

// -------------------------------------------------------- //
// *** error handling
// -------------------------------------------------------- //

status_t RouteAppNodeManager::setLogTarget(
	const BMessenger&							target) {
	
	BAutolock _l(this);
	
	if(!target.IsValid())
		return B_BAD_VALUE;
	
	m_logTarget = target;
	return B_OK;
}

// -------------------------------------------------------- //
// NodeManager hook implementations
// -------------------------------------------------------- //

void RouteAppNodeManager::nodeCreated(
	NodeRef*											ref) {

	// prepare the log message
	BMessage logMsg(M_LOG);
	BString title = B_TRANSLATE("Node '%name%' created");
	title.ReplaceFirst("%name%", ref->name());
	logMsg.AddString("title", title);

	// create a default group for the node
	// [em 8feb00]
	NodeGroup* g = createGroup(ref->name());

	if(ref->kind() & B_TIME_SOURCE) {
		// notify observers
		BMessage m(M_TIME_SOURCE_CREATED);
		m.AddInt32("nodeID", ref->id());
		notify(&m);
	}

	// adopt node's time source if it's not the system clock (the default)
	// [em 20mar00]
	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) {

	// prepare the log message
	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) {
		// notify observers
		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;

	// prepare the log message
	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))
		// don't react to connection Cortex didn't make
		return;
	
	// create or merge groups
	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;	
	}

	// add node names to log messages
	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);

	// add format to log message
	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))
		{
			// merge into consumers group
			group = consumer->group();
			mergeGroups(producer->group(), group);
		}
		else if (producer->group() &&
			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
		{ // add consumer to producers group
			group = producer->group();
			group->addNode(consumer);
		}
		else if (consumer->group() &&
			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
		{ // add producer to consumers group
			group = consumer->group();
			group->addNode(producer);
		}
		else
		{ // make new group for both
			groupName << m_nextGroupNumber++;
			group = createGroup(groupName.String());
			group->addNode(producer);
			group->addNode(consumer);
		}
	}
	else if(_canGroup(producer) && !producer->group())
	{ // make new group for producer
		groupName << m_nextGroupNumber++;
		group = createGroup(groupName.String());
		group->addNode(producer);
	}
	else if(_canGroup(consumer) && !consumer->group())
	{ // make new group for consumer
		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"));
		
	// prepare the log message
	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))
		// don't react to connection Cortex didn't make
		return;

	status_t err;
	
	// if the source and destination nodes belong to the same group,
	// and if no direct or indirect connection remains between the
	// source and destination nodes, split groups

	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;	
	}

	// add node names to log messages
	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;
		
	// prepare the log message
	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;	
	}

	// add node names to log messages
	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);

	// add format to log message
	line = B_TRANSLATE("Tried format:");
	logMsg.AddString("line", line);
	line = "    ";
	line << MediaString::getStringFor(format, true);
	logMsg.AddString("line", line);

	// and send it
	m_logTarget.SendMessage(&logMsg);
}

// -------------------------------------------------------- //
// *** IPersistent
// -------------------------------------------------------- //

void RouteAppNodeManager::xmlExportBegin(
	ExportContext&								context) const {

	status_t err;
	
	try {
		NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
		context.beginElement(_NODE_SET_ELEMENT);
		
		// validate the node set
		for(int n = set.countNodes()-1; n >= 0; --n) {
			media_node_id id = set.nodeAt(n);
			ASSERT(id != media_node::null.node);
			
			// fetch 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;
			}
			// skip unless internal
			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();
	
		// write nodes; enumerate connections
		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);
			
			// fetch 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;
			}

			// fetch connections
			vector<Connection> conSet;
			ref->getInputConnections(conSet);
			ref->getOutputConnections(conSet);
			for(uint32 c = 0; c < conSet.size(); ++c)
				// non-unique connections will be rejected:
				connections.insert(
					connection_map::value_type(conSet[c].id(), conSet[c]));

			// create an IO object for the node & write it
			DormantNodeIO io(ref, nodeSet.keyAt(n));
			if(context.writeObject(&io) < B_OK)
				// abort
				return;
		}		

		// write connections
		for(connection_map::const_iterator it = connections.begin();
			it != connections.end(); ++it) {
		
			ConnectionIO io(
				&(*it).second, 
				this,
				&nodeSet);
			if(context.writeObject(&io) < B_OK)
				// abort
				return;
				
		}
	
		// +++++ write groups
	
		// write UI state
		{
			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();
}

// -------------------------------------------------------- //
// IMPORT	
// -------------------------------------------------------- //

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) {
			// created node; add an entry to the set stored in the
			// ImportContext for later reference
			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);
		
		// instantiate the connection
		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)));
		}
		
		// +++++ group magic?

	}
	else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) {
		// +++++
	}
	else if(
		context.parentElement() &&
		!strcmp(context.parentElement(), _UI_STATE_ELEMENT)) {
	
		// expect a nested message
		MessageIO* io = dynamic_cast<MessageIO*>(child);
		if(!io) {
			BString err;
			err <<
				"RouteAppNodeManager: unexpected child '" <<
				context.element() << "'\n";
			context.reportError(err.String());
		}
		
		// hand it off via the extended context object
		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) {

}

// -------------------------------------------------------- //

/*static*/
void RouteAppNodeManager::AddTo(
	XML::DocumentType*				docType) {

	// set up the document type

	MessageIO::AddTo(docType);
	MediaFormatIO::AddTo(docType);
	ConnectionIO::AddTo(docType);
	DormantNodeIO::AddTo(docType);
	LiveNodeIO::AddTo(docType);
	_add_string_elements(docType);
}

// -------------------------------------------------------- //
// implementation
// -------------------------------------------------------- //

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 {

	// sanity check & easy cases
	ASSERT(ref);
	if(ref->isInternal())
		return true;

	// bar 'touchy' system nodes
	if(ref == audioMixerNode() || ref == audioOutputNode())
		return false;
		
	return true;
}

// END -- RouteAppNodeManager.cpp --