Switch to side-by-side view

--- a/upmpd/upmpd.cxx
+++ b/upmpd/upmpd.cxx
@@ -35,1069 +35,34 @@
 #include "libupnpp/device.hxx"
 #include "libupnpp/log.hxx"
 
+#include "upmpd.hxx"
 #include "mpdcli.hxx"
 #include "upmpdutils.hxx"
+#include "rendering.hxx"
+#include "avtransport.hxx"
+#include "conman.hxx"
 
 static const string dfltFriendlyName("UpMpd");
-
-// The UPnP MPD frontend device with its 3 services
-class UpMpd : public UpnpDevice {
-public:
-	enum Options {
-		upmpdNone,
-		upmpdOwnQueue, // The MPD belongs to us, we shall clear it as we like
-	};
-	UpMpd(const string& deviceid, const unordered_map<string, string>& xmlfiles,
-		  MPDCli *mpdcli, Options opts = upmpdNone);
-
-	// RenderingControl
-	int setMute(const SoapArgs& sc, SoapData& data);
-	int getMute(const SoapArgs& sc, SoapData& data);
-	int setVolume(const SoapArgs& sc, SoapData& data, bool isDb);
-	int getVolume(const SoapArgs& sc, SoapData& data, bool isDb);
-	int listPresets(const SoapArgs& sc, SoapData& data);
-	int selectPreset(const SoapArgs& sc, SoapData& data);
-//	int getVolumeDBRange(const SoapArgs& sc, SoapData& data);
-    virtual bool getEventDataRendering(bool all, 
-									   std::vector<std::string>& names, 
-									   std::vector<std::string>& values);
-
-	// AVTransport
-	int setAVTransportURI(const SoapArgs& sc, SoapData& data, bool setnext);
-	int getPositionInfo(const SoapArgs& sc, SoapData& data);
-	int getTransportInfo(const SoapArgs& sc, SoapData& data);
-	int getMediaInfo(const SoapArgs& sc, SoapData& data);
-	int getDeviceCapabilities(const SoapArgs& sc, SoapData& data);
-	int setPlayMode(const SoapArgs& sc, SoapData& data);
-	int getTransportSettings(const SoapArgs& sc, SoapData& data);
-	int getCurrentTransportActions(const SoapArgs& sc, SoapData& data);
-	int playcontrol(const SoapArgs& sc, SoapData& data, int what);
-	int seek(const SoapArgs& sc, SoapData& data);
-	int seqcontrol(const SoapArgs& sc, SoapData& data, int what);
-    virtual bool getEventDataTransport(bool all, 
-									   std::vector<std::string>& names, 
-									   std::vector<std::string>& values);
-
-	// Connection Manager
-	int getCurrentConnectionIDs(const SoapArgs& sc, SoapData& data);
-	int getCurrentConnectionInfo(const SoapArgs& sc, SoapData& data);
-	int getProtocolInfo(const SoapArgs& sc, SoapData& data);
-	virtual bool getEventDataCM(bool all, std::vector<std::string>& names, 
-								std::vector<std::string>& values);
-
-	// Re-implemented from the base class and shared by all services
-    virtual bool getEventData(bool all, const string& serviceid, 
-							  std::vector<std::string>& names, 
-							  std::vector<std::string>& values);
-
-private:
-	MPDCli *m_mpdcli;
-
-	string m_curMetadata;
-	string m_nextUri;
-	string m_nextMetadata;
-
-	// State variable storage
-	unordered_map<string, string> m_rdstate;
-	unordered_map<string, string> m_tpstate;
-
-	// Translate MPD state to Renderer state variables.
-	bool rdstateMToU(unordered_map<string, string>& state);
-	// Translate MPD state to AVTransport state variables.
-	bool tpstateMToU(unordered_map<string, string>& state);
-
-	// My track identifiers (for cleaning up)
-	set<int> m_songids;
-
-	// Desired volume target. We may delay executing small volume
-	// changes to avoid saturating with small requests.
-	int m_desiredvolume;
-
-	int m_options;
-};
-
-static const string serviceIdRender("urn:upnp-org:serviceId:RenderingControl");
-static const string serviceIdTransport("urn:upnp-org:serviceId:AVTransport");
-static const string serviceIdCM("urn:upnp-org:serviceId:ConnectionManager");
 
 // Note: if we ever need this to work without cxx11, there is this:
 // http://www.tutok.sk/fastgl/callback.html
 UpMpd::UpMpd(const string& deviceid, 
 			 const unordered_map<string, string>& xmlfiles,
 			 MPDCli *mpdcli, Options opts)
