diff --git a/man/Makefile.am b/man/Makefile.am index 4699b3a78b65a5dbcd83d5e2969ab2304644b8f8..ffa94f9cc1a269ad0b89716faee4669bb59730ea 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,4 +1,4 @@ -lkt_manpages = lektor.man lektord.man lkt.man +lkt_manpages = lektor.man lektord.man lkt.man lkt.old.man notrans_dist_man1_MANS = $(lkt_manpages) MAN = $(abs_top_srcdir)/scripts/manpage.bash CLEANFILES = $(lkt_manpages) diff --git a/man/Makefile.in b/man/Makefile.in index e464bfa443983992fe300025f81a5378b2bf82e4..9f4e3af0fff28732aacddc411a35f3b25ac6f91d 100644 --- a/man/Makefile.in +++ b/man/Makefile.in @@ -286,7 +286,7 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -lkt_manpages = lektor.man lektord.man lkt.man +lkt_manpages = lektor.man lektord.man lkt.man lkt.old.man notrans_dist_man1_MANS = $(lkt_manpages) CLEANFILES = $(lkt_manpages) all: all-am diff --git a/man/lkt.old.template b/man/lkt.old.template new file mode 100644 index 0000000000000000000000000000000000000000..3ae343ed531cb4e09e830ab6283cb351a623ce87 --- /dev/null +++ b/man/lkt.old.template @@ -0,0 +1,271 @@ +.TH "LKT" "1" "___DATE___" "LEKTOR MK 7" +.SH "NAME" + +lkt \- the lektor command line client + +.SH "OVERVIEW" +\fBlkt\fP is the \fBlektord\fP client, much like \fBmpc\fP but with +more functionnalities like \fIplaylists\fP and \fIstickers\fP management. +.PP +Commands can be used with the minimal name that permit them to be +distinguished from others commands. Thus, it is possible to use the +string \fIadm\fP as an alias for \fIadmin\fP. This is like cisco switch +command line interface. +.PP +You may be interested in other related programs like: +.PP +.PD 0 +.TP +\fIlektord\fP The lektor daemon +.TP +.PD + +.SH "COMMANDS" +Here are the \fBlkt\fP commands: + +.PP +\fIBASE-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBcurrent\fP +Prints informations about the currently playing kara. Can be used to +display the current kara in a status bar like \fBxmobar\fP or in the +\fBi3 panel\fP +.TP +\fBplay\fP [index] +Toggle play/pause state. If the playback is stopped, start at a possibly +specified index +.TP +\fBnext\fP +Play next kara in the queue +.TP +\fBprevious\fP +Play the previous kara in the queue +.TP +\fBshuffle\fP +Shuffle the queue. If the state was stoppped, play from the first kara +in the queue. If lektor was already playing a kara, it will play it +from the begening. The current kara will be placed in first position +in the queue. +.TP +\fBstatus\fP +Prints information about the state of lektor and the currently playing kara +.TP +\fBstop\fP +Stop the playback and reading the queue, the state is now \fIstopped\fP. +.PP +\fIPLAYLIST-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBplt create\fP <plt-name> +Creates a playlist, do nothing if it was already present +.TP +\fBplt destroy\fP <plt-name> +Delete a playlist with all its content, do nothing if the playlist didn't exists +.TP +\fBplt delete\fP <plt-name> <query> +Deletes karas from a playlist with a valid \fIquery\fP +.TP +\fBplt add\fP <plt-name> <query> +Adds karas to a playlist with a valid \fIquery\fP +.TP +\fBplt list\fP <plt-name> +List the content of the playlist named <plt-name> +.TP +\fBplt list\fP +List all the available playlists +.PP + +\fIQUEUE-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBqueue\fP [count] +Prints the names and ids of the next karas in the queue +.TP +\fBqueue pos\fP <pos | from:to> +Prints the names and ids of karas in the queue. Karas can be designated by +their position or with a range +.TP +\fBqueue pop\fP +Delete the currently playing kara from the queue and pass to the next one. +This can work only if the currently playong kara is not the last +.TP +\fBqueue add\fP <query> +Add karas to the queue at the end of it with a valid query +.TP +\fBqueue insert\fP <query> +Add karas to the queue just after the currently playing kara +.TP +\fBqueue seek\fP <id> +Goto to the kara with the specified id in the queue +.TP +\fBqueue delete\fP <id> +Delete karas from the playlist with their id. You can't delete the currently +playing kara, for that use the \fBpop\fP queue command +.TP +\fBqueue clear\fP +Clear the queue and set the state to \fIstopped\fP +.TP +\fBqueue crop\fP +Crop the queue, delete every kara from it appart from the currently +playing one +.TP +\fBqueue replace\fP <plt-name> +Replace the queue with the content of a playlist. Keep the playling state +.PP + +\fISEARCH-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBsearch database\fP <query> +Search and prints the kara that correspond to the query in the database +.TP +\fBsearch plt\fP <plt-name> <query> +Search, prints and add to an existing playlist the karas that match +the query +.TP +\fBsearch plt\fP <plt-name> +List the content of the playlist named <plt-name> +.TP +\fBsearch plt\fP +List all the available playlists +.TP +\fBsearch count\fP <query> +Search and prints the number of karas that match the query +.TP +\fBsearch get\fP <id> +Get informations about a specific id +.TP +\fBsearch queue\fP <query> +Search in the queue and prints the karas that match the query +.PP + +\fISTICKERS-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBsticker create\fP <name> +Create a sticker that can be used to tag \fIkara\fP and \fIplt\fP objects +.TP +\fBsticker get\fP <type> <uri> [ <name> [ <op> <value> ] ] +List the stickers of an object \fIuri\fP. The object \fItype\fP can be +\fIkara\fP or \fIplt\fP + +A condition can be defined on the value of the sticker with an operator +\fIop\fP and an integer value \fIvalue\fP. Supported operators are \fIl\fP +for 'less than', \fIe\fP for 'equal to' and \fIg\fP for 'greater than'. +Operations are not strict +.TP +\fBsticker set\fP <type> <uri> <name> <value> +Set the value of a sticker \fIname\fP to \fIvalue\fP for the object with the +id \fIuri\fP +.TP +\fBsticker delete\fP <type> <uri> [name] +Delete all the stickers or only one (specified by \fIname\fP) of the object +with the id \fIuri\fP +.PP + +\fIADMIN-COMMANDS\fP +.PP +.PD 0 +.TP +.PD +\fBadmin ping\fP +Pings the lektord daemon, prints \fIOK\fP only if the ping succeeded +.TP +\fBadmin kill\fP +Kill the lektord daemon +.TP +\fBadmin restart\fP +Try to restart the lektord daemon +.TP +\fBadmin rescan\fP +Rescan karas from the filesystem. New karas that are not in the database +will be added to it. Don't synchronize from the repo +.TP +\fBadmin populate\fP +Force a rescan (without taking into account the timestamp). Can be used +to bootstrap the database from a filesystem +.TP +\fBadmin update\fP +Update the base from the \fIKurisu\fP repo. Don't scan for new files in +the filesystem +.TP +\fBadmin config\fP +Prints to \fIstdout\fP the default configuration file +.PP + +.SH "OPTIONS" +Options can be passed to \fBlkt\fP before specifying the command in a +\fIoption=value\fP format. This is done this way to allow one to make +an alias of the \fBlkt\fP command. +.PP +The possible options are the following: +.PP +.PD 0 +.TP +.PD +\fBhost\fP +The hostname or the IP of the machine where the \fBlektord\fP daemon +is running +.TP +\fBport\fP +The port on which the \fBlektord\fP daemon is listening +.TP +\fBpwd\fP +The password to use for commands that require authentification. This is the +case of most of the \fIadmin\fP commands +.PP + +.SH "QUERIES" +Queries are a way of listing karas in the database. They are composed of a +type and the next of the line is the SQL regex that the kara must verify. +In SQL regexes, the wildcard is the "%" character and the jocker the +character "_". Queries are case insensitive. +.PP +Valid types for a query are the following: \fIid\fP, \fIlang\fP, \fItype\fP, +\fIcategory\fP, \fIauthor\fP, \fIquery\fP and \fIplaylist\fP. +.PP +For the type \fItype\fP, the valid values are the following: \fIOP\fP, +\fIED\fP, \fIIS\fP, \fIAMV\fP, \fIVOCA\fP, \fIMV\fP, \fIPV\fP and \fILIVE\fP. +.PP +For the type \fIlang\fP, the valid values are the following: \fIjp\fP, +\fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP, \fImulti\fP +and \fIundefined\fP. +.PP +For the \fIcategory\fP type, the valid values are the following: +\fIvo\fP, \fIva\fP, \fIcdg\fP, \fIamv\fP, \fIvocaloid\fP and \fIautres\fP. +.PP +Here are some examples of queries: +.PP +.PD 0 +.TP +.PD +\fBtype OP\fP +Select karas that are openings +.TP +\fBauthor kubat\fP +Select karas where the author is "kubat" +.PP + +.SH "EXAMPLES" +Valid invocations of the \fBlkt\fP command are the following: +.PP +.PD 0 +.TP +.PD +\fBlkt host=sakura port=6601 pwd=toto admin restart\fP +Restart the lektord daemon on the \fIsakura\fP PC. This daemon is listening on +the port \fI6601\fP and the password of the admin user is \fItoto\fP +.TP +\fBlkt q a author krocoh\fP +Add kara that Krocoh has done to the queue diff --git a/src/Makefile.am b/src/Makefile.am index b95770d7d3c592a42eadd389e0c620708c12b9d9..eac0e4cbee317428865ae8093a201f975783beb1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,9 +84,10 @@ EXTRA_DIST = database/disk.sql database/memory.sql ## MAIN FILES ## ################ -bin_PROGRAMS = lektord lkt +bin_PROGRAMS = lektord lkt.old lkt ## The lkt client +lkt_old_SOURCES = main/lkt-old.c base/cmd.c base/common.c lkt_SOURCES = main/lkt.c base/cmd.c base/common.c ## The lektord server diff --git a/src/Makefile.in b/src/Makefile.in index d824d34b88da889e9d0336697d3320b003268696..0c7d8c290652282b4d1e4483e64548e4662b3e77 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -99,7 +99,7 @@ host_triplet = @host@ # TODO pour le Kubat du futur: c'est pas beau, trouver un truc pour que ça se fasse tout seul @LKT_STATIC_MODULE_TRUE@am__append_8 = module/thread.c -bin_PROGRAMS = lektord$(EXEEXT) lkt$(EXEEXT) +bin_PROGRAMS = lektord$(EXEEXT) lkt.old$(EXEEXT) lkt$(EXEEXT) subdir = src ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/config/m4/libtool.m4 \ @@ -212,6 +212,10 @@ am_lkt_OBJECTS = main/lkt.$(OBJEXT) base/cmd.$(OBJEXT) \ base/common.$(OBJEXT) lkt_OBJECTS = $(am_lkt_OBJECTS) lkt_LDADD = $(LDADD) +am_lkt_old_OBJECTS = main/lkt-old.$(OBJEXT) base/cmd.$(OBJEXT) \ + base/common.$(OBJEXT) +lkt_old_OBJECTS = $(am_lkt_old_OBJECTS) +lkt_old_LDADD = $(LDADD) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false @@ -245,7 +249,8 @@ am__depfiles_remade = base/$(DEPDIR)/cmd.Po base/$(DEPDIR)/common.Po \ database/$(DEPDIR)/liblektor_la-queue.Plo \ database/$(DEPDIR)/liblektor_la-stickers.Plo \ database/$(DEPDIR)/liblektor_la-update.Plo \ - database/$(DEPDIR)/liblektor_la-user.Plo main/$(DEPDIR)/lkt.Po \ + database/$(DEPDIR)/liblektor_la-user.Plo \ + main/$(DEPDIR)/lkt-old.Po main/$(DEPDIR)/lkt.Po \ main/$(DEPDIR)/server.Po mkv/$(DEPDIR)/liblektor_la-mkv.Plo \ mkv/$(DEPDIR)/liblektor_la-utils.Plo \ mkv/$(DEPDIR)/liblektor_la-write.Plo \ @@ -277,10 +282,11 @@ am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(liblektor_la_SOURCES) $(liblktmodrepo_la_SOURCES) \ - $(liblktmodsdl_la_SOURCES) $(lektord_SOURCES) $(lkt_SOURCES) + $(liblktmodsdl_la_SOURCES) $(lektord_SOURCES) $(lkt_SOURCES) \ + $(lkt_old_SOURCES) DIST_SOURCES = $(am__liblektor_la_SOURCES_DIST) \ $(liblktmodrepo_la_SOURCES) $(liblktmodsdl_la_SOURCES) \ - $(lektord_SOURCES) $(lkt_SOURCES) + $(lektord_SOURCES) $(lkt_SOURCES) $(lkt_old_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -477,6 +483,7 @@ liblektor_la_LDFLAGS = -avoid-version -pthread -lsqlite3 \ @LKT_STATIC_MODULE_TRUE@liblektor_la_LIBADD = liblktmodsdl.la liblktmodrepo.la CLEANFILES = database/disk.c database/memory.c EXTRA_DIST = database/disk.sql database/memory.sql +lkt_old_SOURCES = main/lkt-old.c base/cmd.c base/common.c lkt_SOURCES = main/lkt.c base/cmd.c base/common.c lektord_SOURCES = main/server.c lektord_LDADD = liblektor.la @@ -744,6 +751,12 @@ base/common.$(OBJEXT): base/$(am__dirstamp) \ lkt$(EXEEXT): $(lkt_OBJECTS) $(lkt_DEPENDENCIES) $(EXTRA_lkt_DEPENDENCIES) @rm -f lkt$(EXEEXT) $(AM_V_CCLD)$(LINK) $(lkt_OBJECTS) $(lkt_LDADD) $(LIBS) +main/lkt-old.$(OBJEXT): main/$(am__dirstamp) \ + main/$(DEPDIR)/$(am__dirstamp) + +lkt.old$(EXEEXT): $(lkt_old_OBJECTS) $(lkt_old_DEPENDENCIES) $(EXTRA_lkt_old_DEPENDENCIES) + @rm -f lkt.old$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(lkt_old_OBJECTS) $(lkt_old_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) @@ -782,6 +795,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@database/$(DEPDIR)/liblektor_la-stickers.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@database/$(DEPDIR)/liblektor_la-update.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@database/$(DEPDIR)/liblektor_la-user.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/lkt-old.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/lkt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mkv/$(DEPDIR)/liblektor_la-mkv.Plo@am__quote@ # am--include-marker @@ -1203,6 +1217,7 @@ distclean: distclean-am -rm -f database/$(DEPDIR)/liblektor_la-stickers.Plo -rm -f database/$(DEPDIR)/liblektor_la-update.Plo -rm -f database/$(DEPDIR)/liblektor_la-user.Plo + -rm -f main/$(DEPDIR)/lkt-old.Po -rm -f main/$(DEPDIR)/lkt.Po -rm -f main/$(DEPDIR)/server.Po -rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo @@ -1281,6 +1296,7 @@ maintainer-clean: maintainer-clean-am -rm -f database/$(DEPDIR)/liblektor_la-stickers.Plo -rm -f database/$(DEPDIR)/liblektor_la-update.Plo -rm -f database/$(DEPDIR)/liblektor_la-user.Plo + -rm -f main/$(DEPDIR)/lkt-old.Po -rm -f main/$(DEPDIR)/lkt.Po -rm -f main/$(DEPDIR)/server.Po -rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo diff --git a/src/main/lkt-old.c b/src/main/lkt-old.c new file mode 100644 index 0000000000000000000000000000000000000000..f6c0987ca2c0b8398f4edb2abee19296cb914196 --- /dev/null +++ b/src/main/lkt-old.c @@ -0,0 +1,1154 @@ +#define _POSIX_C_SOURCE 200809L + +#include <lektor/common.h> +#include <lektor/net.h> +#include <lektor/cmd.h> +#include <lektor/config.h> + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <signal.h> +#include <locale.h> +#include <stdnoreturn.h> +#include <stdbool.h> +#include <stdarg.h> +#include <limits.h> + +#define LKT_KEY_VALUE_SEP ": \n\t\0" +#define fail_if(cond, msg) { if (cond) { LOG_ERROR("LKT", "%s", msg); exit(EXIT_FAILURE); } } +#define fail(msg) { LOG_ERROR("LKT", "%s", msg); exit(EXIT_FAILURE); } + +#define exit_with_status(sock, buff) { \ + read_socket(sock, buff, 2); \ + fail_if(buff[0] != 'O' && buff[1] != 'K', "ACK"); \ + exit(EXIT_SUCCESS); \ +} + +/* Type definition. */ + +typedef struct { + const char *host; /* Serveur host, may be resolved. */ + const char *port; /* Serveur port. */ + const char *pwd; /* The password for the user. */ + const char **argv; /* Pointer to the argv from the main function. */ + int argc; /* Argument count of the args. */ +} args_t; + +/* Global variables. */ + +static const char *host; +static const char *port; +static const char *password; +static const char *LKT_QUEUE_DEFAULT[] = { "10" }; + +/* Communication functions and fonction that interact with lektor stuff. */ + +static int +lkt_valid_type(const char *type) +{ + return (STR_MATCH(type, "query") || + STR_MATCH(type, "category") || + STR_MATCH(type, "author") || + STR_MATCH(type, "lang") || + STR_MATCH(type, "id") || + STR_MATCH(type, "type") || + STR_MATCH(type, "playlist")); +} + +static int +read_socket(FILE *sock, char *buff, size_t max_len) +{ + size_t i, len; + for (i = 0; i < max_len; ++i) { + len = fread(buff + i, sizeof(char), 1, sock); + if (buff[i] == '\n' || len != 1) + break; + } + fail_if(len == 0, "Connexion error"); + return i; +} + +static FILE * +create_socket(const char *host, const char *port) +{ + FILE *socket_desc; + struct addrinfo hints, *found; + socklen_t sock_len; + struct sockaddr sock; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + getaddrinfo(host, port, &hints, &found); // <---- Another assert yeeted because it makes php panic + + sock = *found->ai_addr; + sock_len = found->ai_addrlen; + freeaddrinfo(found); + + int cx = socket(AF_INET, SOCK_STREAM, 0); + + if (cx <= 0 || connect(cx, &sock, sock_len)) + exit(EXIT_FAILURE); + + socket_desc = fdopen(cx, "r+"); + fail_if(!socket_desc, "Failed to connect to lektord"); + return socket_desc; +} + +static FILE * +lkt_connect(void) +{ + char buff[LKT_MESSAGE_MAX]; + FILE *sock = create_socket(host, port); + read_socket(sock, buff, LKT_MESSAGE_MAX); + return sock; +} + +static void +write_socket(FILE *sock, const char *format, ...) +{ + int size; + char buff[LKT_MESSAGE_MAX]; + va_list ap; + + va_start(ap, format); + size = vsnprintf(buff, LKT_MESSAGE_MAX - 1, format, ap); + buff[LKT_MESSAGE_MAX - 1] = 0; + + if (size < 0) + fail("Connexion error"); + + if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size) + fail("Connexion error"); + + va_end(ap); +} + +noreturn static inline void +lkt_send_and_exit(const char *buffer, size_t len) +{ + write_socket(lkt_connect(), buffer, len); + exit(EXIT_SUCCESS); +} + +static char * +lkt_skip_key(char *buffer) +{ + size_t len = strcspn(buffer, LKT_KEY_VALUE_SEP); + char *ret = &buffer[len + 1]; + len = strspn(ret, ": "); + return ret + len; +} + +static inline void +send_cmd_with_uri(FILE *sock, char *cmd, int argc, const char **argv) +{ + int i; + char buf[LKT_MESSAGE_MAX] = {0}; + for (i = 1; i < argc - 1; ++i) { + strncat(buf, argv[i], LKT_MESSAGE_MAX - 1); + strncat(buf, " ", LKT_MESSAGE_MAX - 1); + } + strncat(buf, argv[i], LKT_MESSAGE_MAX - 1); + strncat(buf, "\n", LKT_MESSAGE_MAX - 1); + write_socket(sock, "%s %s://%s", cmd, argv[0], buf); +} + +/* Functions implementing options. */ + +#define just_send_one_arg(func, cmd) \ +noreturn void func (struct cmd_args *args) { \ + fail_if(args->argc != 1, "Invalid argument"); \ + FILE *sock = lkt_connect(); \ + char buff[2]; \ + write_socket(sock, cmd " %s\n", args->argv[0]); \ + exit_with_status(sock, buff); \ +} +just_send_one_arg(stickers_create__, "sticker __create") +just_send_one_arg(stickers_destroy__, "sticker __destroy") +just_send_one_arg(plt_destroy__, "rm") +just_send_one_arg(plt_create__, "playlistadd") +just_send_one_arg(queue_dump__, "__dump") +#undef just_send_one_arg + +#define just_send(func, msg) /* Just send a simple string functions */ \ + noreturn void func (struct cmd_args *args) { \ + fail_if(args->argc, "This command takes no arguments"); \ + lkt_send_and_exit(msg, sizeof(msg)); \ + } +just_send(queue_clear__, "clear\n") +just_send(queue_crop__, "crop\n") +just_send(next__, "next\n") +just_send(prev__, "previous\n") +just_send(stop__, "stop\n") +just_send(shuffle__, "shuffle\nplay\n") +#undef just_send + +noreturn void +simple_send_with_password__(const char *cmd) +{ + if (!password) + fail("Password not provided"); + FILE *sock = lkt_connect(); + write_socket(sock, "command_list_begin\n"); + write_socket(sock, "password %s\n", password); + write_socket(sock, "%s\n", cmd); + write_socket(sock, "command_list_end\n"); + exit(EXIT_SUCCESS); +} + +noreturn void +restart__(struct cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the previous command takes no arguments"); + simple_send_with_password__("__restart"); +} + +noreturn void +kill__(struct cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the previous command takes no arguments"); + simple_send_with_password__("kill"); +} + +noreturn void +rescan_or_update__(struct cmd_args *args, const char *cmd) +{ + if (!password) + fail("Password not provided"); + FILE *sock = lkt_connect(); + char buff[LKT_MESSAGE_MAX]; + int i; + + /* All the db */ + if (args->argc == 0) { + write_socket(sock, "command_list_begin\n"); + write_socket(sock, "password %s\n", password); + write_socket(sock, "%s\n", cmd); + write_socket(sock, "command_list_end\n"); + } + + /* With a query */ + else { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + for (i = 0; i < args->argc; ++i) + strncat(buff, args->argv[i], LKT_MESSAGE_MAX - 1); + write_socket(sock, "command_list_begin\n"); + write_socket(sock, "password %s\n", password); + write_socket(sock, "%s %s\n", cmd, buff); + write_socket(sock, "command_list_end\n"); + } + + exit(EXIT_SUCCESS); +} + +noreturn void +rescan__(struct cmd_args *args) +{ + rescan_or_update__(args, "rescan"); +} + +noreturn void +update__(struct cmd_args *args) +{ + rescan_or_update__(args, "update"); +} + +noreturn void +populate__(struct cmd_args *args) +{ + rescan_or_update__(args, "__rescan"); +} + +noreturn void +queue_replace__(struct cmd_args *args) +{ + if (args->argc != 1) + fail("Invalid argument"); + bool play = false; + char buff[LKT_MESSAGE_MAX]; + FILE *sock = lkt_connect(); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + + if (STR_NMATCH(buff, "state", len)) { + char *it = lkt_skip_key(buff); + play = STR_NMATCH(it, "play", 4); + break; + } + + if (STR_NMATCH(buff, "OK", 2)) + break; + else if (STR_NMATCH(buff, "ACK", 3)) + fail("ACK"); + } + + fclose(sock); + sock = lkt_connect(); + write_socket(sock, "command_list_begin\n"); + write_socket(sock, "clear\n"); + write_socket(sock, "add playlist://%s\n", args->argv[0]); + if (play) + write_socket(sock, "play\n"); + write_socket(sock, "command_list_end\n"); + exit_with_status(sock, buff); +} + +noreturn void +config__(struct cmd_args *args) +{ + int ret = EXIT_SUCCESS; + if (args->argc == 0) + fwrite(lkt_default_config_file, sizeof(char), + strlen(lkt_default_config_file), stdout); + exit(ret); +} + +noreturn void +play__(struct cmd_args *args) +{ + if (args->argc > 1) + fail("Invalid argument"); + + int pos = 0; + if (args->argc != 0) + pos = atoi(args->argv[0]); + + static const char status__[] = "status\n"; + static const char cmd_play__[] = "play\n"; + static const char cmd_play_from__[] = "play %d\n"; + static const char cmd_pause__[] = "pause\n"; + + char buff[LKT_MESSAGE_MAX]; + FILE *sock = lkt_connect(); + write_socket(sock, status__); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + + if (STR_NMATCH(buff, "state", len)) { + fclose(sock); + + if (STR_NMATCH(lkt_skip_key(buff), "stop", 4)) { + if (!pos) + lkt_send_and_exit(cmd_play__, sizeof(cmd_play__)); /* In bytes. */ + else { + write_socket(lkt_connect(), cmd_play_from__, pos); + exit(EXIT_SUCCESS); + } + } + + else + lkt_send_and_exit(cmd_pause__, sizeof(cmd_pause__)); /* In bytes. */ + + exit(EXIT_FAILURE); + } + } +} + +noreturn void +ping__(struct cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the ping command takes no arguments"); + char buff[6] = {0}; + FILE *sock = lkt_connect(); + write_socket(sock, "ping\n"); + read_socket(sock, buff, 6 * sizeof(char)); + if (!STR_NMATCH(buff, "OK", 2)) + fail("ACK"); + exit(write(1, "OK\n", sizeof("OK\n")) == 0); +} + +noreturn void +current__(struct cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the current command takes no arguments"); + + static const char current_song__[] = "currentsong\n"; + char buff[LKT_MESSAGE_MAX]; + + char *mem = NULL; + FILE *sock = lkt_connect(); + + write_socket(sock, current_song__); + assert(mem = calloc(6 * LKT_MESSAGE_MAX, sizeof(char))); + assert(memset(mem, 0, 6 * LKT_MESSAGE_MAX * sizeof(char))); + + char *const title = &mem[0]; + char *const author = &mem[LKT_MESSAGE_MAX]; + char *const source = &mem[2 * LKT_MESSAGE_MAX]; + char *const type = &mem[3 * LKT_MESSAGE_MAX]; + char *const category = &mem[4 * LKT_MESSAGE_MAX]; + char *const language = &mem[5 * LKT_MESSAGE_MAX]; + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + const size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + char *value = lkt_skip_key(buff); + const size_t value_size = strcspn(value, "\n\0") * sizeof(char); + + if (STR_NMATCH("title", buff, len)) + assert(memcpy(title, value, value_size)); + + if (STR_NMATCH("author", buff, len)) + assert(memcpy(author, value, value_size)); + + if (STR_NMATCH("source", buff, len)) + assert(memcpy(source, value, value_size)); + + if (STR_NMATCH("type", buff, len)) + assert(memcpy(type, value, value_size)); + + if (STR_NMATCH("language", buff, len)) + assert(memcpy(language, value, value_size)); + + if (STR_NMATCH("category", buff, len)) + assert(memcpy(category, value, value_size)); + + /* At this point every key has been parsed. */ + if (STR_NMATCH(buff, "OK", 2)) + goto ok; + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + } + +ok: + if (language[0]) + printf("%s - %s / %s - %s - %s [ %s ]\n", + category, language, source, type, title, author); + else + printf("%s / %s - %s - %s [ %s ]\n", + category, source, type, title, author); + + exit(EXIT_SUCCESS); +} + +noreturn void +queue_pop__(struct cmd_args *args) +{ + fail_if(args->argc, "Invalid argument"); + int songid = 0; + char buff[LKT_MESSAGE_MAX]; + FILE *sock = lkt_connect(); + + /* Get lektor's status. */ + write_socket(sock, "status\n"); + +#define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; } + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + assign_int("songid", songid) + + /* At this point every key has been parsed. */ + if (STR_NMATCH(buff, "OK", 2)) + break; + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + } +#undef assign_int + + fclose(sock); + sock = lkt_connect(); + if (!songid) + exit(EXIT_FAILURE); + write_socket(sock, "next\ndeleteid %d\n", songid); + exit_with_status(sock, buff); +} + +noreturn void +status__(struct cmd_args *args) +{ + if (args->argc != 0) + fail("Invalid argument, the status command takes no arguments"); + + static const char *const status_str__ = "status\n"; + static const char *const stats_str__ = "stats\n"; + + char buff[LKT_MESSAGE_MAX]; + char flags[24]; + + bool play = false, stopped = true, is_updating = false; + int time_pos = 0, time_duration = 0, song_index = 0, plt_len = 0, ret = EXIT_FAILURE, it = 0; + size_t len; + time_t update_ts = 0; + + memset(flags, 0, 24 * sizeof(char)); + FILE *sock = lkt_connect(); + +#define assign_flag(str, f) if (STR_NMATCH(buff, str, len) && (atoi(lkt_skip_key(buff)) == 1)) { flags[it++] = f; continue; } +#define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (strtoll(lkt_skip_key(buff), NULL, 0)); continue; } +#define check_end() \ + if (STR_NMATCH(buff, "OK", 2)) break; \ + else if (STR_NMATCH(buff, "ACK", 3)) goto error; + + /* Get lektor's status */ + write_socket(sock, status_str__); + for (;;) { + len = read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + buff[len] = '\0'; + len = strcspn(buff, LKT_KEY_VALUE_SEP); + + if (STR_NMATCH(buff, "state", len)) { + char *it = lkt_skip_key(buff); + play = STR_NMATCH(it, "play", 4); + stopped = STR_NMATCH(it, "stop", 4); + continue; + } + + assign_flag("random", 'r') + assign_flag("repeat", 'l') + assign_flag("single", 's') + assign_flag("consume", 'c') + + assign_int("elapsed", time_pos) + assign_int("duration", time_duration) + assign_int("song", song_index) + assign_int("playlistlength", plt_len) + + check_end() + } + + /* Get lektor's stats */ + write_socket(sock, stats_str__); + for (;;) { + len = read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + buff[len] = '\0'; + len = strcspn(buff, LKT_KEY_VALUE_SEP); + + assign_int("__is_updating", is_updating) + assign_int("db_update", update_ts) + + check_end() + } + + /* End of communication */ + fclose(sock); + +#undef assign_flag +#undef assign_int +#undef check_end + + struct tm *p_tm = localtime(&update_ts); + len = strftime(buff, LKT_MESSAGE_MAX - 1, "%F %H:%M:%S", p_tm); + buff[len] = '\0'; + + int pla_m = time_pos / 60; + char pla_s = time_pos % 60; + int dur_m = time_duration / 60; + char dur_s = time_duration % 60; + + printf("Lektor: %s\n" + "Queue: #%d/%d\n" + "Playback: %d:%d/%d:%d\n" + "Flags: %s\n" + "%s: %s\n", + play ? "play" : "stopped", + song_index + 1, plt_len, + pla_m, pla_s, dur_m, dur_s, + flags[0] ? flags : "(none)", + is_updating ? "Updating" : "Last update", buff); + + /* If there is a kara loaded in mpv, get this kara. */ + if (!stopped) { + printf("Kara: "); + current__(args); + } + + ret = EXIT_SUCCESS; +error: + exit(ret); +} + +noreturn void +queue_delete__(struct cmd_args *args) +{ + if (args->argc != 1) + fail("Invalid argument, need onlt one argument: queue delete <id>"); + + static const char *cmd_id__ = "deleteid %d\n"; + int dumy = 0; + FILE *sock = lkt_connect(); + char buff[3]; + + sscanf(args->argv[0], "%d", &dumy); + if (dumy != 0) { + write_socket(sock, cmd_id__, dumy); + exit_with_status(sock, buff); + } + + fail("Invalid argument"); +} + +noreturn void +queue_add__(struct cmd_args *args) +{ + char buff[3]; + fail_if(args->argc < 1, "Invalid arguments"); + fail_if(!lkt_valid_type(args->argv[0]), "Invalid query type"); + FILE *sock = lkt_connect(); + send_cmd_with_uri(sock, "add", args->argc, args->argv); + exit_with_status(sock, buff); +} + +noreturn void +queue_insert__(struct cmd_args *args) +{ + char buff[3]; + fail_if(args->argc < 1, "Invalid arguments"); + fail_if(!lkt_valid_type(args->argv[0]), "Invalid query type"); + FILE *sock = lkt_connect(); + send_cmd_with_uri(sock, "__insert", args->argc, args->argv); + exit_with_status(sock, buff); +} + +noreturn void +queue_seek__(struct cmd_args *args) +{ + fail_if(args->argc != 1, "The seek command needs one argument: queue seek <id>"); + + char *endptr, buf[3]; + long id = strtol(args->argv[0], &endptr, 0); + + if ((errno == ERANGE && (id == LONG_MAX || id == LONG_MIN)) || + (errno != 0 && id == 0) || + (endptr == args->argv[0])) + fail("Invalid argument, not an integer"); + + if (*endptr != '\0') + fail("Invalid argument, must be only one integer"); + + FILE *sock = lkt_connect(); + write_socket(sock, "playid %ld\n", id); + exit_with_status(sock, buf); +} + +noreturn void +queue_pos__(struct cmd_args *args) +{ + char buff[LKT_MESSAGE_MAX], *endptr; + + if (args->argc != 1) + fail("Invalid argument for the pos command: queue pos <arg> where arg is a position or a range"); + + long continuation = 0, up = 0; + + continuation = strtol(args->argv[0], &endptr, 0); + if ((errno == ERANGE && (continuation == LONG_MAX || continuation == LONG_MIN)) || (errno != 0 + && continuation == 0) || (endptr == args->argv[0])) + fail("Invalid argument, not an integer: queue pos <arg> where arg is a position or a range"); + + if (*endptr != '\0') { + /* A range */ + if (*(++endptr) == '\0') + fail("Invalid argument, a range is two integers"); + up = atoi(endptr); + } + + + FILE *sock = NULL; +redo: + sock = lkt_connect(); + if (up != 0) + write_socket(sock, "playlist %d:%d\n", continuation, up); + else + write_socket(sock, "playlist %d\n", continuation); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + if (STR_NMATCH(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + if (continuation > 0) { + fclose(sock); + goto redo; + } + } + + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +noreturn void +queue_list__(struct cmd_args *args) +{ + char buff[LKT_MESSAGE_MAX], *endptr; + FILE *sock = NULL; + long continuation = 0, song_index = 1; + + /* Arguments stuff. */ + + if (args->argc == 0) + args->argv = LKT_QUEUE_DEFAULT; + else if (args->argc != 1) + fail("Invalid argument: queue <count?>"); + + continuation = strtol(args->argv[0], &endptr, 0); + if ((errno == ERANGE && (continuation == LONG_MAX || continuation == LONG_MIN)) || (errno != 0 + && continuation == 0) || (endptr == args->argv[0])) + fail("Invalid argument, not an integer"); + + if (*endptr != '\0') + fail("Invalid argument"); + + /* Get the current pos to get limits for the playlist command. */ + + sock = lkt_connect(); + write_socket(sock, "status\n"); + +#define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; } + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + assign_int("song", song_index); + + /* At this point every key has been parsed. */ + if (STR_NMATCH(buff, "OK", 2)) + break; + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + } +#undef assign_int + fclose(sock); + + /* Get the content of the queue. */ + + continuation = labs(continuation); + song_index = MAX(song_index + 1, 1); + safe_snprintf(buff, LKT_MESSAGE_MAX, "%ld:%ld", song_index, song_index + continuation - 1); + args->argc = 1; + args->argv[0] = buff; + queue_pos__(args); +} + +/* Functions implementing options, but for for playlists. */ + +noreturn void +plt_add__(struct cmd_args *args) +{ + char buff[2]; + FILE *sock = lkt_connect(); + fail_if(args->argc < 3, + "Invalid argument, need at least three arguments: plt add <plt> <query>"); + fail_if(!lkt_valid_type(args->argv[1]), "Invalid argument, type for the query is invalid"); + + int i; + char buf[LKT_MESSAGE_MAX] = {0}; + for (i = 2; i < args->argc - 1; ++i) { + strncat(buf, args->argv[i], LKT_MESSAGE_MAX - 1); + strncat(buf, " ", LKT_MESSAGE_MAX - 1); + } + strncat(buf, args->argv[i], LKT_MESSAGE_MAX - 1); + strncat(buf, "\n", LKT_MESSAGE_MAX - 1); + write_socket(sock, "playlistadd %s %s://%s", args->argv[0], args->argv[1], buf); + + exit_with_status(sock, buff); +} + +noreturn void +plt_delete__(struct cmd_args *args) +{ + FILE *sock = lkt_connect(); + char buff[2]; + char cmd[128]; + fail_if(args->argc < 3, "Invalid argument"); + snprintf(cmd, 128 - 1, "playlistdelete %s", args->argv[0]); + cmd[127] = '\0'; + send_cmd_with_uri(sock, cmd, args->argc - 1, &args->argv[1]); + exit_with_status(sock, buff); +} + +noreturn void +stickers_get__(struct cmd_args *args) +{ + char buff[LKT_MESSAGE_MAX]; + FILE *sock; + if (args->argc == 2) + write_socket(sock = lkt_connect(), "sticker get %s %s\n", args->argv[0], args->argv[1]); + else if (args->argc == 3) + write_socket(sock = lkt_connect(), "sticker get %s %s %s\n", args->argv[0], args->argv[1], + args->argv[2]); + else if (args->argc == 5) { + const char *op = args->argv[3]; + op = op[0] == 'e' ? "=" : op[0] == 'l' ? "<" : op[0] == 'g' ? ">" : NULL; + fail_if(!op, "Invalid argument"); + write_socket(sock = lkt_connect(), "sticker get %s %s %s %s %s\n", args->argv[0], + args->argv[1], args->argv[2], op, args->argv[4]); + } else + fail("Invalid argument"); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + fprintf(stdout, "%s", buff); + } +} + +noreturn void +stickers_set__(struct cmd_args *args) +{ + fail_if(args->argc != 4, "Invalid argument"); + FILE *sock = lkt_connect(); + char buff[2]; + write_socket(sock, "sticker set %s %s %s %s\n", args->argv[0], + args->argv[1], args->argv[2], args->argv[3]); + exit_with_status(sock, buff); +} + +noreturn void +stickers_delete__(struct cmd_args *args) +{ + FILE *sock; + char buff[2]; + if (args->argc == 2) + write_socket(sock = lkt_connect(), "sticker delete %s %s", + args->argv[0], args->argv[1]); + else if (args->argc == 3) + write_socket(sock = lkt_connect(), "sticker delete %s %s %s", + args->argv[0], args->argv[1], args->argv[2]); + else + fail("Invalid argument"); + exit_with_status(sock, buff); +} + +/* Search functions. */ + +noreturn void +__continuation_calls(const char *cmd) +{ + char buff[LKT_MESSAGE_MAX]; + int continuation = 0; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket(sock, "%d %s\n", continuation, cmd); + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + if (STR_NMATCH(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + fclose(sock); + goto redo; + } + + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +noreturn void +list_plt__(struct cmd_args *args) +{ + fail_if(args->argc != 0, "Invalid argument number"); + __continuation_calls("listplaylists"); +} + +noreturn void +list_plt_content__(struct cmd_args *args) +{ + fail_if(args->argc != 1, "Invalid argument number"); + char buff[LKT_MESSAGE_MAX]; + safe_snprintf(buff, LKT_MESSAGE_MAX, "listplaylist %s", args->argv[0]); + __continuation_calls(buff); + exit(EXIT_FAILURE); +} + +noreturn void +search_with_cmd__(struct cmd_args *args, const char *cmd) +{ + fail_if(args->argc < 2, "Invalid number of arguments"); + fail_if(!lkt_valid_type(args->argv[0]), "Invalid type for the query"); + + char buff[LKT_MESSAGE_MAX]; + int continuation = 0, i; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket(sock, "%d %s %s://", continuation, cmd, args->argv[0]); + for (i = 1; i < args->argc - 1; ++i) + write_socket(sock, "%s ", args->argv[i]); + write_socket(sock, "%s\n", args->argv[i]); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + if (STR_NMATCH(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + fclose(sock); + goto redo; + } + + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +noreturn void +search_get__(struct cmd_args *args) +{ + fail_if(args->argc != 1, "Invalid number of arguments"); + fail_if(!strtol(args->argv[0], NULL, 0), "Invalid id"); + char buff[LKT_MESSAGE_MAX]; + FILE *sock = lkt_connect(); + + write_socket(sock, "find %s\n", args->argv[0]); + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + (void) write(1, buff, strlen(buff)); + } +} + +noreturn void +search_plt__(struct cmd_args *args) +{ + if (args->argc == 0) + list_plt__(args); + if (args->argc == 1) + list_plt_content__(args); + + fail_if(args->argc < 3, "Invalid number of arguments"); + fail_if(!lkt_valid_type(args->argv[1]), "Invalid type for query"); + char buff[LKT_MESSAGE_MAX]; + int continuation = 0, i; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket(sock, "%d listplaylistinfo %s %s://", continuation, + args->argv[0], args->argv[1]); + for (i = 2; i < args->argc - 1; ++i) + write_socket(sock, "%s ", args->argv[i]); + write_socket(sock, "%s\n", args->argv[i]); + + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + if (STR_NMATCH(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + fclose(sock); + goto redo; + } + + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +#define search_with_cmd(func, cmd) /* I don't want to write always the same things */ \ + noreturn void func (struct cmd_args *args) { search_with_cmd__(args, #cmd); } +search_with_cmd(search_db__, search) +search_with_cmd(search_count__, count) +search_with_cmd(search_queue__, playlistfind) +#undef search_with_cmd + +/* Parsing stuff. */ + +static struct cmd_opt options_queue[] = { + { .name = "insert", .call = queue_insert__ }, + { .name = "pos", .call = queue_pos__ }, + { .name = "pop", .call = queue_pop__ }, + { .name = "add", .call = queue_add__ }, + { .name = "seek", .call = queue_seek__ }, + { .name = "delete", .call = queue_delete__ }, + { .name = "clear", .call = queue_clear__ }, + { .name = "crop", .call = queue_crop__ }, + { .name = "replace", .call = queue_replace__ }, + { .name = "dump", .call = queue_dump__ }, + CMD_OPT_DEFAULT(queue_list__), +}; + +static struct cmd_opt options_plt[] = { + { .name = "add", .call = plt_add__ }, + { .name = "delete", .call = plt_delete__ }, + { .name = "destroy", .call = plt_destroy__ }, + { .name = "create", .call = plt_create__ }, + { .name = "list", .call = search_plt__ }, + CMD_OPT_NULL, +}; + +static struct cmd_opt options_search[] = { + { .name = "database", .call = search_db__ }, + { .name = "get", .call = search_get__ }, + { .name = "plt", .call = search_plt__ }, + { .name = "count", .call = search_count__ }, + { .name = "queue", .call = search_queue__ }, + CMD_OPT_NULL, +}; + +static struct cmd_opt options_admin[] = { + { .name = "ping", .call = ping__ }, + { .name = "kill", .call = kill__ }, + { .name = "restart", .call = restart__ }, + { .name = "rescan", .call = rescan__ }, + { .name = "update", .call = update__ }, + { .name = "populate", .call = populate__ }, + { .name = "config", .call = config__ }, + CMD_OPT_NULL, +}; + +static struct cmd_opt options_stickers[] = { + { .name = "get", .call = stickers_get__ }, + { .name = "set", .call = stickers_set__ }, + { .name = "delete", .call = stickers_delete__ }, + { .name = "create", .call = stickers_create__ }, + { .name = "destroy", .call = stickers_destroy__ }, + CMD_OPT_NULL, +}; + +#define sub_command(name) /* Create sub-commands here */ \ + noreturn void name ## __(struct cmd_args *args) { \ + fail_if(!args->argc, "Invalid command, specify a sub command for " #name); \ + cmd_parse(options_ ## name, args->argc, args->argv); \ + } +sub_command(stickers) +sub_command(search) +sub_command(plt) +sub_command(admin) + +noreturn void +queue__(struct cmd_args *args) +{ + if (args->argc == 0) + queue_list__(args); + cmd_parse(options_queue, args->argc, args->argv); +} +#undef sub_command + +static struct cmd_opt options_[] = { + { .name = "current", .call = current__ }, + { .name = "play", .call = play__ }, + { .name = "next", .call = next__ }, + { .name = "previous", .call = prev__ }, + { .name = "queue", .call = queue__ }, + { .name = "shuffle", .call = shuffle__ }, + { .name = "status", .call = status__ }, + { .name = "stop", .call = stop__ }, + { .name = "plt", .call = plt__ }, + { .name = "search", .call = search__ }, + { .name = "admin", .call = admin__ }, + { .name = "stickers", .call = stickers__ }, + CMD_OPT_NULL, +}; + +/* The sigpipe function, if SIGPIPE signal is sent. */ + +static void +sigpipe__(int sig) +{ + LOG_ERROR("GENERAL", "Exit because of signal sigpipe (%d)", sig); + exit(EXIT_FAILURE); +} + +/* Functions declarations. */ + +static void +parse_args(args_t *args, int argc, const char **argv) +{ + int i, got = 0; + size_t len; + + for (i = 1; i < argc && i < 3; ++i) { + len = strcspn(argv[i], "="); + + if (STR_NMATCH("host", argv[i], len)) { + args->host = (argv[i] + len + 1); + ++got; + } + + else if (STR_NMATCH("port", argv[i], len)) { + args->port = (argv[i] + len + 1); + ++got; + } + + else if (STR_NMATCH("pwd", argv[i], len)) { + args->pwd = (argv[i] + len + 1); + ++got; + } + } + + args->argv = &argv[got + 1]; + args->argc = argc - (got + 1); +} + +int +main(int argc, const char **argv) +{ + __log_level = ERROR; + executable_name = "lkt"; + assert(NULL != setlocale(LC_ALL, "en_US.UTF-8")); /* BECAUSE! */ + if (signal(SIGPIPE, sigpipe__)) { + // LOG_ERROR("SYS", "%s", "Failed to install handler for SIGPIPE signal"); + } + + args_t args = { + .host = "localhost", + .port = "6600", + }; + + parse_args(&args, argc, argv); + + host = args.host; + port = args.port; + password = args.pwd; + + /* Communication with lektor. */ + cmd_parse(options_, args.argc, args.argv); +}