--- a/src/ohcredentials.cxx
+++ b/src/ohcredentials.cxx
@@ -28,6 +28,7 @@
#include <map>
#include <utility>
#include <vector>
+#include <regex>
#include "conftree.h"
#include "main.hxx"
@@ -39,6 +40,7 @@
#include "libupnpp/base64.hxx"
#include "libupnpp/soaphelp.hxx"
#include "libupnpp/device/device.hxx"
+#include "mediaserver/cdplugins/cdplugin.hxx"
using namespace std;
using namespace std::placeholders;
@@ -52,27 +54,88 @@
{"qobuz.com", "qobuz"}
};
-// Should be derived into ServiceCredsQobuz, ServiceCredsTidal, there
-// is a lot in common and a few diffs.
+// This is used for translating urls for the special use of
+// Kazoo/Lumin. The media server, which is used to run the http server
+// and for getting the real media URLs, must run on this host (for one
+// thing the creds are passed through a local file).
+// *** Note that this needs xxxautostart to work, else the HTTP server
+// won't be listening (as long as nobody accesses the app section
+// of the media server) ***
+static string upnphost;
+
+// Called from OHPlaylist. The CP (Kazoo/Lumin mostly) will send URIs
+// like qobuz:// tidal:// and expect the renderer to know what to do
+// with them. We transform them so that they point to our media server
+// gateway (which should be running of course for this to work).
+bool OHCredsMaybeMorphSpecialUri(string& uri)
+{
+ if (uri.find("http://") == 0 || uri.find("https://") == 0) {
+ return true;
+ }
+
+ static string sport;
+ if (sport.empty()) {
+ std::unique_lock<std::mutex>(g_configlock);
+ int port = CDPluginServices::default_microhttpport();
+ if (!g_config->get("plgmicrohttpport", sport)) {
+ sport = SoapHelp::i2s(port);
+ }
+ }
+
+ // http://wiki.openhome.org/wiki/Av:Developer:Eriskay:StreamingServices
+ // Tidal and qobuz tracks added by Kazoo / Lumin:
+ // tidal://track?version=1&trackId=[tidal_track_id]
+ // qobuz://track?version=2&trackId=[qobuz_track_id]
+
+ string se =
+ "(tidal|qobuz)://track\\?version=([[:digit:]]+)&trackId=([[:digit:]]+)";
+ std::regex e(se);
+ std::smatch mr;
+ bool found = std::regex_match(uri, mr, e);
+ if (found) {
+ string pathprefix = CDPluginServices::getpathprefix(mr[1]);
+
+ // The microhttpd code actually only cares about getting a
+ // trackId parameter. Make it look what the plugins normally
+ // generate anyway:
+ string path = path_cat(pathprefix,
+ "track?version=1&trackId=" + mr[3].str());
+ uri = string("http://") + upnphost + ":" + sport + path;
+ }
+ return found;
+}
+
+// We might want to derive this into ServiceCredsQobuz,
+// ServiceCredsTidal, there is a lot in common and a few diffs.
struct ServiceCreds {
ServiceCreds() {}
ServiceCreds(const string& inm, const string& u, const string& p,
const string& ep)
: servicename(inm), user(u), password(p), encryptedpass(ep) {
- // The appid used by the Qobuz python module. Has to be
- // consistent with the token obtained by the same, so we
- // return it (by convention, as seen in wiresharking kazoo) in
- // the data field. Of course, this class should be derived
- // into service-specific ones, and this should happen only for
- // the qobuz version. We could and do obtain the appid from
- // the module, but kazoo wants it before we login, so just
- // hard-code it for now.
- data = "285473059";
- }
+
+ if (servicename == "qobuz") {
+ // The appid used by the Qobuz python module. Has to be
+ // consistent with the token obtained by the same, so we
+ // return it (by convention, as seen in wiresharking
+ // kazoo) in the data field. We could and do obtain the
+ // appid from the module, but kazoo apparently wants it
+ // before we login, so just hard-code it for now. The
+ // Python code uses the value from XBMC,285473059,
+ // ohplayer uses 854233864
+ data = "285473059";
+ } else if (servicename == "tidal") {
+ // data contains the country code
+ data = "FR";
+ }
+ }
+
~ServiceCreds() {
delete cmd;
}
+ // We need a Python helper to perform the login. That's the media
+ // server gateway module, from which we only use a separate method
+ // which logs-in and returns the auth data (token, etc.)
bool maybeStartCmd() {
LOGDEB("ServiceCreds: " << servicename << " maybeStartCmd()\n");
if (nullptr == cmd) {
@@ -90,8 +153,13 @@
path_cat(g_datadir, "cdplugins") + ":" +
path_cat(g_datadir, "cdplugins/pycommon") + ":" +
path_cat(g_datadir, "cdplugins/" + servicename);
+ string configname = string("UPMPD_CONFIG=") + g_configfilename;
+ // hostport is not needed by this login-only instance.
+ string hostport = string("UPMPD_HTTPHOSTPORT=bogus:0");
+ string pp = string("UPMPD_PATHPREFIX=") +
+ CDPluginServices::getpathprefix(servicename);
if (!cmd->startCmd(exepath, {/*args*/},
- /* env */ {pythonpath})) {
+ /* env */ {pythonpath, configname, hostport, pp})) {
LOGERR("ServiceCreds::maybeStartCmd: startCmd failed\n");
return false;
}
@@ -101,9 +169,17 @@
string login() {
LOGDEB("ServiceCreds: " << servicename << " login\n");
- if (!token.empty()) {
- return token;
- }
+
+ // Check if already logged-in
+ if (servicename == "qobuz" || servicename == "tidal") {
+ if (!servicedata["token"].empty()) {
+ return servicedata["token"];
+ }
+ } else {
+ LOGERR("Unsupported service: " << servicename << endl);
+ return string();
+ }
+
if (!maybeStartCmd()) {
return string();
}
@@ -114,32 +190,42 @@
servicename << " user " << user << endl);
return string();
}
- auto it = res.find("appid");
- if (it == res.end()) {
- LOGERR("ServiceCreds::login: no appid. Service " <<
- servicename << " user " << user << endl);
- return string();
- }
- appid = it->second;
- data = appid;
- it = res.find("token");
- if (it == res.end()) {
- LOGERR("ServiceCreds::login: no token. Service " <<
- servicename << " user " << user << endl);
- return string();
- }
- return token = it->second;
+
+ vector<string> toknames;
+ if (servicename == "qobuz") {
+ toknames = vector<string>{"token", "appid"};
+ } else if (servicename == "tidal") {
+ toknames = vector<string>{"token", "country"};
+ }
+ for (const auto& toknm : toknames) {
+ auto it = res.find(toknm);
+ if (it == res.end()) {
+ LOGERR("ServiceCreds::login: no " << toknm << ". Service " <<
+ servicename << " user " << user << endl);
+ return string();
+ }
+ servicedata[toknm] = it->second;
+ }
+ if (servicename == "qobuz") {
+ data = servicedata["appid"];
+ } else if (servicename == "tidal") {
+ data = servicedata["country"];
+ }
+ return servicedata["token"];
}
void logout() {
- appid.clear();
- token.clear();
+ servicedata.clear();
}
string str() {
string s;
+ string sdata;
+ for (const auto& entry:servicedata) {
+ sdata += entry.first + " : " + entry.second + ", ";
+ }
s += "Service: " + servicename + " User: " + user +
- /*" Pass: "+password*/ + " Appid " + appid + " Token " + token +
+ /*" Pass: "+password*/ + " Servicedata: " + sdata +
/*" EPass: "+encryptedpass*/ + " Enabled: " +
SoapHelp::i2s(enabled) + " Status: " + status + " Data: " + data;
return s;
@@ -150,14 +236,14 @@
string user;
string password;
string encryptedpass;
- string appid;
- string token;
bool enabled{true};
+ CmdTalk *cmd{0};
+ // Things we obtain from the module and send to the CP
+ unordered_map<string,string> servicedata;
+
string status;
- // For qobuz, data contains the "app id" 854233864 for ohplayer.
- // XBMC is 285473059
+ // See comments about 'data' use above.
string data;
- CmdTalk *cmd{0};
};
class OHCredentials::Internal {
@@ -214,18 +300,17 @@
bool save() {
string credsfile = path_cat(cachedir, "screds");
- FILE *fp = fopen(credsfile.c_str(), "w");
- fchmod(fileno(fp), 0600);
- if (nullptr == fp) {
+ ConfSimple credsconf(credsfile.c_str());
+ if (!credsconf.ok()) {
LOGERR("OHCredentials: error opening " << credsfile <<
" errno " << errno << endl);
return false;
}
for (const auto& cred : creds) {
- fprintf(fp, "[%s]\nu=%s\np=%s\n", cred.second.servicename.c_str(),
- cred.second.user.c_str(), cred.second.password.c_str());
- }
- fclose(fp);
+ credsconf.set("u", cred.second.user, cred.second.servicename);
+ credsconf.set("p", cred.second.password, cred.second.servicename);
+ }
+ chmod(credsfile.c_str(), 0600);
return true;
}
@@ -268,6 +353,9 @@
dev->addActionMapping(
this, "GetSequenceNumber",
bind(&OHCredentials::actGetSequenceNumber, this, _1, _2));
+
+ unsigned short usport;
+ dev->ipv4(&upnphost, &usport);
}
OHCredentials::~OHCredentials()