-	: UpnpDevice(deviceid, xmlfiles), m_mpdcli(mpdcli), m_desiredvolume(-1),
+	: UpnpDevice(deviceid, xmlfiles), m_mpdcli(mpdcli), 
 	  m_options(opts)
 {
-	addServiceType(serviceIdRender,
-				   "urn:schemas-upnp-org:service:RenderingControl:1");
-	addActionMapping("SetMute", bind(&UpMpd::setMute, this, _1, _2));
-	addActionMapping("GetMute", bind(&UpMpd::getMute, this, _1, _2));
-	addActionMapping("SetVolume", bind(&UpMpd::setVolume, this, _1, _2, false));
-	addActionMapping("GetVolume", bind(&UpMpd::getVolume, this, _1, _2, false));
-	addActionMapping("ListPresets", bind(&UpMpd::listPresets, this, _1, _2));
-	addActionMapping("SelectPreset", bind(&UpMpd::selectPreset, this, _1, _2));
-
-	addServiceType(serviceIdTransport,
-				   "urn:schemas-upnp-org:service:AVTransport:1");
-	addActionMapping("SetAVTransportURI", 
-					 bind(&UpMpd::setAVTransportURI, this, _1, _2, false));
-	addActionMapping("SetNextAVTransportURI", 
-					 bind(&UpMpd::setAVTransportURI, this, _1, _2, true));
-	addActionMapping("GetPositionInfo", 
-					 bind(&UpMpd::getPositionInfo, this, _1, _2));
-	addActionMapping("GetTransportInfo", 
-					 bind(&UpMpd::getTransportInfo, this, _1, _2));
-	addActionMapping("GetMediaInfo", 
-					 bind(&UpMpd::getMediaInfo, this, _1, _2));
-	addActionMapping("GetDeviceCapabilities", 
-					 bind(&UpMpd::getDeviceCapabilities, this, _1, _2));
-	addActionMapping("SetPlayMode", bind(&UpMpd::setPlayMode, this, _1, _2));
-	addActionMapping("GetTransportSettings", 
-					 bind(&UpMpd::getTransportSettings, this, _1, _2));
-	addActionMapping("GetCurrentTransportActions", 
-					 bind(&UpMpd::getCurrentTransportActions, this, _1, _2));
-	addActionMapping("Stop", bind(&UpMpd::playcontrol, this, _1, _2, 0));
-	addActionMapping("Play", bind(&UpMpd::playcontrol, this, _1, _2, 1));
-	addActionMapping("Pause", bind(&UpMpd::playcontrol, this, _1, _2, 2));
-	addActionMapping("Seek", bind(&UpMpd::seek, this, _1, _2));
-	addActionMapping("Next", bind(&UpMpd::seqcontrol, this, _1, _2, 0));
-	addActionMapping("Previous", bind(&UpMpd::seqcontrol, this, _1, _2, 1));
-
-	addServiceType(serviceIdCM,
-				   "urn:schemas-upnp-org:service:ConnectionManager:1");
-	addActionMapping("GetCurrentConnectionIDs", 
-					 bind(&UpMpd::getCurrentConnectionIDs, this, _1, _2));
-	addActionMapping("GetCurrentConnectionInfo", 
-					 bind(&UpMpd::getCurrentConnectionInfo, this, _1, _2));
-	addActionMapping("GetProtocolInfo", 
-					 bind(&UpMpd::getProtocolInfo, this, _1, _2));
+	m_services.push_back(new UpMpdRenderCtl(this));
+	m_services.push_back(new UpMpdAVTransport(this));
+	m_services.push_back(new UpMpdConMan(this));
 }
 
