--- a/scctl_src/scctl.cpp
+++ b/scctl_src/scctl.cpp
@@ -15,12 +15,30 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-///// Songcast UPnP Controller
+/*
+ * Songcast UPnP controller.
+ *
+ * This can list the state of all Receivers, tell
+ * 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.
+ *
+ * To avoid encurring a discovery timeout for each op, there is a
+ * server mode, in which a permanent process executes the above
+ * commands, received on Unix socket, and returns the results.
+ *
+ * When executing any of the ops from the command line, the program
+ * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <string>
#include <iostream>
@@ -30,12 +48,11 @@
#include "libupnpp/upnpplib.hxx"
#include "libupnpp/log.hxx"
-#include "libupnpp/upnpputils.hxx"
-#include "libupnpp/ptmutex.hxx"
-#include "libupnpp/control/service.hxx"
#include "libupnpp/control/mediarenderer.hxx"
-#include "libupnpp/control/renderingcontrol.hxx"
#include "libupnpp/control/discovery.hxx"
+
+#include "../src/netcon.h"
+#include "../src/upmpdutils.hxx"
using namespace UPnPClient;
using namespace UPnPP;
@@ -299,46 +316,80 @@
}
}
+string showReceivers()
+{
+ vector<SongcastState> 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;
+ }
+ out << scs.nm << " ";
+ out << scs.UDN << " ";
+ if (scs.state == SongcastState::SCS_PLAYING) {
+ out << scs.uri;
+ } else if (scs.state == SongcastState::SCS_GENERROR) {
+ out << scs.reason;
+ }
+ out << endl;
+ }
+ return out.str();
+}
+
static char *thisprog;
static char usage [] =
" -l list renderers with Songcast Receiver capability\n"
-" -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast"
+" -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"
" -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
+" -S run as server\n"
"\n"
"Renderers may be designated by friendly name or UUID\n"
-" \n\n"
+"\n"
;
static void
-Usage(void)
-{
- fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
+Usage(FILE *fp = stderr)
+{
+ fprintf(fp, "%s: usage:\n%s", thisprog, usage);
exit(1);
}
static int op_flags;
#define OPT_l 0x1
#define OPT_s 0x2
#define OPT_x 0x4
+#define OPT_S 0x8
+#define OPT_h 0x10
+
+int runserver();
+bool tryserver(int argc, char *argv[]);
int main(int argc, char *argv[])
{
thisprog = argv[0];
int ret;
- while ((ret = getopt(argc, argv, "lsx")) != -1) {
+ while ((ret = getopt(argc, argv, "lSsxh")) != -1) {
switch (ret) {
case 'l': if (op_flags) Usage(); op_flags |= OPT_l; break;
case 's': if (op_flags) Usage(); op_flags |= OPT_s; break;
+ case 'S': if (op_flags) Usage(); op_flags |= OPT_S; break;
case 'x': if (op_flags) Usage(); op_flags |= OPT_x; break;
+ case 'h': Usage(stdout);
default: Usage();
}
}
- if (Logger::getTheLog("/tmp/upexplo.log") == 0) {
- cerr << "Can't initialize log" << endl;
- return 1;
- }
- Logger::getTheLog("")->setLogLevel(Logger::LLDEB1);
+ // If we're not a server, try to contact one to avoid the
+ // discovery timeout
+ if (!(op_flags & OPT_S) && tryserver(argc -1, &argv[1])) {
+ exit(0);
+ }
LibUPnP *mylib = LibUPnP::getLibUPnP();
if (!mylib) {
@@ -359,26 +410,8 @@
}
if (optind < argc)
Usage();
- vector<SongcastState> vscs;
- listReceivers(vscs);
- for (auto& scs: vscs) {
- switch (scs.state) {
- case SongcastState::SCS_GENERROR: cout << "Error ";break;
- case SongcastState::SCS_NOOH: cout << "Nooh ";break;
- case SongcastState::SCS_NOTRECEIVER: cout << "Off ";break;
- case SongcastState::SCS_STOPPED: cout << "Stop ";break;
- case SongcastState::SCS_PLAYING: cout << "Play ";break;
- }
- cout << scs.nm << " ";
- cout << scs.UDN << " ";
- if (scs.state == SongcastState::SCS_PLAYING) {
- cout << scs.uri;
- } else if (scs.state == SongcastState::SCS_GENERROR) {
- cout << scs.reason;
- }
- cout << endl;
- }
-
+ string out = showReceivers();
+ cout << out;
} else if ((op_flags & OPT_s)) {
if (op_flags & (OPT_l|OPT_x)) {
Usage();
@@ -402,9 +435,208 @@
slaves.push_back(argv[optind++]);
}
ohNoSongcast(slaves);
+ } else if (op_flags & OPT_S) {
+ exit(runserver());
} else {
Usage();
}
return 0;
}
+
+// The Unix socket path which we use for client-server operation
+bool sockname(string& nm)
+{
+ char buf[80];
+ sprintf(buf, "/tmp/scctl%d", int(getuid()));
+ if (access(buf, 0) < 0) {
+ if (mkdir(buf, 0700)) {
+ perror("mkdir");
+ return false;
+ }
+ }
+ sprintf(buf, "/tmp/scctl%d/sock", int(getuid()));
+ nm = buf;
+ return true;
+}
+
+
+// Try to have an op run in server process (to avoid the discovery
+// timeout).
+bool tryserver(const string& cmd)
+{
+ fflush(stdout);
+
+ NetconCli *clicon = new NetconCli();
+ NetconP con(clicon);
+ if (!con) {
+ cerr << "tryserver: new NetconCli failed\n";
+ return false;
+ }
+
+ string snm;
+ if (!sockname(snm)) {
+ return false;
+ }
+ if (clicon->openconn(snm.c_str(), (unsigned int)0) < 0) {
+ // Server not running case, no big deal
+ LOGDEB("openconn(" << snm << ") failed (ok: server not running)\n");
+ return false;
+ }
+
+ if (clicon->send(cmd.c_str(), cmd.size() + 1) < 0) {
+ cerr << "Send failed\n";
+ return false;
+ }
+ char buf[1024];
+ for (;;) {
+ int cnt = clicon->receive(buf, 1024);
+ if (cnt < 0) {
+ perror("receive:");
+ return false;
+ }
+ if (cnt == 0)
+ break;
+ if (write(1, buf, cnt) != cnt) {
+ perror("write");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool tryserver(int argc, char **argv)
+{
+ string cmd;
+ for (int i = 0; i < argc; i++) {
+ // May need quoting here ?
+ cmd += argv[i];
+ cmd += " ";
+ }
+ cmd += "\n";
+ return tryserver(cmd);
+}
+
+
+// Listening endpoint for the server. For each connection, one request
+// is served immediately and the connection is closed.
+class MyNetconServLis : public NetconServLis {
+public:
+protected:
+ int cando(Netcon::Event reason);
+};
+
+// Server worker method. Called for each connection, does one thing
+// and returns.
+int MyNetconServLis::cando(Netcon::Event reason)
+{
+ NetconServCon *con = accept();
+ if (con == 0) {
+ LOGERR("scctl server: accept() failed\n");
+ return 1;
+ }
+ std::unique_ptr<Netcon> conhold(con);
+
+ // Get command
+ string line;
+ {
+ char buf[2048];
+ if (con->getline(buf, 2048, 2) <= 0) {
+ LOGERR("scctl: server: getline() failed\n");
+ return 1;
+ }
+ line = buf;
+ }
+
+ trimstring(line, " \n");
+ vector<string> toks;
+ stringToTokens(line, toks);
+ if (toks.empty()) {
+ return 1;
+ }
+
+ string cmd = toks[0];
+ string out;
+ if (!cmd.compare("-p")) {
+ // ping
+ out = "Ok\n";
+ } else if (!cmd.compare("-l")) {
+ out = showReceivers();
+ } else if (!cmd.compare("-s")) {
+ if (cmd.size() < 3)
+ return 1;
+ vector<string>::iterator beg = toks.begin();
+ beg++;
+ string master = *beg;
+ beg++;
+ vector<string> slaves(beg, toks.end());
+ ohSongcast(master, slaves);
+ } else if (!cmd.compare("-x")) {
+ if (cmd.size() < 2)
+ return 1;
+ vector<string>::iterator beg = toks.begin();
+ beg++;
+ vector<string> slaves(beg, toks.end());
+ ohNoSongcast(slaves);
+ } else {
+ return 1;
+ }
+
+ if (con->send(out.c_str(), out.size(), 0) < 0) {
+ LOGERR("scctl: server: send() failed\n");
+ return 1;
+ }
+
+ return 1;
+}
+
+// Server init routine
+
+int runserver()
+{
+ string snm;
+ if (!sockname(snm)) {
+ return 1;
+ }
+
+ // Check if already running, else clean up socket
+ if (access(snm.c_str(), 0) == 0) {
+
+ if(tryserver("-p\n")) {
+ // Already running
+ return 0;
+ }
+ if (unlink(snm.c_str()) < 0) {
+ perror("unlink");
+ return 1;
+ }
+ }
+
+ // Run
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ // Initialize lib at once, will be ready when we need it
+ showReceivers();
+
+ MyNetconServLis *servlis = new MyNetconServLis();
+ if (servlis == 0) {
+ LOGERR("scctl: server: new NetconServLis failed\n");
+ return 1;
+ }
+ if (servlis->openservice(snm.c_str()) < 0) {
+ LOGERR("scctl: server: openservice(" << snm << ") failed\n");
+ return 1;
+ }
+
+ SelectLoop myloop;
+ myloop.addselcon(NetconP(servlis), Netcon::NETCONPOLL_READ);
+
+ LOGDEB("scctl: server: openservice(" << snm << ") Ok\n");
+
+ if (myloop.doLoop() < 0) {
+ LOGERR("scctl: server: selectloop failed\n");
+ return 1;
+ }
+ return 0;
+}