Switch to side-by-side view

--- a/upmpd/upmpd.cxx
+++ b/upmpd/upmpd.cxx
@@ -38,7 +38,7 @@
 
 static const string dfltFriendlyName("UpMpd");
 
-// The UPnP MPD frontend device with its 2 services
+// The UPnP MPD frontend device with its 3 services
 class UpMpd : public UpnpDevice {
 public:
 	enum Options {
@@ -76,13 +76,23 @@
 									   std::vector<std::string>& names, 
 									   std::vector<std::string>& values);
 
-	// Re-implemented from the base class and shared by both services
+	// 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_nextMetadata;
 
 	// State variable storage
 	unordered_map<string, string> m_rdstate;
@@ -105,6 +115,7 @@
 
 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");
 
 UpMpd::UpMpd(const string& deviceid, 
 			 const unordered_map<string, string>& xmlfiles,
@@ -189,6 +200,18 @@
 	}
 	{	auto bound = bind(&UpMpd::seqcontrol, this, _1, _2, 1);
 		addActionMapping("Previous", bound);
+	}
+
+	addServiceType(serviceIdCM,
+				   "urn:schemas-upnp-org:service:ConnectionManager:1");
+	{	auto bound = bind(&UpMpd::getCurrentConnectionIDs, this, _1, _2);
+		addActionMapping("GetCurrentConnectionIDs", bound);
+	}
+	{	auto bound = bind(&UpMpd::getCurrentConnectionInfo, this, _1, _2);
+		addActionMapping("GetCurrentConnectionInfo", bound);
+	}
+	{	auto bound = bind(&UpMpd::getProtocolInfo, this, _1, _2);
+		addActionMapping("GetProtocolInfo", bound);
 	}
 }
 
@@ -209,6 +232,8 @@
 		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;
@@ -548,14 +573,16 @@
 	status["CurrentTrackDuration"] = is_song?
 		upnpduration(mpds.songlenms):"00:00:00";
 	status["AVTransportURI"] = uri;
-	status["AVTransportURIMetaData"] = is_song?didlmake(mpds) : "";
+	//status["AVTransportURIMetaData"] = is_song ? m_curMetadata : "";
+	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");
-	status["NextAVTransportURIMetaData"] = is_song?didlmake(mpds, true) : "";
+	//status["NextAVTransportURIMetaData"] = is_song ? m_nextMetadata : "";
+	status["NextAVTransportURIMetaData"] = is_song ? didlmake(mpds, true) : "";
 
 	status["PlaybackStorageMedium"] = playmedium;
 	status["PossiblePlaybackStorageMedium"] = "HDD,NETWORK";
@@ -634,6 +661,8 @@
 		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
@@ -665,6 +694,11 @@
 	if ((songid = m_mpdcli->insert(uri, setnext?curpos+1:curpos)) < 0) {
 		return UPNP_E_INTERNAL_ERROR;
 	}
+	if (setnext)
+		m_nextMetadata = metadata;
+	else
+		m_curMetadata = metadata;
+
 	if (!setnext) {
 		MpdStatus::State st = mpds.state;
 		// Have to tell mpd which track to play, else it will keep on
@@ -972,6 +1006,99 @@
 	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/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;
+}
 
 /////////////////////////////////////////////////////////////////////
 // Main program
@@ -1013,6 +1140,12 @@
 
 static string datadir(DATADIR "/");
 static string configdir(CONFIGDIR "/");
+
+// Our XML description data. !Keep description.xml first!
+static const char *xmlfilenames[] = {/* keep first */ "description.xml", 
+	 "RenderingControl.xml", "AVTransport.xml", "ConnectionManager.xml"};
+
+static const int xmlfilenamescnt = sizeof(xmlfilenames) / sizeof(char *);
 
 int main(int argc, char *argv[])
 {
@@ -1125,46 +1258,30 @@
 	// Create unique ID
 	string UUID = LibUPnP::makeDevUUID(friendlyname);
 
-	// Read our XML data.
+	// Read our XML data to make it available from the virtual directory
 	string reason;
-
-	string description;
-	string filename = datadir + "description.xml";
-	if (!file_to_string(filename, description, &reason)) {
-		LOGFAT("Failed reading " << filename << " : " << reason << endl);
-		return 1;
-	}
-	// Update device description with UUID and friendlyname
-    description = regsub1("@UUID@", description, UUID);
-    description = regsub1("@FRIENDLYNAME@", description, friendlyname);
-
-	string rdc_scdp;
-	filename = datadir + "RenderingControl.xml";
-	if (!file_to_string(filename, rdc_scdp, &reason)) {
-		LOGFAT("Failed reading " << filename << " : " << reason << endl);
-		return 1;
-	}
-
-	string avt_scdp;
-	filename = datadir + "AVTransport.xml";
-	if (!file_to_string(filename, avt_scdp, &reason)) {
-		LOGFAT("Failed reading " << filename << " : " << reason << endl);
-		return 1;
-	}
-
-	// List the XML files to be served through http (all will live in '/')
 	unordered_map<string, string> xmlfiles;
-	xmlfiles["description.xml"] = description;
-	xmlfiles["RenderingControl.xml"] = rdc_scdp;
-	xmlfiles["AVTransport.xml"] = avt_scdp;
+	for (int i = 0; i < xmlfilenamescnt; i++) {
+		string filename = path_cat(datadir, xmlfilenames[i]);
+		string data;
+		if (!file_to_string(filename, data, &reason)) {
+			LOGFAT("Failed reading " << filename << " : " << reason << endl);
+			return 1;
+		}
+		if (i == 0) {
+			// Special for description: set UUID and friendlyname
+			data = regsub1("@UUID@", data, UUID);
+			data = regsub1("@FRIENDLYNAME@", data, friendlyname);
+		}
+		xmlfiles[xmlfilenames[i]] = data;
+	}
 
 	// Initialize the UPnP device object.
 	UpMpd device(string("uuid:") + UUID, xmlfiles, &mpdcli,
-				 ownqueue?UpMpd::upmpdOwnQueue:UpMpd::upmpdNone);
-
+				 ownqueue ? UpMpd::upmpdOwnQueue : UpMpd::upmpdNone);
+
+	// And forever generate state change events.
 	LOGDEB("Entering event loop" << endl);
-
-	// And forever generate state change events.
 	device.eventloop();
 
 	return 0;