-// This is called by the polling loop at regular intervals, or when
-// triggered, to retrieve changed state variables for each of the
-// services (the list of services was defined in the base class by the
-// "addServiceTypes()" calls during construction).
-//
-// We might add a method for triggering an event from the action
-// methods after changing state, which would really act only if the
-// interval with the previous event is long enough. But things seem to
-// work ok with the systematic delay.
-bool UpMpd::getEventData(bool all, const string& serviceid, 
-						 std::vector<std::string>& names, 
-						 std::vector<std::string>& values)
+UpMpd::~UpMpd()
 {
-	if (!serviceid.compare(serviceIdRender)) {
-		return getEventDataRendering(all, names, values);
-	} else if (!serviceid.compare(serviceIdTransport)) {
-		return getEventDataTransport(all, names, values);
-	} else if (!serviceid.compare(serviceIdCM)) {
-		return getEventDataCM(all, names, values);
-	} else {
-		LOGERR("UpMpd::getEventData: servid? [" << serviceid << "]" << endl);
-		return UPNP_E_INVALID_PARAM;
-	}
-}
-
-////////////////////////////////////////////////////
-/// RenderingControl methods
-
-// State variables for the RenderingControl. All evented through LastChange
-//  PresetNameList
-//  Mute
-//  Volume
-//  VolumeDB
-// LastChange contains all the variables that were changed since the last
-// event. For us that's at most Mute, Volume, VolumeDB
-// <Event xmlns=���urn:schemas-upnp-org:metadata-1-0/AVT_RCS">
-//   <InstanceID val=���0���>
-//     <Mute channel=���Master��� val=���0���/>
-//     <Volume channel=���Master��� val=���24���/>
-//     <VolumeDB channel=���Master��� val=���24���/>
-//   </InstanceID>
-// </Event>
-
-bool UpMpd::rdstateMToU(unordered_map<string, string>& status)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-
-	int volume = m_desiredvolume >= 0 ? m_desiredvolume : mpds.volume;
-	if (volume < 0)
-		volume = 0;
-	char cvalue[30];
-	sprintf(cvalue, "%d", volume);
-	status["Volume"] = cvalue;
-//	sprintf(cvalue, "%d", percentodbvalue(volume));
-//	status["VolumeDB"] =  cvalue;
-	status["Mute"] =  volume == 0 ? "1" : "0";
-	return true;
-}
-
-bool UpMpd::getEventDataRendering(bool all, std::vector<std::string>& names, 
-								  std::vector<std::string>& values)
-{
-	//LOGDEB("UpMpd::getEventDataRendering. desiredvolume " << 
-	//		   m_desiredvolume << (all?" all " : "") << endl);
-	if (m_desiredvolume >= 0) {
-		m_mpdcli->setVolume(m_desiredvolume);
-		m_desiredvolume = -1;
-	}
-
-	unordered_map<string, string> newstate;
-	rdstateMToU(newstate);
-	if (all)
-		m_rdstate.clear();
-
-	string 
-		chgdata("<Event xmlns=\"urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">\n"
-				"<InstanceID val=\"0\">\n");
-
-	bool changefound = false;
-	for (unordered_map<string, string>::const_iterator it = newstate.begin();
-		 it != newstate.end(); it++) {
-
-		const string& oldvalue = mapget(m_rdstate, it->first);
-		if (!it->second.compare(oldvalue))
-			continue;
-
-		changefound = true;
-
-		chgdata += "<";
-		chgdata += it->first;
-		chgdata += " val=\"";
-		chgdata += xmlquote(it->second);
-		chgdata += "\"/>\n";
-	}
-	chgdata += "</InstanceID>\n</Event>\n";
-
-	if (!changefound) {
-		return true;
-	}
-
-	names.push_back("LastChange");
-	values.push_back(chgdata);
-
-	m_rdstate = newstate;
-
-	return true;
-}
-
-// Actions:
-// Note: we need to return all out arguments defined by the SOAP call even if
-// they don't make sense (because there is no song playing). Ref upnp arch p.51:
-//
-//   argumentName: Required if and only if action has out
-//   arguments. Value returned from action. Repeat once for each out
-//   argument. If action has an argument marked as retval, this
-//   argument must be the first element. (Element name not qualified
-//   by a namespace; element nesting context is sufficient.) Case
-//   sensitive. Single data type as defined by UPnP service
-//   description. Every ���out��� argument in the definition of the action
-//   in the service description must be included, in the same order as
-//   specified in the service description (SCPD) available from the
-//   device.
-
-#if 0
-int UpMpd::getVolumeDBRange(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-
-	it = sc.args.find("Channel");
-	if (it == sc.args.end() || it->second.compare("Master")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	data.addarg("MinValue", "-10240");
-	data.addarg("MaxValue", "0");
-
-	return UPNP_E_SUCCESS;
-}
-#endif
-int UpMpd::setMute(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-
-	it = sc.args.find("Channel");
-	if (it == sc.args.end() || it->second.compare("Master")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-		
-	it = sc.args.find("DesiredMute");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	if (it->second[0] == 'F' || it->second[0] == '0') {
-		// Restore pre-mute
-		m_mpdcli->setVolume(1, true);
-	} else if (it->second[0] == 'T' || it->second[0] == '1') {
-		if (m_desiredvolume >= 0) {
-			m_mpdcli->setVolume(m_desiredvolume);
-			m_desiredvolume = -1;
-		}
-		m_mpdcli->setVolume(0, true);
-	} else {
-		return UPNP_E_INVALID_PARAM;
-	}
-	loopWakeup();
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getMute(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-
-	it = sc.args.find("Channel");
-	if (it == sc.args.end() || it->second.compare("Master")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	int volume = m_mpdcli->getVolume();
-	data.addarg("CurrentMute", volume == 0 ? "1" : "0");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::setVolume(const SoapArgs& sc, SoapData& data, bool isDb)
-{
-	map<string, string>::const_iterator it;
-
-	it = sc.args.find("Channel");
-	if (it == sc.args.end() || it->second.compare("Master")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-		
-	it = sc.args.find("DesiredVolume");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	int volume = atoi(it->second.c_str());
-	if (isDb) {
-		volume = dbvaluetopercent(volume);
-	} 
-	if (volume < 0 || volume > 100) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	
-	int previous_volume = m_mpdcli->getVolume();
-	int delta = previous_volume - volume;
-	if (delta < 0)
-		delta = -delta;
-	LOGDEB("UpMpd::setVolume: volume " << volume << " delta " << delta << endl);
-	if (delta >= 5) {
-		m_mpdcli->setVolume(volume);
-		m_desiredvolume = -1;
-	} else {
-		m_desiredvolume = volume;
-	}
-
-	loopWakeup();
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getVolume(const SoapArgs& sc, SoapData& data, bool isDb)
-{
-	// LOGDEB("UpMpd::getVolume" << endl);
-	map<string, string>::const_iterator it;
-
-	it = sc.args.find("Channel");
-	if (it == sc.args.end() || it->second.compare("Master")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-		
-	int volume = m_mpdcli->getVolume();
-	if (isDb) {
-		volume = percentodbvalue(volume);
-	}
-	char svolume[30];
-	sprintf(svolume, "%d", volume);
-	data.addarg("CurrentVolume", svolume);
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::listPresets(const SoapArgs& sc, SoapData& data)
-{
-	// The 2nd arg is a comma-separated list of preset names
-	data.addarg("CurrentPresetNameList", "FactoryDefaults");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::selectPreset(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-		
-	it = sc.args.find("PresetName");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	if (it->second.compare("FactoryDefaults")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-
-	// Well there is only the volume actually...
-	int volume = 50;
-	m_mpdcli->setVolume(volume);
-
-	return UPNP_E_SUCCESS;
-}
-
-///////////////// AVTransport methods
-
-// Translate MPD mode flags to UPnP Play mode
-static string mpdsToPlaymode(const MpdStatus& mpds)
-{
-	string playmode = "NORMAL";
-    if (!mpds.rept && mpds.random && !mpds.single)
-		playmode = "SHUFFLE";
-	else if (mpds.rept && !mpds.random && mpds.single)
-		playmode = "REPEAT_ONE";
-	else if (mpds.rept && !mpds.random && !mpds.single)
-		playmode = "REPEAT_ALL";
-	else if (mpds.rept && mpds.random && !mpds.single)
-		playmode = "RANDOM";
-	else if (!mpds.rept && !mpds.random && mpds.single)
-		playmode = "DIRECT_1";
-	return playmode;
-}
-
-// AVTransport eventing
-// 
-// Some state variables do not generate events and must be polled by
-// the control point: RelativeTimePosition AbsoluteTimePosition
-// RelativeCounterPosition AbsoluteCounterPosition.
-// This leaves us with:
-//    TransportState
-//    TransportStatus
-//    PlaybackStorageMedium
-//    PossiblePlaybackStorageMedia
-//    RecordStorageMedium
-//    PossibleRecordStorageMedia
-//    CurrentPlayMode
-//    TransportPlaySpeed
-//    RecordMediumWriteStatus
-//    CurrentRecordQualityMode
-//    PossibleRecordQualityModes
-//    NumberOfTracks
-//    CurrentTrack
-//    CurrentTrackDuration
-//    CurrentMediaDuration
-//    CurrentTrackMetaData
-//    CurrentTrackURI
-//    AVTransportURI
-//    AVTransportURIMetaData
-//    NextAVTransportURI
-//    NextAVTransportURIMetaData
-//    RelativeTimePosition
-//    AbsoluteTimePosition
-//    RelativeCounterPosition
-//    AbsoluteCounterPosition
-//    CurrentTransportActions
-//
-// To be all bundled inside:    LastChange
-
-// Translate MPD state to UPnP AVTRansport state variables
-bool UpMpd::tpstateMToU(unordered_map<string, string>& status)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	//DEBOUT << "UpMpd::tpstateMToU: curpos: " << mpds.songpos <<
-	//   " qlen " << mpds.qlen << endl;
-	bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
-		(mpds.state == MpdStatus::MPDS_PAUSE);
-
-	string tstate("STOPPED");
-	string tactions("Next,Previous");
-	switch(mpds.state) {
-	case MpdStatus::MPDS_PLAY: 
-		tstate = "PLAYING"; 
-		tactions += ",Pause,Stop,Seek";
-		break;
-	case MpdStatus::MPDS_PAUSE: 
-		tstate = "PAUSED_PLAYBACK"; 
-		tactions += ",Play,Stop,Seek";
-		break;
-	default:
-		tactions += ",Play";
-	}
-	status["TransportState"] = tstate;
-	status["CurrentTransportActions"] = tactions;
-	status["TransportStatus"] = m_mpdcli->ok() ? "OK" : "ERROR_OCCURRED";
-	status["TransportPlaySpeed"] = "1";
-
-	const string& uri = mapget(mpds.currentsong, "uri");
-	status["CurrentTrack"] = "1";
-	status["CurrentTrackURI"] = uri;
-
-	// If we own the queue, just use the metadata from the content directory.
-	// else, try to make up something from mpd status.
-	if ((m_options & upmpdOwnQueue)) {
-		status["CurrentTrackMetaData"] = is_song ? m_curMetadata : "";
-	} else {
-		status["CurrentTrackMetaData"] = is_song ? didlmake(mpds) : "";
-	}
-
-	string playmedium("NONE");
-	if (is_song)
-		playmedium = uri.find("http://") == 0 ?	"HDD" : "NETWORK";
-	status["NumberOfTracks"] = "1";
-	status["CurrentMediaDuration"] = is_song?
-		upnpduration(mpds.songlenms):"00:00:00";
-	status["CurrentTrackDuration"] = is_song?
-		upnpduration(mpds.songlenms):"00:00:00";
-	status["AVTransportURI"] = uri;
-	if ((m_options & upmpdOwnQueue)) {
-		status["AVTransportURIMetaData"] = is_song ? m_curMetadata : "";
-	} else {
-		status["AVTransportURIMetaData"] = is_song ? didlmake(mpds) : "";
-	}
-	status["RelativeTimePosition"] = is_song?
-		upnpduration(mpds.songelapsedms):"0:00:00";
-	status["AbsoluteTimePosition"] = is_song?
-		upnpduration(mpds.songelapsedms) : "0:00:00";
-
-	status["NextAVTransportURI"] = mapget(mpds.nextsong, "uri");
-	if ((m_options & upmpdOwnQueue)) {
-		status["NextAVTransportURIMetaData"] = is_song ? m_nextMetadata : "";
-	} else {
-		status["NextAVTransportURIMetaData"] = is_song?didlmake(mpds, true) :"";
-	}
-
-	status["PlaybackStorageMedium"] = playmedium;
-	status["PossiblePlaybackStorageMedium"] = "HDD,NETWORK";
-	status["RecordStorageMedium"] = "NOT_IMPLEMENTED";
-	status["RelativeCounterPosition"] = "0";
-	status["AbsoluteCounterPosition"] = "0";
-	status["CurrentPlayMode"] = mpdsToPlaymode(mpds);
-
-	status["PossibleRecordStorageMedium"] = "NOT_IMPLEMENTED";
-	status["RecordMediumWriteStatus"] = "NOT_IMPLEMENTED";
-	status["CurrentRecordQualityMode"] = "NOT_IMPLEMENTED";
-	status["PossibleRecordQualityModes"] = "NOT_IMPLEMENTED";
-	return true;
-}
-
-bool UpMpd::getEventDataTransport(bool all, std::vector<std::string>& names, 
-								  std::vector<std::string>& values)
-{
-	unordered_map<string, string> newtpstate;
-	tpstateMToU(newtpstate);
-	if (all)
-		m_tpstate.clear();
-
-	bool changefound = false;
-
-	string 
-		chgdata("<Event xmlns=\"urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">\n"
-				"<InstanceID val=\"0\">\n");
-	for (unordered_map<string, string>::const_iterator it = newtpstate.begin();
-		 it != newtpstate.end(); it++) {
-
-		const string& oldvalue = mapget(m_tpstate, it->first);
-		if (!it->second.compare(oldvalue))
-			continue;
-
-		if (it->first.compare("RelativeTimePosition") && 
-			it->first.compare("AbsoluteTimePosition")) {
-			//DEBOUT << "Transport state update for " << it->first << 
-			// " oldvalue [" << oldvalue << "] -> [" << it->second << endl;
-			changefound = true;
-		}
-
-		chgdata += "<";
-		chgdata += it->first;
-		chgdata += " val=\"";
-		chgdata += xmlquote(it->second);
-		chgdata += "\"/>\n";
-	}
-	chgdata += "</InstanceID>\n</Event>\n";
-
-	if (!changefound) {
-		// DEBOUT << "UpMpd::getEventDataTransport: no updates" << endl;
-		return true;
-	}
-
-	names.push_back("LastChange");
-	values.push_back(chgdata);
-
-	m_tpstate = newtpstate;
-	// DEBOUT << "UpMpd::getEventDataTransport: " << chgdata << endl;
-	return true;
-}
-
-// http://192.168.4.4:8200/MediaItems/246.mp3
-int UpMpd::setAVTransportURI(const SoapArgs& sc, SoapData& data, bool setnext)
-{
-	map<string, string>::const_iterator it;
-		
-	it = setnext? sc.args.find("NextURI") : sc.args.find("CurrentURI");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	string uri = it->second;
-	string metadata;
-	it = setnext? sc.args.find("NextURIMetaData") : 
-		sc.args.find("CurrentURIMetaData");
-	if (it != sc.args.end())
-		metadata = it->second;
-	//cerr << "SetTransport: setnext " << setnext << " metadata[" << metadata <<
-	// "]" << endl;
-
-	if ((m_options & upmpdOwnQueue) && !setnext) {
-		// If we own the queue, just clear it before setting the
-		// track.  Else it's difficult to impossible to prevent it
-		// from growing if upmpdcli restarts. If the option is not set, the
-		// user prefers to live with the issue.
-		m_mpdcli->clearQueue();
-	}
-
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
-		(mpds.state == MpdStatus::MPDS_PAUSE);
-	int curpos = mpds.songpos;
-	LOGDEB("UpMpd::set" << (setnext?"Next":"") << 
-		   "AVTransportURI: curpos: " <<
-		   curpos << " is_song " << is_song << " qlen " << mpds.qlen << endl);
-
-	// curpos == -1 means that the playlist was cleared or we just started. A
-	// play will use position 0, so it's actually equivalent to curpos == 0
-	if (curpos == -1) {
-		curpos = 0;
-	}
-
-	if (mpds.qlen == 0 && setnext) {
-		LOGDEB("setNextAVTRansportURI invoked but empty queue!" << endl);
-		return UPNP_E_INVALID_PARAM;
-	}
-	int songid;
-	if ((songid = m_mpdcli->insert(uri, setnext?curpos+1:curpos)) < 0) {
-		return UPNP_E_INTERNAL_ERROR;
-	}
-
-	metadata = regsub1("<\\?xml.*\\?>", metadata, "");
-	if (setnext) {
-		m_nextUri = uri;
-		m_nextMetadata = metadata;
-	} else {
-		m_curMetadata = metadata;
-		m_nextUri = "";
-		m_nextMetadata = "";
-	}
-
-	if (!setnext) {
-		MpdStatus::State st = mpds.state;
-		// Have to tell mpd which track to play, else it will keep on
-		// the previous despite of the insertion. The UPnP docs say
-		// that setAVTransportURI should not change the transport
-		// state (pause/stop stay pause/stop) but it seems that some clients
-		// expect that the track will start playing.
-		// Needs to be revisited after seeing more clients. For now try to 
-		// preserve state as per standard.
-		// Audionet: issues a Play
-		// BubbleUpnp: issues a Play
-		// MediaHouse: no setnext, Play
-		m_mpdcli->play(curpos);
-#if 1 || defined(upmpd_do_restore_play_state_after_add)
-		switch (st) {
-		case MpdStatus::MPDS_PAUSE: m_mpdcli->togglePause(); break;
-		case MpdStatus::MPDS_STOP: m_mpdcli->stop(); break;
-		default: break;
-		}
-#endif
-		// Clean up old song ids
-		if (!(m_options & upmpdOwnQueue)) {
-			for (set<int>::iterator it = m_songids.begin();
-				 it != m_songids.end(); it++) {
-				// Can't just delete here. If the id does not exist, MPD 
-				// gets into an apparently permanent error state, where even 
-				// get_status does not work
-				if (m_mpdcli->statId(*it)) {
-					m_mpdcli->deleteId(*it);
-				}
-			}
-			m_songids.clear();
-		}
-	}
-
-	if (!(m_options & upmpdOwnQueue)) {
-		m_songids.insert(songid);
-	}
-
-	loopWakeup();
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getPositionInfo(const SoapArgs& sc, SoapData& data)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	//LOGDEB("UpMpd::getPositionInfo. State: " << mpds.state << endl);
-
-	bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
-		(mpds.state == MpdStatus::MPDS_PAUSE);
-
-	if (is_song) {
-		data.addarg("Track", "1");
-	} else {
-		data.addarg("Track", "0");
-	}
-
-	if (is_song) {
-		data.addarg("TrackDuration", upnpduration(mpds.songlenms));
-	} else {
-		data.addarg("TrackDuration", "00:00:00");
-	}
-
-	if (is_song) {
-		if ((m_options & upmpdOwnQueue)) {
-			data.addarg("TrackMetaData", m_curMetadata);
-		} else {
-			data.addarg("TrackMetaData", didlmake(mpds));
-		}
-	} else {
-		data.addarg("TrackMetaData", "");
-	}
-
-	const string& uri = mapget(mpds.currentsong, "uri");
-	if (is_song && !uri.empty()) {
-		data.addarg("TrackURI", xmlquote(uri));
-	} else {
-		data.addarg("TrackURI", "");
-	}
-	if (is_song) {
-		data.addarg("RelTime", upnpduration(mpds.songelapsedms));
-	} else {
-		data.addarg("RelTime", "0:00:00");
-	}
-
-	if (is_song) {
-		data.addarg("AbsTime", upnpduration(mpds.songelapsedms));
-	} else {
-		data.addarg("AbsTime", "0:00:00");
-	}
-
-	data.addarg("RelCount", "0");
-	data.addarg("AbsCount", "0");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getTransportInfo(const SoapArgs& sc, SoapData& data)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	//LOGDEB("UpMpd::getTransportInfo. State: " << mpds.state << endl);
-
-	string tstate("STOPPED");
-	switch(mpds.state) {
-	case MpdStatus::MPDS_PLAY: tstate = "PLAYING"; break;
-	case MpdStatus::MPDS_PAUSE: tstate = "PAUSED_PLAYBACK"; break;
-	default: break;
-	}
-	data.addarg("CurrentTransportState", tstate);
-	data.addarg("CurrentTransportStatus", m_mpdcli->ok() ? "OK" : 
-				"ERROR_OCCURRED");
-	data.addarg("CurrentSpeed", "1");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getDeviceCapabilities(const SoapArgs& sc, SoapData& data)
-{
-	data.addarg("PlayMedia", "NETWORK,HDD");
-	data.addarg("RecMedia", "NOT_IMPLEMENTED");
-	data.addarg("RecQualityModes", "NOT_IMPLEMENTED");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getMediaInfo(const SoapArgs& sc, SoapData& data)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	LOGDEB("UpMpd::getMediaInfo. State: " << mpds.state << endl);
-
-	bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
-		(mpds.state == MpdStatus::MPDS_PAUSE);
-
-	data.addarg("NrTracks", "1");
-	if (is_song) {
-		data.addarg("MediaDuration", upnpduration(mpds.songlenms));
-	} else {
-		data.addarg("MediaDuration", "00:00:00");
-	}
-
-	const string& thisuri = mapget(mpds.currentsong, "uri");
-	if (is_song && !thisuri.empty()) {
-		data.addarg("CurrentURI", xmlquote(thisuri));
-	} else {
-		data.addarg("CurrentURI", "");
-	}
-	if (is_song) {
-		if ((m_options & upmpdOwnQueue)) {
-			data.addarg("CurrentURIMetaData", m_curMetadata);
-		} else {
-			data.addarg("CurrentURIMetaData", didlmake(mpds));
-		}
-	} else {
-		data.addarg("CurrentURIMetaData", "");
-	}
-	if ((m_options & upmpdOwnQueue)) {
-		data.addarg("NextURI", m_nextUri);
-		data.addarg("NextURIMetaData", is_song ? m_nextMetadata : "");
-	} else {
-		data.addarg("NextURI", mapget(mpds.nextsong, "uri"));
-		data.addarg("NextURIMetaData", is_song ? didlmake(mpds, true) : "");
-	}
-	string playmedium("NONE");
-	if (is_song)
-		playmedium = thisuri.find("http://") == 0 ?	"HDD" : "NETWORK";
-	data.addarg("PlayMedium", playmedium);
-
-	data.addarg("RecordMedium", "NOT_IMPLEMENTED");
-	data.addarg("WriteStatus", "NOT_IMPLEMENTED");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::playcontrol(const SoapArgs& sc, SoapData& data, int what)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	LOGDEB("UpMpd::playcontrol State: " << mpds.state <<" what "<<what<< endl);
-
-	if ((what & ~0x3)) {
-		LOGERR("UpMPd::playcontrol: bad control " << what << endl);
-		return UPNP_E_INVALID_PARAM;
-	}
-
-	bool ok = true;
-	switch (mpds.state) {
-	case MpdStatus::MPDS_PLAY: 
-		switch (what) {
-		case 0:	ok = m_mpdcli->stop(); break;
-		case 1: ok = m_mpdcli->play();break;
-		case 2: ok = m_mpdcli->togglePause();break;
-		}
-		break;
-	case MpdStatus::MPDS_PAUSE:
-		switch (what) {
-		case 0:	ok = m_mpdcli->stop(); break;
-		case 1: ok = m_mpdcli->togglePause();break;
-		case 2: break;
-		}
-		break;
-	case MpdStatus::MPDS_STOP:
-	default:
-		switch (what) {
-		case 0:	break;
-		case 1: ok = m_mpdcli->play();break;
-		case 2: break;
-		}
-		break;
-	}
-	
-	loopWakeup();
-	return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
-}
-
-int UpMpd::seqcontrol(const SoapArgs& sc, SoapData& data, int what)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	LOGDEB("UpMpd::seqcontrol State: " << mpds.state << " what "<<what<< endl);
-
-	if ((what & ~0x1)) {
-		LOGERR("UpMPd::seqcontrol: bad control " << what << endl);
-		return UPNP_E_INVALID_PARAM;
-	}
-
-	bool ok = true;
-	switch (what) {
-	case 0: ok = m_mpdcli->next();break;
-	case 1: ok = m_mpdcli->previous();break;
-	}
-
-	loopWakeup();
-	return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
-}
-	
-int UpMpd::setPlayMode(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-		
-	it = sc.args.find("NewPlayMode");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	string playmode(it->second);
-	bool ok;
-	if (!playmode.compare("NORMAL")) {
-		ok = m_mpdcli->repeat(false) && m_mpdcli->random(false) &&
-			m_mpdcli->single(false);
-	} else if (!playmode.compare("SHUFFLE")) {
-		ok = m_mpdcli->repeat(false) && m_mpdcli->random(true) &&
-			m_mpdcli->single(false);
-	} else if (!playmode.compare("REPEAT_ONE")) {
-		ok = m_mpdcli->repeat(true) && m_mpdcli->random(false) &&
-			m_mpdcli->single(true);
-	} else if (!playmode.compare("REPEAT_ALL")) {
-		ok = m_mpdcli->repeat(true) && m_mpdcli->random(false) &&
-			m_mpdcli->single(false);
-	} else if (!playmode.compare("RANDOM")) {
-		ok = m_mpdcli->repeat(true) && m_mpdcli->random(true) &&
-			m_mpdcli->single(false);
-	} else if (!playmode.compare("DIRECT_1")) {
-		ok = m_mpdcli->repeat(false) && m_mpdcli->random(false) &&
-			m_mpdcli->single(true);
-	} else {
-		return UPNP_E_INVALID_PARAM;
-	}
-	loopWakeup();
-	return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
-}
-
-int UpMpd::getTransportSettings(const SoapArgs& sc, SoapData& data)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	string playmode = mpdsToPlaymode(mpds);
-	data.addarg("PlayMode", playmode);
-	data.addarg("RecQualityMode", "NOT_IMPLEMENTED");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getCurrentTransportActions(const SoapArgs& sc, SoapData& data)
-{
-	const MpdStatus &mpds = m_mpdcli->getStatus();
-	string tactions("Next,Previous");
-	switch(mpds.state) {
-	case MpdStatus::MPDS_PLAY: 
-		tactions += ",Pause,Stop,Seek";
-		break;
-	case MpdStatus::MPDS_PAUSE: 
-		tactions += ",Play,Stop,Seek";
-		break;
-	default:
-		tactions += ",Play";
-	}
-	data.addarg("CurrentTransportActions", tactions);
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::seek(const SoapArgs& sc, SoapData& data)
-{
-	map<string, string>::const_iterator it;
-		
-	it = sc.args.find("Unit");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	string unit(it->second);
-
-	it = sc.args.find("Target");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	string target(it->second);
-
-	//LOGDEB("UpMpd::seek: unit " << unit << " target " << target << 
-	//	   " current posisition " << mpds.songelapsedms / 1000 << 
-	//	   " seconds" << endl);
-
-	int abs_seconds;
-	// Note that ABS_TIME and REL_TIME don't mean what you'd think
-	// they mean.  REL_TIME means relative to the current track,
-	// ABS_TIME to the whole media (ie for a multitrack tape). So
-	// take both ABS and REL as absolute position in the current song
- 	if (!unit.compare("REL_TIME") || !unit.compare("ABS_TIME")) {
-		abs_seconds = upnpdurationtos(target);
-	} else {
-		return UPNP_E_INVALID_PARAM;
-	}
-	LOGDEB("UpMpd::seek: seeking to " << abs_seconds << " seconds (" <<
-		   upnpduration(abs_seconds * 1000) << ")" << endl);
-
-	loopWakeup();
-	return m_mpdcli->seek(abs_seconds) ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
-}
-
-///////////////// ConnectionManager methods
-
-// "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3,"
-// "http-get:*:audio/L16:DLNA.ORG_PN=LPCM,"
-// "http-get:*:audio/x-flac:DLNA.ORG_PN=FLAC"
-static const string 
-myProtocolInfo(
-	"http-get:*:audio/wav:*,"
-	"http-get:*:audio/wave:*,"
-	"http-get:*:audio/x-wav:*,"
-	"http-get:*:audio/x-aiff:*,"
-	"http-get:*:audio/mpeg:*,"
-	"http-get:*:audio/x-mpeg:*,"
-	"http-get:*:audio/mp1:*,"
-	"http-get:*:audio/aac:*,"
-	"http-get:*:audio/flac:*,"
-	"http-get:*:audio/x-flac:*,"
-	"http-get:*:audio/m4a:*,"
-	"http-get:*:audio/mp4:*,"
-	"http-get:*:audio/x-m4a:*,"
-	"http-get:*:audio/vorbis:*,"
-	"http-get:*:audio/ogg:*,"
-	"http-get:*:audio/x-ogg:*,"
-	"http-get:*:audio/x-scpls:*,"
-	"http-get:*:audio/L16;rate=11025;channels=1:*,"
-	"http-get:*:audio/L16;rate=22050;channels=1:*,"
-	"http-get:*:audio/L16;rate=44100;channels=1:*,"
-	"http-get:*:audio/L16;rate=48000;channels=1:*,"
-	"http-get:*:audio/L16;rate=88200;channels=1:*,"
-	"http-get:*:audio/L16;rate=96000;channels=1:*,"
-	"http-get:*:audio/L16;rate=176400;channels=1:*,"
-	"http-get:*:audio/L16;rate=192000;channels=1:*,"
-	"http-get:*:audio/L16;rate=11025;channels=2:*,"
-	"http-get:*:audio/L16;rate=22050;channels=2:*,"
-	"http-get:*:audio/L16;rate=44100;channels=2:*,"
-	"http-get:*:audio/L16;rate=48000;channels=2:*,"
-	"http-get:*:audio/L16;rate=88200;channels=2:*,"
-	"http-get:*:audio/L16;rate=96000;channels=2:*,"
-	"http-get:*:audio/L16;rate=176400;channels=2:*,"
-	"http-get:*:audio/L16;rate=192000;channels=2:*"
-	);
-
-bool UpMpd::getEventDataCM(bool all, std::vector<std::string>& names, 
-						   std::vector<std::string>& values)
-{
-	//LOGDEB("UpMpd:getEventDataCM" << endl);
-
-	// Our data never changes, so if this is not an unconditional request,
-	// we return nothing.
-	if (all) {
-		names.push_back("SinkProtocolInfo");
-		values.push_back(myProtocolInfo);
-	}
-	return true;
-}
-
-int UpMpd::getCurrentConnectionIDs(const SoapArgs& sc, SoapData& data)
-{
-	LOGDEB("UpMpd:getCurrentConnectionIDs" << endl);
-	data.addarg("ConnectionIDs", "0");
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getCurrentConnectionInfo(const SoapArgs& sc, SoapData& data)
-{
-	LOGDEB("UpMpd:getCurrentConnectionInfo" << endl);
-	map<string, string>::const_iterator it;
-	it = sc.args.find("ConnectionID");
-	if (it == sc.args.end() || it->second.empty()) {
-		return UPNP_E_INVALID_PARAM;
-	}
-	if (it->second.compare("0")) {
-		return UPNP_E_INVALID_PARAM;
-	}
-
-	data.addarg("RcsID", "0");
-	data.addarg("AVTransportID", "0");
-	data.addarg("ProtocolInfo", "");
-	data.addarg("PeerConnectionManager", "");
-	data.addarg("PeerConnectionID", "-1");
-	data.addarg("Direction", "Input");
-	data.addarg("Status", "Unknown");
-
-	return UPNP_E_SUCCESS;
-}
-
-int UpMpd::getProtocolInfo(const SoapArgs& sc, SoapData& data)
-{
-	LOGDEB("UpMpd:getProtocolInfo" << endl);
-	data.addarg("Source", "");
-	data.addarg("Sink", myProtocolInfo);
-
-	return UPNP_E_SUCCESS;
+	for (vector<UpnpService*>::iterator it = m_services.begin();
+		 it != m_services.end(); it++) {
+		delete(*it);
+	}
 }
 
 /////////////////////////////////////////////////////////////////////