Switch to side-by-side view

--- a/scctl_src/scctl.cpp
+++ b/scctl_src/scctl.cpp
@@ -19,7 +19,7 @@
  * Songcast UPnP controller. 
  *
  * This can list the state of all Receivers, tell
- * a Receiver to play the same URI as another one (master/slave,
+ * a Receiver to play the same URI as another one (master/slave),
  * except that the slaves don't really follow the master state, they
  * will just keep playing the same URI), or tell a Receiver to return
  * to Playlist operation.
@@ -32,6 +32,7 @@
  * first tries to contact the server, and does things itself if no
  * server is found (encurring 2-3 S of timeout in the latter case).
  */
+#include "libupnpp/config.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,300 +46,58 @@
 #include <iostream>
 #include <vector>
 #include <algorithm>
-using namespace std;
 
 #include "libupnpp/upnpplib.hxx"
 #include "libupnpp/log.hxx"
 #include "libupnpp/control/mediarenderer.hxx"
 #include "libupnpp/control/discovery.hxx"
+#include "libupnpp/control/linnsongcast.hxx"
 
 #include "../src/netcon.h"
 #include "../src/upmpdutils.hxx"
 
 using namespace UPnPClient;
 using namespace UPnPP;
-
-UPnPDeviceDirectory *superdir;
-
-MRDH getRenderer(const string& name)
-{
-    if (superdir == 0) {
-        superdir = UPnPDeviceDirectory::getTheDir();
-    }
-
-    UPnPDeviceDesc ddesc;
-    if (superdir->getDevByUDN(name, ddesc)) {
-        return MRDH(new MediaRenderer(ddesc));
-    } else if (superdir->getDevByFName(name, ddesc)) {
-        return MRDH(new MediaRenderer(ddesc));
-    } 
-    cerr << "getDevByFname failed for " << name << endl;
-    return MRDH();
-}
-
-struct SongcastState {
-    string nm;
-    string UDN;
-    enum SCState {SCS_GENERROR, SCS_NOOH, SCS_NOTRECEIVER,
-                  SCS_STOPPED, SCS_PLAYING};
-    SCState state;
-    string uri;
-    string meta;
-    int receiverSourceIndex;
-    string reason;
-
-    OHPRH prod;
-    OHRCH rcv;
-
-    SongcastState() 
-        : state(SCS_GENERROR), receiverSourceIndex(-1) {
-    }
-
-    void reset() {
-        nm.clear();
-        state = SongcastState::SCS_GENERROR;
-        receiverSourceIndex = -1;
-        reason.clear();
-        uri.clear();
-        meta.clear();
-        prod.reset();
-        rcv.reset();
-    }
-};
-
-void getSongcastState(const string& nm, SongcastState& st, bool live = true)
-{
-    st.reset();
-    st.nm = nm;
-
-    MRDH rdr = getRenderer(nm);
-    if (!rdr) {
-        st.reason = nm + " not a media renderer?";
-        return;
-    }
-    st.nm = rdr->desc()->friendlyName;
-    st.UDN = rdr->desc()->UDN;
-
-    OHPRH prod = rdr->ohpr();
-    if (!prod) {
-        st.state = SongcastState::SCS_NOOH;
-        st.reason =  nm + ": device has no OHProduct service";
-        return;
-    }
-    int index;
-    if (prod->sourceIndex(&index)) {
-        st.reason = nm + " : sourceIndex failed";
-        return;
-    }
-
-    vector<OHProduct::Source> sources;
-    if (prod->getSources(sources) || sources.size() == 0) {
-        st.reason = nm + ": getSources failed";
-        return;
-    }
-    unsigned int rcvi = 0;
-    for (; rcvi < sources.size(); rcvi++) {
-        if (!sources[rcvi].name.compare("Receiver"))
-            break;
-    }
-    if (rcvi == sources.size()) {
-        st.state = SongcastState::SCS_NOOH;
-        st.reason = nm +  " has no Receiver service";
-        return;
-    }
-    st.receiverSourceIndex = int(rcvi);
-
-    if (index < 0 || index >= int(sources.size())) {
-        st.reason = nm +  ": bad index " + SoapHelp::i2s(index) +
-            " not inside sources of size " +  SoapHelp::i2s(sources.size());
-        return;
-    }
-
-    // Looks like the device has a receiver service. We may have to return a 
-    // handle for it.
-    OHRCH rcv = rdr->ohrc();
-
-    string sname = sources[index].name;
-    if (sname.compare("Receiver")) {
-        st.state = SongcastState::SCS_NOTRECEIVER;
-        st.reason = nm +  " not in receiver mode ";
-        goto out;
-    }
-
-    if (!rcv) {
-        st.reason = nm +  ": no receiver service??";
-        goto out;
-    }
-    if (rcv->sender(st.uri, st.meta)) {
-        st.reason = nm +  ": Receiver::Sender failed";
-        goto out;
-    }
-
-    OHPlaylist::TPState tpst;
-    if (rcv->transportState(&tpst)) {
-        st.reason = nm +  ": Receiver::transportState() failed";
-        goto out;
-    }
-
-    if (tpst == OHPlaylist::TPS_Playing) {
-        st.state = SongcastState::SCS_PLAYING;
-    } else {
-        st.state = SongcastState::SCS_STOPPED;
-    }
-out:
-    if (live) {
-        st.prod = prod;
-        st.rcv = rcv;
-    }
-        
-    return;
-}
-
-void listReceivers(vector<SongcastState>& vscs)
-{
-    vector<UPnPDeviceDesc> vdds;
-    if (!MediaRenderer::getDeviceDescs(vdds)) {
-        cerr << "listReceivers::getDeviceDescs failed" << endl;
-        return;
-    }
-
-    for (auto& entry : vdds) {
-        SongcastState st;
-        getSongcastState(entry.UDN, st, false);
-        if (st.state == SongcastState::SCS_NOTRECEIVER || 
-            st.state == SongcastState::SCS_PLAYING ||
-            st.state == SongcastState::SCS_STOPPED) {
-            vscs.push_back(st);
-        }
-    }
-}
-
-bool setReceiverPlaying(const string& nm, SongcastState& st, 
-                        const string& uri, const string& meta)
-{
-    if (!st.rcv || !st.prod) {
-        st.reason = nm + " : null handle ??";
-        return false;
-    }
-
-    if (st.rcv->setSender(uri, meta)) {
-        st.reason = nm + " Receiver::setSender() failed";
-        return false;
-    }
-    if (st.prod->setSourceIndex(st.receiverSourceIndex)) {
-        st.reason = nm + " : can't set source index to " +
-            SoapHelp::i2s(st.receiverSourceIndex);
-        return false;
-    }
-    if (st.rcv->play()) {
-        st.reason = nm + " Receiver::play() failed";
-        return false;
-    }
-    return true;
-}
-
-bool stopReceiver(const string& nm, SongcastState st)
-{
-    if (!st.rcv || !st.prod) {
-        st.reason = nm + " : null handle ??";
-        return false;
-    }
-    if (st.rcv->stop()) {
-        st.reason = nm + " Receiver::play() failed";
-        return false;
-    }
-    if (st.prod->setSourceIndex(0)) {
-        st.reason = nm + " : can't set source index to " +
-            SoapHelp::i2s(st.receiverSourceIndex);
-        return false;
-    }
-    return true;
-}
-
-void ohSongcast(const string& masterName, const vector<string>& slaves)
-{
-    SongcastState mstate;
-    getSongcastState(masterName, mstate);
-    if (mstate.state != SongcastState::SCS_PLAYING) {
-        cerr << "Required master not in Receiver Playing mode" << endl;
-        return;
-    }
-
-    // Note: sequence sent from windows songcast when setting up a receiver:
-    //   Product::SetSourceIndex / Receiver::SetSender / Receiver::Play
-    // When stopping:
-    //   Receiver::Stop / Product::SetStandby
-    for (auto& sl: slaves) {
-        cerr << "Setting up " << sl << endl;
-        SongcastState sstate;
-        getSongcastState(sl, sstate);
-
-        switch (sstate.state) {
-        case SongcastState::SCS_GENERROR:
-        case SongcastState::SCS_NOOH:
-            cerr << sl << sstate.reason << endl;
-            continue;
-        case SongcastState::SCS_STOPPED:
-        case SongcastState::SCS_PLAYING:
-            cerr << sl << ": already in receiver mode" << endl;
-            continue;
-        case SongcastState::SCS_NOTRECEIVER: 
-            if (setReceiverPlaying(sl, sstate, mstate.uri, mstate.meta)) {
-                cerr << sl << " set up for playing " << mstate.uri << endl;
-            } else {
-                cerr << sstate.reason << endl;
-            }
-        }
-    }
-}
-
-void ohNoSongcast(const vector<string>& slaves)
-{
-    for (auto& sl: slaves) {
-        cerr << "Songcast: resetting " << sl << endl;
-        SongcastState sstate;
-        getSongcastState(sl, sstate);
-
-        switch (sstate.state) {
-        case SongcastState::SCS_GENERROR:
-        case SongcastState::SCS_NOOH:
-            cerr << sl << sstate.reason << endl;
-            continue;
-        case SongcastState::SCS_NOTRECEIVER: 
-            cerr << sl << ": not in receiver mode" << endl;
-            continue;
-        case SongcastState::SCS_STOPPED:
-        case SongcastState::SCS_PLAYING:
-            if (stopReceiver(sl, sstate)) {
-                cerr << sl << " back from receiver mode " << endl;
-            } else {
-                cerr << sstate.reason << endl;
-            }
-        }
-    }
-}
+using namespace std;
+using namespace Songcast;
 
 string showReceivers()
 {
-    vector<SongcastState> vscs;
+    vector<ReceiverState> vscs;
     listReceivers(vscs);
     ostringstream out;
 
     for (auto& scs: vscs) {
         switch (scs.state) {
-        case SongcastState::SCS_GENERROR:    out << "Error ";break;
-        case SongcastState::SCS_NOOH:        out << "Nooh  ";break;
-        case SongcastState::SCS_NOTRECEIVER: out << "Off   ";break;
-        case SongcastState::SCS_STOPPED:     out << "Stop  ";break;
-        case SongcastState::SCS_PLAYING:     out << "Play  ";break;
+        case ReceiverState::SCRS_GENERROR:    out << "Error ";break;
+        case ReceiverState::SCRS_NOOH:        out << "Nooh  ";break;
+        case ReceiverState::SCRS_NOTRECEIVER: out << "Off   ";break;
+        case ReceiverState::SCRS_STOPPED:     out << "Stop  ";break;
+        case ReceiverState::SCRS_PLAYING:     out << "Play  ";break;
         }
         out << scs.nm << " ";
         out << scs.UDN << " ";
-        if (scs.state == SongcastState::SCS_PLAYING) {
+        if (scs.state == ReceiverState::SCRS_PLAYING) {
             out << scs.uri;
-        } else if (scs.state == SongcastState::SCS_GENERROR) {
+        } else if (scs.state == ReceiverState::SCRS_GENERROR) {
             out << scs.reason;
         }
+        out << endl;
+    }
+    return out.str();
+}
+
+string showSenders()
+{
+    vector<SenderState> vscs;
+    listSenders(vscs);
+    ostringstream out;
+
+    for (auto& scs: vscs) {
+        out << scs.nm << " ";
+        out << scs.UDN << " ";
+        out << scs.reason << " ";
+        out << scs.uri;
         out << endl;
     }
     return out.str();
@@ -347,13 +106,17 @@
 static char *thisprog;
 static char usage [] =
 " -l List renderers with Songcast Receiver capability\n"
+" -L List Songcast Senders\n"
 " -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast\n"
-"    Receivers and make them play from the same uri as the master\n"
+"    Receivers and make them play from the same uri as the master receiver\n"
 " -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
 " -S Run as server\n"
 " -f If no server is found, scctl will fork one after performing the\n"
 "    requested command, so that the next execution will not have to wait for\n"
 "    the discovery timeout.n"
+" -r <sender> <renderer> <renderer> : set up the renderers in Receiver mode\n"
+"    playing data from the sender. This is like -s but we get the uri from \n"
+"    the sender instead of a sibling receiver\n"
 " -h This help.\n"
 "\n"
 "Renderers may be designated by friendly name or UUID\n"
@@ -374,6 +137,8 @@
 #define OPT_h    0x10
 #define OPT_f    0x20
 #define OPT_p    0x40
+#define OPT_r    0x80
+#define OPT_L    0x100
 
 int runserver();
 bool tryserver(int flags, int argc, char *argv[]);
@@ -383,14 +148,40 @@
     thisprog = argv[0];
 
     int ret;
-    while ((ret = getopt(argc, argv, "fhlsSx")) != -1) {
+    while ((ret = getopt(argc, argv, "fhLlrsSx")) != -1) {
         switch (ret) {
         case 'f': op_flags |= OPT_f; break;
         case 'h': Usage(stdout); break;
-        case 'l': op_flags |= OPT_l; break;
-        case 's': op_flags |= OPT_s; break;
-        case 'S': op_flags |= OPT_S; break;
-        case 'x': op_flags |= OPT_x; break;
+        case 'l':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_l;
+            break;
+        case 'L':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_L;
+            break;
+        case 'r':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_r;
+            break;
+        case 's':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_s;
+            break;
+        case 'S':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_S;
+            break;
+        case 'x':
+            if (op_flags & ~OPT_f)
+                Usage();
+            op_flags |= OPT_x;
+            break;
         default: Usage();
         }
     }
@@ -418,38 +209,35 @@
     }
     // mylib->setLogFileName("/tmp/libupnp.log");
 
-
+    vector<string> args;
+    while (optind < argc) {
+        args.push_back(argv[optind++]);
+    }
+    
     if ((op_flags & OPT_l)) {
-        if (op_flags & (OPT_s|OPT_x)) {
-            Usage();
-        }
-        if (optind < argc)
+        if (args.size())
             Usage();
         string out = showReceivers();
         cout << out;
+    } else if ((op_flags & OPT_L)) {
+        if (args.size())
+            Usage();
+        string out = showSenders();
+        cout << out;
+    } else if ((op_flags & OPT_r)) {
+        if (args.size() < 2)
+            Usage();
+        setReceiversFromSender(args[0], vector<string>(args.begin() + 1,
+                                                       args.end()));
     } else if ((op_flags & OPT_s)) {
-        if (op_flags & (OPT_l|OPT_x)) {
+        if (args.size() < 2)
             Usage();
-        }
-        if (optind > argc - 2)
+        setReceiversFromReceiver(args[0], vector<string>(args.begin()+1,
+                                                         args.end()));
+    } else if ((op_flags & OPT_x)) {
+        if (args.size() < 1)
             Usage();
-        string master = argv[optind++];
-        vector<string> slaves;
-        while (optind < argc) {
-            slaves.push_back(argv[optind++]);
-        }
-        ohSongcast(master, slaves);
-    } else if ((op_flags & OPT_x)) {
-        if (op_flags & (OPT_l|OPT_s)) {
-            Usage();
-        }
-        if (optind > argc - 1)
-            Usage();
-        vector<string> slaves;
-        while (optind < argc) {
-            slaves.push_back(argv[optind++]);
-        }
-        ohNoSongcast(slaves);
+        stopReceivers(args);
     } else if ((op_flags & OPT_S)) {
         exit(runserver());
     } else {
@@ -593,6 +381,15 @@
         out = "Ok\n";
     } else if (opflags & OPT_l) {
         out = showReceivers();
+    } else if (opflags & OPT_r) {
+        if (toks.size() < 3)
+            return 1;
+        vector<string>::iterator beg = toks.begin();
+        beg++;
+        string sender = *beg;
+        beg++;
+        vector<string> receivers(beg, toks.end());
+        setReceiversFromSender(sender, receivers);
     } else if (opflags & OPT_s) {
         if (toks.size() < 3)
             return 1;
@@ -601,14 +398,14 @@
         string master = *beg;
         beg++;
         vector<string> slaves(beg, toks.end());
-        ohSongcast(master, slaves);
+        setReceiversFromReceiver(master, slaves);
     } else if (opflags & OPT_x) {
         if (toks.size() < 2)
             return 1;
         vector<string>::iterator beg = toks.begin();
         beg++;
         vector<string> slaves(beg, toks.end());
-        ohNoSongcast(slaves);
+        stopReceivers(slaves);
     } else {
         LOGERR("scctl: server: bad cmd:" << toks[0] << endl);
         return 1;