chiark / gitweb /
Delete/get/set playlists from command-line client. Doesn't support
[disorder] / clients / disorder.c
index fa42558dd59ed838b6c5050a6fbfa9eeb81a621c..719cf0401d4fd5b3665e40bb92c1505a8009969a 100644 (file)
  * USA
  */
 
-#include <config.h>
-#include "types.h"
+#include "common.h"
 
 #include <getopt.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/un.h>
-#include <stdio.h>
 #include <errno.h>
-#include <stdlib.h>
-#include <string.h>
 #include <locale.h>
 #include <time.h>
 #include <stddef.h>
 #include <unistd.h>
-#include <assert.h>
 #include <pcre.h>
 #include <ctype.h>
+#include <langinfo.h>
 
 #include "configuration.h"
 #include "syscalls.h"
 #include "kvp.h"
 #include "split.h"
 #include "sink.h"
-#include "plugin.h"
 #include "mem.h"
 #include "defs.h"
 #include "authorize.h"
 #include "vector.h"
 #include "version.h"
+#include "dateparse.h"
+#include "inputline.h"
 
 static disorder_client *client;
 
@@ -180,15 +177,34 @@ static void cf_queue(char attribute((unused)) **argv) {
 }
 
 static void cf_quack(char attribute((unused)) **argv) {
-  xprintf("\n"
-         " .------------------.\n"
-         " | Naath is a babe! |\n"
-         " `---------+--------'\n"
-         "            \\\n"
-         "              >0\n"
-         "               (<)'\n"
-         "~~~~~~~~~~~~~~~~~~~~~~\n"
-         "\n");
+  if(!strcasecmp(nl_langinfo(CODESET), "utf-8")) {
+#define TL "\xE2\x95\xAD"
+#define TR "\xE2\x95\xAE"
+#define BR "\xE2\x95\xAF"
+#define BL "\xE2\x95\xB0"
+#define H "\xE2\x94\x80"
+#define V "\xE2\x94\x82"
+#define T "\xE2\x94\xAC"
+    xprintf("\n"
+            " "TL H H H H H H H H H H H H H H H H H H TR"\n"
+            " "V" Naath is a babe! "V"\n"
+            " "BL H H H H H H H H H T H H H H H H H H BR"\n"
+            "            \\\n"
+            "              >0\n"
+            "               (<)'\n"
+            "~~~~~~~~~~~~~~~~~~~~~~\n"
+            "\n");
+  } else {
+    xprintf("\n"
+            " .------------------.\n"
+            " | Naath is a babe! |\n"
+            " `---------+--------'\n"
+            "            \\\n"
+            "              >0\n"
+            "               (<)'\n"
+            "~~~~~~~~~~~~~~~~~~~~~~\n"
+            "\n");
+  }
 }
 
 static void cf_somelist(char **argv,
@@ -468,6 +484,165 @@ static void cf_setup_guest(char **argv) {
     exit(EXIT_FAILURE);
 }
 
+struct scheduled_event {
+  time_t when;
+  struct kvp *actiondata;
+  char *id;
+};
+
+static int compare_event(const void *av, const void *bv) {
+  struct scheduled_event *a = (void *)av, *b = (void *)bv;
+
+  /* Primary sort key is the trigger time */
+  if(a->when < b->when)
+    return -1;
+  else if(a->when > b->when)
+    return 1;
+  /* For events that go off at the same time just sort by ID */
+  return strcmp(a->id, b->id);
+}
+
+static void cf_schedule_list(char attribute((unused)) **argv) {
+  char **ids;
+  int nids, n;
+  struct scheduled_event *events;
+  char tb[128];
+  const char *action, *key, *value, *priority;
+  int prichar;
+
+  /* Get all known events */
+  if(disorder_schedule_list(getclient(), &ids, &nids))
+    exit(EXIT_FAILURE);
+  events = xcalloc(nids, sizeof *events);
+  for(n = 0; n < nids; ++n) {
+    events[n].id = ids[n];
+    if(disorder_schedule_get(getclient(), ids[n], &events[n].actiondata))
+      exit(EXIT_FAILURE);
+    events[n].when = atoll(kvp_get(events[n].actiondata, "when"));
+  }
+  /* Sort by trigger time */
+  qsort(events, nids, sizeof *events, compare_event);
+  /* Display them */
+  for(n = 0; n < nids; ++n) {
+    strftime(tb, sizeof tb, "%Y-%m-%d %H:%M:%S %Z", localtime(&events[n].when));
+    action = kvp_get(events[n].actiondata, "action");
+    priority = kvp_get(events[n].actiondata, "priority");
+    if(!strcmp(priority, "junk"))
+      prichar = 'J';
+    else if(!strcmp(priority, "normal"))
+      prichar = 'N';
+    else
+      prichar = '?';
+    xprintf("%11s %-25s %c %-8s %s",
+           events[n].id, tb, prichar, kvp_get(events[n].actiondata, "who"),
+           action);
+    if(!strcmp(action, "play"))
+      xprintf(" %s",
+             nullcheck(utf82mb(kvp_get(events[n].actiondata, "track"))));
+    else if(!strcmp(action, "set-global")) {
+      key = kvp_get(events[n].actiondata, "key");
+      value = kvp_get(events[n].actiondata, "value");
+      if(value)
+       xprintf(" %s=%s",
+               nullcheck(utf82mb(key)),
+               nullcheck(utf82mb(value)));
+      else
+       xprintf(" %s unset",
+               nullcheck(utf82mb(key)));
+    }
+    xprintf("\n");
+  }
+}
+
+static void cf_schedule_del(char **argv) {
+  if(disorder_schedule_del(getclient(), argv[0]))
+    exit(EXIT_FAILURE);
+}
+
+static void cf_schedule_play(char **argv) {
+  if(disorder_schedule_add(getclient(),
+                          dateparse(argv[0]),
+                          argv[1],
+                          "play",
+                          argv[2]))
+    exit(EXIT_FAILURE);
+}
+
+static void cf_schedule_set_global(char **argv) {
+  if(disorder_schedule_add(getclient(),
+                          dateparse(argv[0]),
+                          argv[1],
+                          "set-global",
+                          argv[2],
+                          argv[3]))
+    exit(EXIT_FAILURE);
+}
+
+static void cf_schedule_unset_global(char **argv) {
+  if(disorder_schedule_add(getclient(),
+                          dateparse(argv[0]),
+                          argv[1],
+                          "set-global",
+                          argv[2],
+                          (char *)0))
+    exit(EXIT_FAILURE);
+}
+
+static void cf_playlists(char attribute((unused)) **argv) {
+  char **vec;
+
+  if(disorder_playlists(getclient(), &vec, 0))
+    exit(EXIT_FAILURE);
+  while(*vec)
+    xprintf("%s\n", nullcheck(utf82mb(*vec++)));
+}
+
+static void cf_playlist_del(char **argv) {
+  if(disorder_playlist_delete(getclient(), argv[0]))
+    exit(EXIT_FAILURE);
+}
+
+static void cf_playlist_get(char **argv) {
+  char **vec;
+
+  if(disorder_playlist_get(getclient(), argv[0], &vec, 0))
+    exit(EXIT_FAILURE);
+  while(*vec)
+    xprintf("%s\n", nullcheck(utf82mb(*vec++)));
+}
+
+static void cf_playlist_set(char **argv) {
+  struct vector v[1];
+  FILE *input;
+  const char *tag;
+  char *l;
+
+  if(argv[1]) {
+    // Read track list from file
+    if(!(input = fopen(argv[1], "r")))
+      fatal(errno, "opening %s", argv[1]);
+    tag = argv[1];
+  } else {
+    // Read track list from standard input
+    input = stdin;
+    tag = "stdin";
+  }
+  vector_init(v);
+  while(inputline(tag, input, &l, '\n')) {
+    if(!strcmp(l, "."))
+      break;
+    vector_append(v, l);
+  }
+  if(ferror(input))
+    fatal(errno, "reading %s", tag);
+  if(input != stdin)
+    fclose(input);
+  if(disorder_playlist_lock(getclient(), argv[0])
+     || disorder_playlist_set(getclient(), argv[0], v->vec, v->nvec)
+     || disorder_playlist_unlock(getclient()))
+    exit(EXIT_FAILURE);
+}
+
 static const struct command {
   const char *name;
   int min, max;
@@ -475,22 +650,22 @@ static const struct command {
   int (*isarg)(const char *);
   const char *argstr, *desc;
 } commands[] = {
-  { "adduser",        2, 3, cf_adduser, isarg_rights, "USER PASSWORD [RIGHTS]",
+  { "adduser",        2, 3, cf_adduser, isarg_rights, "USERNAME PASSWORD [RIGHTS]",
                       "Create a new user" },
   { "allfiles",       1, 2, cf_allfiles, isarg_regexp, "DIR [~REGEXP]",
                       "List all files and directories in DIR" },
-  { "authorize",      1, 2, cf_authorize, isarg_rights, "USER [RIGHTS]",
-                      "Authorize USER to connect to the server" },
-  { "deluser",        1, 1, cf_deluser, 0, "USER",
-                      "Delete a user" },
+  { "authorize",      1, 2, cf_authorize, isarg_rights, "USERNAME [RIGHTS]",
+                      "Authorize user USERNAME to connect" },
+  { "deluser",        1, 1, cf_deluser, 0, "USERNAME",
+                      "Delete user USERNAME" },
   { "dirs",           1, 2, cf_dirs, isarg_regexp, "DIR [~REGEXP]",
                       "List directories in DIR" },
   { "disable",        0, 0, cf_disable, 0, "",
                       "Disable play" },
   { "disable-random", 0, 0, cf_random_disable, 0, "",
                       "Disable random play" },
-  { "edituser",       3, 3, cf_edituser, 0, "USER PROPERTY VALUE",
-                      "Set a property of a user" },
+  { "edituser",       3, 3, cf_edituser, 0, "USERNAME PROPERTY VALUE",
+                      "Set a property of user USERNAME" },
   { "enable",         0, 0, cf_enable, 0, "",
                       "Enable play" },
   { "enable-random",  0, 0, cf_random_enable, 0, "",
@@ -519,6 +694,14 @@ static const struct command {
                       "Add TRACKS to the end of the queue" },
   { "playing",        0, 0, cf_playing, 0, "",
                       "Report the playing track" },
+  { "playlist-del",   1, 1, cf_playlist_del, 0, "PLAYLIST",
+                      "Delete a playlist" },
+  { "playlist-get",   1, 1, cf_playlist_get, 0, "PLAYLIST",
+                      "Get the contents of a playlist" },
+  { "playlist-set",   1, 2, cf_playlist_set, isarg_filename, "PLAYLIST [PATH]",
+                      "Set the contents of a playlist" },
+  { "playlists",      0, 0, cf_playlists, 0, "",
+                      "List playlists" },
   { "prefs",          1, 1, cf_prefs, 0, "TRACK",
                       "Display all the preferences for TRACK" },
   { "quack",          0, 0, cf_quack, 0, 0, 0 },
@@ -542,6 +725,16 @@ static const struct command {
                       "Resume after a pause" },
   { "rtp-address",    0, 0, cf_rtp_address, 0, "",
                       "Report server's broadcast address" },
+  { "schedule-del",   1, 1, cf_schedule_del, 0, "EVENT",
+                      "Delete a scheduled event" },
+  { "schedule-list",  0, 0, cf_schedule_list, 0, "",
+                      "List scheduled events" },
+  { "schedule-play",  3, 3, cf_schedule_play, 0, "WHEN PRI TRACK",
+                      "Play TRACK later" },
+  { "schedule-set-global", 4, 4, cf_schedule_set_global, 0, "WHEN PRI NAME VAL",
+                      "Set a global preference later" },
+  { "schedule-unset-global", 3, 3, cf_schedule_unset_global, 0, "WHEN PRI NAME",
+                      "Unset a global preference later" },
   { "scratch",        0, 0, cf_scratch, 0, "",
                       "Scratch the currently playing track" },
   { "scratch-id",     1, 1, cf_scratch, 0, "ID",
@@ -566,8 +759,8 @@ static const struct command {
                       "Unset a preference" },
   { "unset-global",   1, 1, cf_unset_global, 0, "NAME",
                       "Unset a global preference" },
-  { "userinfo",       2, 2, cf_userinfo, 0, "USER PROPERTY",
-                      "Get a property of as user" },
+  { "userinfo",       2, 2, cf_userinfo, 0, "USERNAME PROPERTY",
+                      "Get a property of a user" },
   { "users",          0, 0, cf_users, 0, "",
                       "List all users" },
   { "version",        0, 0, cf_version, 0, "",
@@ -612,6 +805,7 @@ int main(int argc, char **argv) {
   pcre_malloc = xmalloc;
   pcre_free = xfree;
   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
+  if(!setlocale(LC_TIME, "")) fatal(errno, "error calling setlocale");
   while((n = getopt_long(argc, argv, "+hVc:dHlNu:p:", options, 0)) >= 0) {
     switch(n) {
     case 'h': help();
@@ -639,7 +833,7 @@ int main(int argc, char **argv) {
   optind = 1;                          /* for subsequent getopt calls */
   /* accumulate command args */
   while(n < argc) {
-    if((i = TABLE_FIND(commands, struct command, name, argv[n])) < 0)
+    if((i = TABLE_FIND(commands, name, argv[n])) < 0)
       fatal(0, "unknown command '%s'", argv[n]);
     if(n + commands[i].min >= argc)
       fatal(0, "missing arguments to '%s'", argv[n]);