a6c667725bee0ddce273621dbc9d9877345580b3
[paraslash.git] / mysql_selector.c
1 /*
2  * Copyright (C) 1999-2007 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 /** \file mysql_selector.c para_server's mysql-based audio file selector */
20
21 /** \cond some internal constants */
22 #define MEDIUM_BLOB_SIZE 16777220 /*  (2**24 + 4) */
23 #define BLOB_SIZE 65539 /* (2**16 + 3) */
24 /** \endcond */
25 #include "server.cmdline.h"
26 #include "server.h"
27 #include "vss.h"
28 #include "afs.h"
29 #include <mysql/mysql.h>
30 #include <mysql/mysql_version.h>
31 #include <regex.h>
32 #include "error.h"
33 #include "net.h"
34 #include "string.h"
35 #include "user_list.h"
36 #include "mysql_selector_command_list.h"
37
38 /** pointer to the shared memory area */
39 extern struct misc_meta_data *mmd;
40
41 static void *mysql_ptr = NULL;
42
43 /**
44  * contains name/replacement pairs used by s_a_r_list()
45  *
46  * \sa s_a_r()
47  */
48 struct para_macro {
49         /** the name of the macro */
50         const char *name;
51         /** the replacement text */
52         const char *replacement;
53 };
54
55 static const struct para_macro mysql_macro_list[] = {
56         {       .name = "IS_N_SET",
57                 .replacement = "(data.%s != '1')"
58         }, {
59                 .name = "IS_SET",
60                 .replacement = "(data.%s = '1')"
61         }, {
62                 .name = "PICID",
63                 .replacement = "%sdata.Pic_Id"
64         }, {
65                 .name = "NAME_LIKE",
66                 .replacement = "(data.name like '%s')"
67         }, {
68                 .name = "LASTPLAYED",
69                 .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
70                         "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
71         }, {
72                 .name = "NUMPLAYED",
73                 .replacement = "%sdata.Numplayed"
74         }, {
75                 .name = NULL,
76         }
77 };
78
79 /**
80  * simple search and replace routine
81  *
82  * \param src source string
83  * \param macro_name the name of the macro
84  * \param replacement the replacement format string
85  *
86  * In \p src, replace each occurence of \p macro_name(arg) by the string
87  * determined by the \p replacement format string. \p replacement may (but
88  * needs not) contain a single string conversion specifier (%s) which gets
89  * replaced by \p arg.
90  *
91  * \return A string in which all matches in \p src are replaced, or \p NULL if
92  * an syntax error was encountered. Caller must free the result.
93  *
94  * \sa regcomp(3)
95  */
96 __must_check __malloc static char *s_a_r(const char *src, const char* macro_name,
97                 const char *replacement)
98 {
99         regex_t preg;
100         size_t  nmatch = 1;
101         regmatch_t pmatch[1];
102         int eflags = 0;
103         char *dest = NULL;
104         const char *bufptr = src;
105
106         if (!macro_name || !replacement || !src)
107                 return para_strdup(src);
108         regcomp(&preg, macro_name, 0);
109         while (regexec(&preg,  bufptr, nmatch, pmatch, eflags)
110                         != REG_NOMATCH) {
111                 char *tmp, *arg, *o_bracket, *c_bracket;
112
113                 o_bracket = strchr(bufptr + pmatch[0].rm_so, '(');
114                 c_bracket = o_bracket? strchr(o_bracket, ')') : NULL;
115                 if (!c_bracket)
116                         goto out;
117                 tmp = para_strdup(bufptr);
118                 tmp[pmatch[0].rm_so] = '\0';
119                 dest = para_strcat(dest, tmp);
120                 free(tmp);
121
122                 arg = para_strdup(o_bracket + 1);
123                 arg[c_bracket - o_bracket - 1] = '\0';
124                 tmp = make_message(replacement, arg);
125                 free(arg);
126                 dest = para_strcat(dest, tmp);
127                 free(tmp);
128                 bufptr = c_bracket;
129                 bufptr++;
130         }
131         dest = para_strcat(dest, bufptr);
132 //      PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
133 out:
134         regfree(&preg);
135         return dest;
136 }
137
138 /**
139  * replace a string according to a list of macros
140  *
141  * \param macro_list the array containing a macro/replacement pairs.
142  * \param src the source string
143  *
144  * This function just calls s_a_r() for each element of \p macro_list.
145  *
146  * \return \p NULL if one of the underlying calls to \p s_a_r returned \p NULL.
147  * Otherwise the completely expanded version of \p src is returned.
148  */
149 __must_check __malloc static char *s_a_r_list(const struct para_macro *macro_list,
150                 char *src)
151 {
152         const struct para_macro *mp = macro_list;
153         char *ret = NULL, *tmp = para_strdup(src);
154
155         while (mp->name) {
156                 ret = s_a_r(tmp, mp->name, mp->replacement);
157                 free(tmp);
158                 if (!ret) /* syntax error */
159                         return NULL;
160                 tmp = ret;
161                 mp++;
162         }
163         //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
164         return ret;
165 }
166
167 static int real_query(const char *query)
168 {
169         if (!mysql_ptr)
170                 return -E_NOTCONN;
171         PARA_DEBUG_LOG("%s\n", query);
172         if (mysql_real_query(mysql_ptr, query, strlen(query))) {
173                 PARA_ERROR_LOG("real_query error (%s)\n",
174                         mysql_error(mysql_ptr));
175                 return -E_QFAILED;
176         }
177         return 1;
178 }
179
180 /*
181  * Use open connection given by mysql_ptr to query server. Returns a
182  * result pointer on succes and NULL on errors
183  */
184 static struct MYSQL_RES *get_result(const char *query)
185 {
186         void *result;
187
188         if (real_query(query) < 0)
189                 return NULL;
190         result = mysql_store_result(mysql_ptr);
191         if (!result)
192                 PARA_ERROR_LOG("%s", "store_result error\n");
193         return result;
194 }
195 /*
196  * write input from fd to dynamically allocated char array,
197  * but maximal max_size byte. Return size.
198  */
199 static int fd2buf(int fd, char **buf_ptr, size_t max_size)
200 {
201         const size_t chunk_size = 1024;
202         size_t size = 2048;
203         char *buf = para_malloc(size * sizeof(char)), *p = buf;
204         int ret;
205
206         while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
207                 p += ret;
208                 if ((p - buf) + chunk_size >= size) {
209                         char *tmp;
210
211                         size *= 2;
212                         if (size > max_size) {
213                                 ret = -E_TOOBIG;
214                                 goto out;
215                         }
216                         tmp = para_realloc(buf, size);
217                         p = (p - buf) + tmp;
218                         buf = tmp;
219                 }
220         }
221         if (ret < 0)
222                 goto out;
223         *buf_ptr = buf;
224         ret = p - buf;
225 out:
226         if (ret < 0 && buf)
227                 free(buf);
228         return ret;
229 }
230
231 static char *escape_blob(const char* old, size_t size)
232 {
233         char *new;
234
235         if (!mysql_ptr)
236                 return NULL;
237         new = para_malloc(2 * size * sizeof(char) + 1);
238         mysql_real_escape_string(mysql_ptr, new, old, size);
239         return new;
240 }
241
242 static char *escape_str(const char* old)
243 {
244         return escape_blob(old, strlen(old));
245 }
246
247 static char *escaped_basename(const char *name)
248 {
249         char *esc, *bn = para_basename(name);
250
251         if (!bn)
252                 return NULL;
253         esc = escape_str(bn);
254         free(bn);
255         return esc;
256 }
257
258 /*
259  * new attribute
260  */
261 int com_na(__a_unused int fd, int argc, char *argv[])
262 {
263         char *q, *tmp;
264         int ret;
265
266         if (argc < 2)
267                 return -E_MYSQL_SYNTAX;
268         tmp = escape_str(argv[1]);
269         if (!tmp)
270                 return -E_ESCAPE;
271         q = make_message("alter table data add %s char(1) "
272                 "not null default 0", tmp);
273         free(tmp);
274         ret = real_query(q);
275         free(q);
276         return ret;
277 }
278
279 /*
280  * delete attribute
281  */
282 int com_da(__a_unused int fd, int argc, char *argv[])
283 {
284         char *q, *tmp;
285         int ret;
286
287         if (argc < 2)
288                 return -E_MYSQL_SYNTAX;
289         tmp = escape_str(argv[1]);
290         if (!tmp)
291                 return -E_ESCAPE;
292         q = make_message("alter table data drop %s", tmp);
293         free(tmp);
294         ret = real_query(q);
295         free(q);
296         return ret;
297 }
298
299 /* stradd/pic_add */
300 static int com_stradd_picadd(int fd, int argc, char *argv[])
301 {
302         char *blob = NULL, *esc_blob = NULL, *q = NULL, *tmp = NULL;
303         const char *fmt, *del_fmt;
304         int ret, stradd = strcmp(argv[0], "picadd");
305         size_t size;
306
307         if (argc < 2)
308                 return -E_MYSQL_SYNTAX;
309         if (strlen(argv[1]) >= MAXLINE - 1)
310                 return -E_NAMETOOLONG;
311         if (!mysql_ptr)
312                 return -E_NOTCONN;
313         if (stradd) {
314                 size = BLOB_SIZE;
315                 fmt = "insert into streams (name, def) values ('%s','%s')";
316                 del_fmt="delete from streams where name='%s'";
317         } else {
318                 size = MEDIUM_BLOB_SIZE;
319                 fmt = "insert into pics (name, pic) values ('%s','%s')";
320                 del_fmt="delete from pics where pic='%s'";
321         }
322         tmp = escape_str(argv[1]);
323         if (!tmp)
324                 return -E_ESCAPE;
325         q = make_message(del_fmt, tmp);
326         free(tmp);
327         ret = real_query(q);
328         free(q);
329         if (ret < 0)
330                 return ret;
331         if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
332                 return ret;
333         if ((ret = fd2buf(fd, &blob, size)) < 0)
334                 return ret;
335         size = ret;
336         if (stradd)
337                 blob[size] = '\0';
338         ret = -E_ESCAPE;
339         esc_blob = escape_blob(blob, size);
340         if (!esc_blob)
341                 goto out;
342         tmp = escape_str(argv[1]);
343         if (!tmp)
344                 goto out;
345         q = make_message(fmt, tmp, esc_blob);
346         ret = real_query(q);
347 out:
348         free(blob);
349         free(esc_blob);
350         free(tmp);
351         free(q);
352         return ret;
353 }
354
355 /* stradd */
356 int com_stradd(int fd, int argc, char *argv[])
357 {
358         return com_stradd_picadd(fd, argc, argv);
359 }
360
361 /* pic_add */
362 int com_picadd(int fd, int argc, char *argv[])
363 {
364         return com_stradd_picadd(fd, argc, argv);
365 }
366
367 /*
368  * print results to fd
369  */
370 static int print_results(int fd, void *result,
371                 my_ulonglong top, my_ulonglong left,
372                 my_ulonglong bottom, my_ulonglong right)
373 {
374         unsigned int i,j;
375         int ret;
376         MYSQL_ROW row;
377
378         for (i = top; i <= bottom; i++) {
379                 row = mysql_fetch_row(result);
380                 if (!row || !row[0])
381                         return -E_NOROW;
382                 for (j = left; j <= right; j++) {
383                         ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
384                                         row[j]?  row[j] : "NULL");
385                         if (ret < 0)
386                                 return ret;
387                 }
388                 ret = send_buffer(fd, "\n");
389                 if (ret < 0)
390                         return ret;
391         }
392         return 0;
393 }
394
395 /*
396  * verbatim
397  */
398 int com_verb(int fd, int argc, char *argv[])
399 {
400         void *result = NULL;
401         int ret;
402         my_ulonglong num_rows, num_fields, top = 0, left = 0;
403         char *tmp;
404
405         if (argc < 2)
406                 return -E_MYSQL_SYNTAX;
407         tmp = escape_str(argv[1]);
408         if (!tmp)
409                 return -E_ESCAPE;
410         result = get_result(tmp);
411         free(tmp);
412         if (!result)
413                 /* return success, because it's ok to have no results */
414                 return 1;
415         num_fields = mysql_field_count(mysql_ptr);
416         num_rows = mysql_num_rows(result);
417         ret = 1;
418         if (num_fields && num_rows)
419                 ret = print_results(fd, result, top, left, num_rows - 1,
420                         num_fields - 1);
421         mysql_free_result(result);
422         return ret;
423 }
424
425 /* returns NULL on errors or if there are no atts defined yet */
426 static void *get_all_attributes(void)
427 {
428         void *result = get_result("desc data");
429         unsigned int num_rows;
430
431         if (!result)
432                 return NULL;
433         num_rows = mysql_num_rows(result);
434         if (num_rows < 5) {
435                 mysql_free_result(result);
436                 return NULL;
437         }
438         mysql_data_seek(result, (my_ulonglong)4); /* skip Lastplayed, Numplayed... */
439         return result;
440 }
441
442 /*
443  * list all attributes
444  */
445 int com_laa(int fd, int argc, __a_unused char *argv[])
446 {
447         void *result;
448         int ret;
449         my_ulonglong top = 0, left = 0, bottom, right = 0;
450
451         if (argc != 1)
452                 return -E_MYSQL_SYNTAX;
453         result = get_all_attributes();
454         if (!result)
455                 return -E_NOATTS;
456         bottom = mysql_num_rows(result);
457         if (bottom < 5)
458                 return -E_MYSQL_SYNTAX;
459         bottom -= 5;
460         ret = print_results(fd, result, top, left, bottom, right);
461         mysql_free_result(result);
462         return ret;
463 }
464
465 /*
466  * history
467  */
468 int com_hist(int fd, int argc, char *argv[])
469 {
470         int ret;
471         void *result = NULL;
472         char *q, *atts;
473         my_ulonglong num_rows, top = 0, left = 0, right = 1;
474
475         if (argc > 3)
476                 return -E_MYSQL_SYNTAX;
477         if (argc > 1) {
478                 char *tmp = escape_str(argv[1]);
479                 if (!tmp)
480                         return -E_ESCAPE;
481                 atts = make_message("where %s = '1'", tmp);
482                 free(tmp);
483         } else
484                 atts = para_strdup(NULL);
485
486         q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
487                 "data %s order by lastplayed", atts);
488         free(atts);
489         result = get_result(q);
490         free(q);
491         if (!result)
492                 return -E_NORESULT;
493         num_rows = mysql_num_rows(result);
494         ret = 1;
495         if (num_rows)
496                 ret = print_results(fd, result, top, left, num_rows - 1, right);
497         mysql_free_result(result);
498         return ret;
499 }
500
501 /*
502  * get last num audio files
503  */
504 int com_last(int fd, int argc, char *argv[])
505 {
506         void *result = NULL;
507         char *q;
508         int num, ret;
509         my_ulonglong top = 0, left = 0, right = 0;
510
511         if (argc < 2)
512                 num = 10;
513         else
514                 num = atoi(argv[1]);
515         if (!num)
516                 return -E_MYSQL_SYNTAX;
517         q = make_message("select name from data order by lastplayed desc "
518                 "limit %u", num);
519         result = get_result(q);
520         free(q);
521         if (!result)
522                 return -E_NORESULT;
523         ret = print_results(fd, result, top, left, mysql_num_rows(result) - 1,
524                 right);
525         mysql_free_result(result);
526         return ret;
527 }
528
529 int com_mbox(int fd, int argc, char *argv[])
530 {
531         void *result;
532         MYSQL_ROW row;
533         int ret;
534         my_ulonglong num_rows, num_fields, top = 0, left = 0;
535         char *query = para_strdup("select concat('From foo@localhost ', "
536                 "date_format(Lastplayed, '%a %b %e %T %Y'), "
537                 "'\nReceived: from\nTo: bar\n");
538
539         ret = -E_NOATTS;
540         result = get_all_attributes();
541         if (!result)
542                 goto out;
543         ret = -E_NOROW;
544         while ((row = mysql_fetch_row(result))) {
545                 char *tmp;
546
547                 if (!row[0])
548                         goto out;
549                 tmp = make_message("%sX-Attribute-%s: ', %s, '\n", query,
550                         row[0], row[0]);
551                 free(query);
552                 query = tmp;
553         }
554         query = para_strcat(query,
555                 "From: a\n"
556                 "Subject: "
557                 "', name, '"
558                 "\n\n\n"
559                 "') from data"
560         );
561         if (argc >= 2) {
562                 char *esc = escape_str(argv[1]), *tmp;
563                 ret = -E_ESCAPE;
564                 if (!esc)
565                         goto out;
566                 tmp = make_message("%s where name LIKE '%s'", query, esc);
567                 free(esc);
568                 free(query);
569                 query = tmp;
570         }
571         mysql_free_result(result);
572         ret = -E_NORESULT;
573         result = get_result(query);
574         if (!result)
575                 goto out;
576         ret = -E_EMPTY_RESULT;
577         num_fields = mysql_field_count(mysql_ptr);
578         num_rows = mysql_num_rows(result);
579         if (!num_fields || !num_rows)
580                 goto out;
581         ret = print_results(fd, result, top, left, num_rows - 1,
582                 num_fields - 1);
583 out:
584         free(query);
585         if (result)
586                 mysql_free_result(result);
587         return ret;
588 }
589
590 /*
591  * get attributes by name. If verbose is not 0, this function returns a string
592  * of the form 'att1="0",att2="1"'... which is used in com_cam() for
593  * constructing a mysql update query. Otherwise the space-separated list of all
594  * attributes which are set in the audio file given by name is returned.  Never
595  * returns NULL in *NON VERBOSE* mode.
596  */
597 static char *get_atts(char *name, int verbose)
598 {
599         char *atts = NULL, *buf, *ebn;
600         void *result = NULL, *result2 = NULL;
601         MYSQL_ROW row, row2;
602         int i;
603         my_ulonglong num_fields, offset = 4; /* skip Lastplayed, Numplayed... */
604
605
606         result2 = get_all_attributes();
607         if (!result2)
608                 goto out;
609         ebn = escaped_basename(name);
610         if (!ebn)
611                 goto out;
612         buf = make_message("select * from data where name='%s'", ebn);
613         free(ebn);
614         result = get_result(buf);
615         free(buf);
616         if (!result)
617                 goto out;
618         num_fields = mysql_num_fields(result);
619         if (num_fields < 5)
620                 goto out;
621         mysql_data_seek(result2, offset);
622         row = mysql_fetch_row(result);
623         if (!row)
624                 goto out;
625         for (i = 4; i < num_fields; i++) {
626                 int is_set = row[i] && !strcmp(row[i], "1");
627                 row2 = mysql_fetch_row(result2);
628                 if (!row2 || !row2[0])
629                         goto out;
630                 if (atts && (verbose || is_set))
631                         atts = para_strcat(atts, verbose? "," : " ");
632                 if (is_set || verbose)
633                         atts = para_strcat(atts, row2[0]);
634                 if (verbose)
635                         atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
636         }
637 out:
638         if (result2)
639                 mysql_free_result(result2);
640         if (result)
641                 mysql_free_result(result);
642         if (!atts && !verbose)
643                 atts = para_strdup("(none)");
644         return atts;
645 }
646
647 /* never returns NULL in verbose mode */
648 static char *get_meta(char *name, int verbose)
649 {
650         MYSQL_ROW row;
651         void *result = NULL;
652         char *ebn, *q, *ret = NULL;
653         const char *verbose_fmt =
654                 "select concat('lastplayed: ', "
655                 "(to_days(now()) - to_days(lastplayed)),"
656                 "' day(s). numplayed: ', numplayed, "
657                 "', pic: ', pic_id) "
658                 "from data where name = '%s'";
659         /* is that really needed? */
660         const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
661                 "'\\', numplayed=\\'', numplayed, "
662                 "'\\', pic_id=\\'', pic_id, '\\'') "
663                 "from data where name = '%s'";
664
665         if (!(ebn = escaped_basename(name)))
666                 goto out;
667         q = make_message(verbose? verbose_fmt : fmt, ebn);
668         free(ebn);
669         result = get_result(q);
670         free(q);
671         if (!result)
672                 goto out;
673         row = mysql_fetch_row(result);
674         if (!row || !row[0])
675                 goto out;
676         ret = para_strdup(row[0]);
677 out:
678         if (result)
679                 mysql_free_result(result);
680         if (!ret && verbose)
681                 ret = para_strdup("(not yet played)");
682         return ret;
683 }
684
685 static char *get_dir(char *name)
686 {
687         char *ret = NULL, *q, *ebn;
688         void *result;
689         MYSQL_ROW row;
690
691         if (!(ebn = escaped_basename(name)))
692                 return NULL;
693         q = make_message("select dir from dir where name = '%s'", ebn);
694         free(ebn);
695         result = get_result(q);
696         free(q);
697         if (!result)
698                 return NULL;
699         row = mysql_fetch_row(result);
700         if (row && row[0])
701                 ret = para_strdup(row[0]);
702         mysql_free_result(result);
703         return ret;
704 }
705
706 /* never returns NULL */
707 static char *get_current_stream(void)
708 {
709         char *ret;
710         MYSQL_ROW row;
711         void *result = get_result("select def from streams where "
712                 "name = 'current_stream'");
713
714         if (!result)
715                 goto err_out;
716         row = mysql_fetch_row(result);
717         if (!row || !row[0])
718                 goto err_out;
719         ret = para_strdup(row[0]);
720         mysql_free_result(result);
721         return ret;
722 err_out:
723         if (result)
724                 mysql_free_result(result);
725         return para_strdup("(none)");
726 }
727
728 /*
729  * Read stream definition of stream streamname and construct mysql
730  * query. Return NULL on errors. If streamname is NULL, use current
731  * stream. If that is also NULL, use query that selects everything.
732  * If filename is NULL, query will list everything, otherwise only
733  * the score of given file.
734  */
735 static char *get_query(char *streamname, char *filename, int with_path)
736 {
737         char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
738         char *where_clause, *order, *query;
739         char command[255] = ""; /* buffer for sscanf */
740         void *result;
741         MYSQL_ROW row;
742         char *end, *tmp;
743         char *select_clause = NULL;
744         if (!streamname)
745                 tmp = get_current_stream();
746         else {
747                 tmp = escape_str(streamname);
748                 if (!tmp)
749                         return NULL;
750         }
751         if (!strcmp(tmp, "(none)")) {
752                 free(tmp);
753                 if (filename) {
754                         char *ret, *ebn = escaped_basename(filename);
755                         if (!ebn)
756                                 return NULL;
757                         ret = make_message("select to_days(now()) - "
758                                 "to_days(lastplayed) from data "
759                                 "where name = '%s'", ebn);
760                         free(ebn);
761                         return ret;
762                 }
763                 if (with_path)
764                         return make_message(
765                                 "select concat(dir.dir, '/', dir.name) "
766                                 "from data, dir where dir.name = data.name "
767                                 "order by data.lastplayed"
768                         );
769                 return make_message(
770                         "select name from data where name is not NULL "
771                         "order by lastplayed"
772                 );
773         }
774         free(tmp);
775         query = make_message("select def from streams where name = '%s'",
776                 streamname);
777         result = get_result(query);
778         free(query);
779         query = NULL;
780         if (!result)
781                 goto out;
782         row = mysql_fetch_row(result);
783         if (!row || !row[0])
784                 goto out;
785         end = row[0];
786         while (*end) {
787                 int n;
788                 char *arg, *line = end;
789
790                 if (!(end = strchr(line, '\n')))
791                         break;
792                 *end = '\0';
793                 end++;
794                 if (sscanf(line, "%200s%n", command, &n) < 1)
795                         continue;
796                 arg = line + n;
797                 if (!strcmp(command, "accept:")) {
798                         char *tmp2 = s_a_r_list(mysql_macro_list, arg);
799                         if (accept_opts)
800                                 accept_opts = para_strcat(
801                                         accept_opts, " or ");
802                         accept_opts = para_strcat(accept_opts, tmp2);
803                         free(tmp2);
804                         continue;
805                 }
806                 if (!strcmp(command, "deny:")) {
807                         char *tmp2 = s_a_r_list(mysql_macro_list, arg);
808                         if (deny_opts)
809                                 deny_opts = para_strcat(deny_opts, " or ");
810                         deny_opts = para_strcat(deny_opts, tmp2);
811                         free(tmp2);
812                         continue;
813                 }
814                 if (!score && !strcmp(command, "score:"))
815                         score = s_a_r_list(mysql_macro_list, arg);
816         }
817         if (!score) {
818                 score = s_a_r_list(mysql_macro_list, conf.mysql_default_score_arg);
819                 if (!score)
820                         goto out;
821         }
822         if (filename) {
823                 char *ebn = escaped_basename(filename);
824                 if (!ebn)
825                         goto out;
826                 select_clause = make_message("select %s from data ", score);
827                 free(score);
828                 where_clause = make_message( "where name = '%s' ", ebn);
829                 free(ebn);
830                 order = para_strdup("");
831                 goto write_query;
832         }
833         select_clause = para_strdup(with_path?
834                 "select concat(dir.dir, '/', dir.name) from data, dir "
835                 "where dir.name = data.name "
836                 :
837                 "select name from data where name is not NULL");
838         order = make_message("order by -(%s)", score);
839         free(score);
840         if (accept_opts && deny_opts) {
841                 where_clause = make_message("and ((%s) and not (%s)) ",
842                         accept_opts, deny_opts);
843                 goto write_query;
844         }
845         if (accept_opts && !deny_opts) {
846                 where_clause = make_message("and (%s) ", accept_opts);
847                 goto write_query;
848         }
849         if (!accept_opts && deny_opts) {
850                 where_clause = make_message("and not (%s) ", deny_opts);
851                 goto write_query;
852         }
853         where_clause = para_strdup("");
854 write_query:
855         query = make_message("%s %s %s", select_clause, where_clause, order);
856         free(order);
857         free(select_clause);
858         free(where_clause);
859 out:
860         if (accept_opts)
861                 free(accept_opts);
862         if (deny_opts)
863                 free(deny_opts);
864         if (result)
865                 mysql_free_result(result);
866         return query;
867 }
868
869
870
871 /*
872  * This is called from server and from some commands. Name must not be NULL
873  * Never returns NULL.
874  */
875 static char *get_selector_info(char *name)
876 {
877         char *meta, *atts, *info, *dir, *query, *stream;
878         void *result = NULL;
879         MYSQL_ROW row = NULL;
880
881         if (!name)
882                 return para_strdup("(none)");
883         stream = get_current_stream();
884         meta = get_meta(name, 1);
885         atts = get_atts(name, 0);
886         dir = get_dir(name);
887         /* get score */
888         query = get_query(stream, name, 0); /* FIXME: pass stream == NULL instead? */
889         if (!query)
890                 goto write;
891         result = get_result(query);
892         free(query);
893         if (result)
894                 row = mysql_fetch_row(result);
895 write:
896         info = make_message("dbinfo1:dir: %s\n"
897                 "dbinfo2:stream: %s, %s, score: %s\n"
898                 "dbinfo3:%s\n",
899                 dir? dir : "(not contained in table)",
900                 stream, meta, 
901                 (result && row && row[0])? row[0] : "(no score)",
902                 atts);
903         free(dir);
904         free(meta);
905         free(atts);
906         free(stream);
907         if (result)
908                 mysql_free_result(result);
909         return info;
910 }
911
912 /* might return NULL */
913 static char *get_current_audio_file(void)
914 {
915         char *name;
916         mmd_lock();
917         name = para_basename(mmd->filename);
918         mmd_unlock();
919         return name;
920 }
921
922 /* If called as child, mmd_lock must be held */
923 static void update_mmd(char *info)
924 {
925         PARA_DEBUG_LOG("%s", "updating shared memory area\n");
926         strncpy(mmd->selector_info, info, MMD_INFO_SIZE - 1);
927         mmd->selector_info[MMD_INFO_SIZE - 1] = '\0';
928 }
929
930 static void refresh_selector_info(void)
931 {
932         char *name = get_current_audio_file();
933         char *info;
934
935         if (!name)
936                 return;
937         info = get_selector_info(name);
938         free(name);
939         mmd_lock();
940         update_mmd(info);
941         mmd_unlock();
942         free(info);
943 }
944
945 /* list attributes / print database info */
946 static int com_la_info(int fd, int argc, char *argv[])
947 {
948         char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
949         int ret, la = strcmp(argv[0], "info");
950
951         if (argc < 2) {
952                 ret = -E_GET_AUDIO_FILE;
953                 if (!(name = get_current_audio_file()))
954                         goto out;
955                 ret = send_va_buffer(fd, "%s\n", name);
956                 if (ret < 0)
957                         goto out;
958         } else {
959                 ret = -E_ESCAPE;
960                 if (!(name = escaped_basename(argv[1])))
961                         goto out;
962         }
963         meta = get_meta(name, 1);
964         atts = get_atts(name, 0);
965         dir = get_dir(name);
966         if (la)
967                 ret = send_va_buffer(fd, "%s\n", atts);
968         else
969                 ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
970                         dir? dir : "(not contained in table)", meta, atts);
971 out:
972         free(meta);
973         free(atts);
974         free(dir);
975         free(name);
976         return ret;
977 }
978
979 /* list attributes */
980 int com_la(int fd, int argc, char *argv[])
981 {
982         return com_la_info(fd, argc, argv);
983 }
984
985 /* print database info */
986 int com_info(int fd, int argc, char *argv[])
987 {
988         return com_la_info(fd, argc, argv);
989 }
990
991 static int change_stream(const char *stream)
992 {
993         char *query;
994         int ret;
995         query = make_message("update streams set def='%s' "
996                 "where name = 'current_stream'", stream);
997         ret = real_query(query);
998         free(query);
999         return ret;
1000 }
1001
1002 static int get_pic_id_by_name(char *name)
1003 {
1004         char *q, *ebn;
1005         void *result = NULL;
1006         int ret;
1007         MYSQL_ROW row;
1008
1009         if (!(ebn = escaped_basename(name)))
1010                 return -E_ESCAPE;
1011         q = make_message("select pic_id from data where name = '%s'", ebn);
1012         free(ebn);
1013         result = get_result(q);
1014         free(q);
1015         if (!result)
1016                 return -E_NORESULT;
1017         row = mysql_fetch_row(result);
1018         ret = -E_NOROW;
1019         if (row && row[0])
1020                 ret = atoi(row[0]);
1021         mysql_free_result(result);
1022         return ret;
1023 }
1024
1025 static int remove_entry(const char *name)
1026 {
1027         char *q, *ebn = escaped_basename(name);
1028         int ret = -E_ESCAPE;
1029
1030         if (!ebn)
1031                 goto out;
1032         q = make_message("delete from data where name = '%s'", ebn);
1033         real_query(q); /* ignore errors */
1034         free(q);
1035         q = make_message("delete from dir where name = '%s'", ebn);
1036         real_query(q); /* ignore errors */
1037         free(q);
1038         ret = 1;
1039 out:
1040         free(ebn);
1041         return ret;
1042 }
1043
1044 static int add_entry(const char *name)
1045 {
1046         char *q, *dn, *ebn = NULL, *edn = NULL;
1047         int ret;
1048
1049         if (!name || !*name)
1050                 return -E_MYSQL_SYNTAX;
1051         ebn = escaped_basename(name);
1052         if (!ebn)
1053                 return -E_ESCAPE;
1054         ret = -E_MYSQL_SYNTAX;
1055         dn = para_dirname(name);
1056         if (!dn)
1057                 goto out;
1058         ret = -E_ESCAPE;
1059         edn = escape_str(dn);
1060         free(dn);
1061         if (!edn || !*edn)
1062                 goto out;
1063         q = make_message("insert into data (name, pic_id) values "
1064                         "('%s', '%s')", ebn, "1");
1065         ret = real_query(q);
1066 //      ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
1067         free(q);
1068         if (ret < 0)
1069                 goto out;
1070         q = make_message("insert into dir (name, dir) values "
1071                         "('%s', '%s')", ebn, edn);
1072 //      ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
1073         ret = real_query(q);
1074         free(q);
1075 out:
1076         if (ebn)
1077                 free(ebn);
1078         if (edn)
1079                 free(edn);
1080         return ret;
1081 }
1082
1083 /*
1084  * remove/add entries
1085  */
1086 static int com_rm_ne(__a_unused int fd, int argc, char *argv[])
1087 {
1088         int ne = !strcmp(argv[0], "ne");
1089         int i, ret;
1090         if (argc < 2)
1091                 return -E_MYSQL_SYNTAX;
1092         for (i = 1; i < argc; i++) {
1093                 ret = remove_entry(argv[i]);
1094                 if (ret < 0)
1095                         return ret;
1096                 if (!ne)
1097                         continue;
1098                 ret = add_entry(argv[i]);
1099                 if (ret < 0)
1100                         return ret;
1101         }
1102         return 1;
1103 }
1104
1105 /*
1106  * rm
1107  */
1108 int com_rm(int fd, int argc, char *argv[])
1109 {
1110         return com_rm_ne(fd, argc, argv);
1111 }
1112
1113 /*
1114  * ne
1115  */
1116 int com_ne(int fd, int argc, char *argv[])
1117 {
1118         return com_ne(fd, argc, argv);
1119 }
1120
1121 /*
1122  * mv: rename entry
1123  */
1124 int com_mv(__a_unused int fd, int argc, char *argv[])
1125 {
1126         char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
1127         int ret;
1128
1129         if (argc != 3)
1130                 return -E_MYSQL_SYNTAX;
1131         ret = -E_ESCAPE;
1132         ebn1 = escaped_basename(argv[1]);
1133         ebn2 = escaped_basename(argv[2]);
1134         if (!ebn1 || !ebn2 || !*ebn1 || !*ebn2)
1135                 goto out;
1136         ret = -E_MYSQL_SYNTAX;
1137         if (!strcmp(ebn1, ebn2))
1138                 goto update_dir;
1139         remove_entry(argv[2]); /* no need to escape, ignore error */
1140         q = make_message("update data set name = '%s' where name = '%s'",
1141                 ebn2, ebn1);
1142         ret = real_query(q);
1143         free(q);
1144         if (ret < 0)
1145                 goto out;
1146         ret = -E_AUDIO_FILE;
1147         if (!mysql_affected_rows(mysql_ptr))
1148                 goto out;
1149         q = make_message("update dir set name = '%s' where name = '%s'",
1150                 ebn2, ebn1);
1151         ret = real_query(q);
1152         free(q);
1153         if (ret < 0)
1154                 goto out;
1155 update_dir:
1156         ret = 1;
1157         dn = para_dirname(argv[2]);
1158         if (!dn)
1159                 goto out;
1160         ret = -E_ESCAPE;
1161         edn = escape_str(dn);
1162         free(dn);
1163         if (!edn)
1164                 goto out;
1165         ret = 1;
1166         if (!*edn)
1167                 goto out;
1168         q = make_message("update dir set dir = '%s' where name = '%s'",
1169                 edn, ebn2);
1170         ret = real_query(q);
1171         free(q);
1172 out:
1173         free(edn);
1174         free(ebn1);
1175         free(ebn2);
1176         return ret;
1177 }
1178
1179 /*
1180  * set field
1181  */
1182 static int com_set(__a_unused int fd, int argc, char *argv[])
1183 {
1184         char *q, *ebn;
1185         long unsigned id;
1186         int i, ret;
1187         const char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
1188
1189         if (argc < 3)
1190                 return -E_MYSQL_SYNTAX;
1191         id = atol(argv[1]);
1192         for (i = 2; i < argc; i++) {
1193                 ebn = escaped_basename(argv[i]);
1194                 if (!ebn)
1195                         return -E_ESCAPE;
1196                 q = make_message("update data set %s = %lu "
1197                                 "where name = '%s'", field, id, ebn);
1198                 free(ebn);
1199                 ret = real_query(q);
1200                 free(q);
1201                 if (ret < 0)
1202                         return ret;
1203         }
1204         return 1;
1205 }
1206
1207 /*
1208  * snp: set numplayed
1209  */
1210 int com_picass(int fd, int argc, char *argv[])
1211 {
1212         return com_set(fd, argc, argv);
1213 }
1214
1215 /*
1216  * snp: set numplayed
1217  */
1218 int com_snp(int fd, int argc, char *argv[])
1219 {
1220         int ret = com_set(fd, argc, argv);
1221         if (ret >= 0)
1222                 refresh_selector_info();
1223         return ret;
1224 }
1225
1226 /*
1227  * picch: change entry's name in pics table
1228  */
1229 int com_picch(__a_unused int fd, int argc, char *argv[])
1230 {
1231         int ret;
1232         long unsigned id;
1233         char *q, *tmp;
1234
1235         if (argc != 3)
1236                 return -E_MYSQL_SYNTAX;
1237         id = atol(argv[1]);
1238         ret = -E_ESCAPE;
1239         tmp = escape_str(argv[2]);
1240         if (!tmp)
1241                 return -E_ESCAPE;
1242         q = make_message("update pics set name = '%s' where id = %lu", tmp, id);
1243         free(tmp);
1244         ret = real_query(q);
1245         free(q);
1246         return ret;
1247 }
1248
1249 /*
1250  * piclist: print list of pics in db
1251  */
1252 int com_piclist(__a_unused int fd, int argc, __a_unused char *argv[])
1253 {
1254         void *result = NULL;
1255         MYSQL_ROW row;
1256         unsigned long *length;
1257         int ret;
1258
1259         if (argc != 1)
1260                 return -E_MYSQL_SYNTAX;
1261         result = get_result("select id,name,pic from pics order by id");
1262         if (!result)
1263                 return -E_NORESULT;
1264         while ((row = mysql_fetch_row(result))) {
1265                 length = mysql_fetch_lengths(result);
1266                 if (!row || !row[0] || !row[1] || !row[2])
1267                         continue;
1268                 ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
1269                 if (ret < 0)
1270                         goto out;
1271         }
1272         ret = 1;
1273 out:
1274         mysql_free_result(result);
1275         return ret;
1276 }
1277
1278 /*
1279  * picdel: delete picture from database
1280  */
1281 int com_picdel(int fd, int argc, char *argv[])
1282 {
1283         char *q;
1284         long unsigned id;
1285         my_ulonglong aff;
1286         int i, ret;
1287
1288         if (argc < 2)
1289                 return -E_MYSQL_SYNTAX;
1290         for (i = 1; i < argc; i++) {
1291                 id = atol(argv[i]);
1292                 q = make_message("delete from pics where id = %lu", id);
1293                 ret = real_query(q);
1294                 free(q);
1295                 if (ret < 0)
1296                         return ret;
1297                 aff = mysql_affected_rows(mysql_ptr);
1298                 if (!aff) {
1299                         ret = send_va_buffer(fd, "No such id: %lu\n", id);
1300                         if (ret < 0)
1301                                 return ret;
1302                         continue;
1303                 }
1304                 q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
1305                 ret = real_query(q);
1306                 free(q);
1307         }
1308         return 1;
1309 }
1310 /*
1311  * pic: get picture by name or by number
1312  */
1313 int com_pic(int fd, int argc, char *argv[])
1314 {
1315         void *result = NULL;
1316         MYSQL_ROW row;
1317         unsigned long *length, id;
1318         int ret;
1319         char *q, *name = NULL;
1320
1321         if (argc < 2) {
1322                 ret = -E_GET_AUDIO_FILE;
1323                 name = get_current_audio_file();
1324         } else {
1325                 ret = -E_ESCAPE;
1326                 name = escaped_basename(argv[1]);
1327         }
1328         if (!name)
1329                 return ret;
1330         if (*name == '#')
1331                 id = atoi(name + 1);
1332         else
1333                 id = get_pic_id_by_name(name);
1334         free(name);
1335         if (id <= 0)
1336                 return id;
1337         q = make_message("select pic from pics where id = '%lu'", id);
1338         result = get_result(q);
1339         free(q);
1340         if (!result)
1341                 return -E_NORESULT;
1342         row = mysql_fetch_row(result);
1343         ret = -E_NOROW;
1344         if (!row || !row[0])
1345                 goto out;
1346         length = mysql_fetch_lengths(result);
1347         ret = send_bin_buffer(fd, row[0], *length);
1348 out:
1349         mysql_free_result(result);
1350         return ret;
1351 }
1352
1353 /* strdel */
1354 int com_strdel(__a_unused int fd, int argc, char *argv[])
1355 {
1356         char *q, *tmp;
1357         int ret;
1358
1359         if (argc < 2)
1360                 return -E_MYSQL_SYNTAX;
1361         tmp = escape_str(argv[1]);
1362         if (!tmp)
1363                 return -E_ESCAPE;
1364         q = make_message("delete from streams where name='%s'", tmp);
1365         free(tmp);
1366         ret = real_query(q);
1367         free(q);
1368         return ret;
1369 }
1370
1371 /*
1372  * ls
1373  */
1374 int com_ls(int fd, int argc, char *argv[])
1375 {
1376         char *q;
1377         void *result;
1378         int ret;
1379         my_ulonglong num_rows, top = 0, left = 0, right = 0;
1380
1381         if (argc > 2)
1382                 return -E_MYSQL_SYNTAX;
1383         if (argc > 1) {
1384                 char *tmp = escape_str(argv[1]);
1385                 if (!tmp)
1386                         return -E_ESCAPE;
1387                 q = make_message("select name from data where name like '%s'",
1388                         tmp);
1389                 free(tmp);
1390         } else
1391                 q = para_strdup("select name from data");
1392         result = get_result(q);
1393         free(q);
1394         if (!result)
1395                 return -E_NORESULT;
1396         num_rows = mysql_num_rows(result);
1397         ret = 1;
1398         if (num_rows)
1399                 ret = print_results(fd, result, top, left, num_rows - 1, right);
1400         mysql_free_result(result);
1401         return ret;
1402 }
1403
1404 /*
1405  * summary
1406  */
1407 int com_summary(__a_unused int fd, int argc, __a_unused char *argv[])
1408 {
1409         MYSQL_ROW row;
1410         MYSQL_ROW row2;
1411         void *result;
1412         void *result2 = NULL;
1413         const char *fmt = "select count(name) from data where %s='1'";
1414         int ret = -E_NORESULT;
1415
1416         if (argc != 1)
1417                 return -E_MYSQL_SYNTAX;
1418         result = get_all_attributes();
1419         if (!result)
1420                 goto out;
1421         while ((row = mysql_fetch_row(result))) {
1422                 char *buf;
1423
1424                 ret = -E_NOROW;
1425                 if (!row[0])
1426                         goto out;
1427                 ret = -E_NORESULT;
1428                 buf = make_message(fmt, row[0]);
1429                 result2 = get_result(buf);
1430                 free(buf);
1431                 if (!result2)
1432                         goto out;
1433                 ret = -E_NOROW;
1434                 row2 = mysql_fetch_row(result2);
1435                 if (!row2 || !row2[0])
1436                         goto out;
1437                 ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
1438                 if (ret < 0)
1439                         goto out;
1440         }
1441         ret = 1;
1442 out:
1443         if (result2)
1444                 mysql_free_result(result2);
1445         if (result)
1446                 mysql_free_result(result);
1447         return ret;
1448 }
1449
1450 static int get_numplayed(char *name)
1451 {
1452         void *result;
1453         MYSQL_ROW row;
1454         const char *fmt = "select numplayed from data where name = '%s'";
1455         char *buf = make_message(fmt, name);
1456         int ret = -E_NORESULT;
1457
1458         result = get_result(buf);
1459         free(buf);
1460         if (!result)
1461                 goto out;
1462         ret = -E_NOROW;
1463         row = mysql_fetch_row(result);
1464         if (!row || !row[0])
1465                 goto out;
1466         ret = atoi(row[0]);
1467 out:
1468         if (result)
1469                 mysql_free_result(result);
1470         return ret;
1471 }
1472
1473 static int update_audio_file(char *name)
1474 {
1475         int ret;
1476         const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
1477         const char *fmt2 = "update data set numplayed = %i where name = '%s'";
1478         char *q;
1479         char *ebn = escaped_basename(name);
1480
1481         ret = -E_ESCAPE;
1482         if (!ebn)
1483                 goto out;
1484         q = make_message(fmt1, ebn);
1485         ret = real_query(q);
1486         free(q);
1487         if (ret < 0)
1488                 goto out;
1489         ret = get_numplayed(ebn);
1490         if (ret < 0)
1491                 goto out;
1492         q = make_message(fmt2, ret + 1, ebn);
1493         ret = real_query(q);
1494         free(q);
1495 out:
1496         free(ebn);
1497         return ret;
1498 }
1499
1500 static void update_audio_file_server_handler(char *name)
1501 {
1502         char *info;
1503         info = get_selector_info(name);
1504         update_mmd(info);
1505         free(info);
1506         update_audio_file(name);
1507 }
1508
1509 int com_us(__a_unused int fd, int argc, char *argv[])
1510 {
1511         char *tmp;
1512         int ret;
1513
1514         if (argc != 2)
1515                 return -E_MYSQL_SYNTAX;
1516         tmp = escape_str(argv[1]);
1517         if (!tmp)
1518                 return -E_ESCAPE;
1519         ret = update_audio_file(argv[1]);
1520         free(tmp);
1521         if (ret >= 0)
1522                 refresh_selector_info();
1523         return ret;
1524 }
1525
1526 /* select previous / next stream */
1527 static int com_ps_ns(__a_unused int fd, int argc, char *argv[])
1528 {
1529         char *query, *stream = get_current_stream();
1530         void *result = get_result("select name from streams");
1531         MYSQL_ROW row;
1532         int ret;
1533         my_ulonglong num_rows, match, i;
1534
1535         ret = -E_MYSQL_SYNTAX;
1536         if (argc != 1)
1537                 goto out;
1538         ret = -E_NORESULT;
1539         if (!result)
1540                 goto out;
1541         num_rows = mysql_num_rows(result);
1542         ret = -E_EMPTY_RESULT;
1543         if (num_rows < 2)
1544                 goto out;
1545         ret = -E_NOROW;
1546         for (i = 0; i < num_rows; i++) {
1547                 row = mysql_fetch_row(result);
1548                 if (!row || !row[0])
1549                         goto out;
1550                 if (!strcmp(row[0], "current_stream"))
1551                         continue;
1552                 if (!strcmp(row[0], stream))
1553                         break;
1554         }
1555         ret = -E_NO_STREAM;
1556         if (i == num_rows)
1557                 goto out;
1558         match = i;
1559         if (!strcmp(argv[0], "ps"))
1560                 i = match > 0? match - 1 : num_rows - 1;
1561         else
1562                 i = match < num_rows - 1? match + 1 : 0;
1563         ret = -E_NOROW;
1564         mysql_data_seek(result, i);
1565         row = mysql_fetch_row(result);
1566         if (!row || !row[0])
1567                 goto out;
1568         if (!strcmp(row[0], "current_stream")) {
1569                 if (!strcmp(argv[0], "ps")) {
1570                         i = match < 2? match + num_rows - 2 : match - 2;
1571                 } else {
1572                         i = match + 2;
1573                         i = i > num_rows - 1? i - num_rows : i;
1574                 }
1575                 mysql_data_seek(result, i);
1576                 row = mysql_fetch_row(result);
1577                 if (!row || !row[0])
1578                         goto out;
1579         }
1580         query = make_message("update streams set def='%s' where name = "
1581                 "'current_stream'", row[0]);
1582         ret = real_query(query);
1583         free(query);
1584         refresh_selector_info();
1585 out:
1586         free(stream);
1587         if (result)
1588                 mysql_free_result(result);
1589         return ret;
1590 }
1591
1592 /* select previous stream */
1593 int com_ps(int fd, int argc, char *argv[])
1594 {
1595         return com_ps_ns(fd, argc, argv);
1596 }
1597
1598 /* select next stream */
1599 int com_ns(int fd, int argc, char *argv[])
1600 {
1601         return com_ps_ns(fd, argc, argv);
1602 }
1603
1604 /* streams */
1605 int com_streams(int fd, int argc, __a_unused char *argv[])
1606 {
1607         unsigned int num_rows;
1608         int i, ret = -E_NORESULT;
1609         void *result;
1610         MYSQL_ROW row;
1611
1612         if (argc > 1 && strcmp(argv[1], "current_stream"))
1613                 return -E_MYSQL_SYNTAX;
1614         if (argc > 1) {
1615                 char *cs = get_current_stream();
1616                 ret = send_va_buffer(fd, "%s\n", cs);
1617                 free(cs);
1618                 return ret;
1619         }
1620         result = get_result("select name from streams");
1621         if (!result)
1622                 goto out;
1623         num_rows = mysql_num_rows(result);
1624         ret = 1;
1625         if (!num_rows)
1626                 goto out;
1627         ret = -E_NOROW;
1628         for (i = 0; i < num_rows; i++) {
1629                 row = mysql_fetch_row(result);
1630                 if (!row || !row[0])
1631                         goto out;
1632                 if (strcmp(row[0], "current_stream"))
1633                         send_va_buffer(fd, "%s\n", row[0]);
1634         }
1635         ret = 1;
1636 out:
1637         if (result)
1638                 mysql_free_result(result);
1639         return ret;
1640 }
1641
1642 /* query stream definition */
1643 int com_strq(int fd, int argc, char *argv[])
1644 {
1645         MYSQL_ROW row;
1646         char *query, *name;
1647         void *result;
1648         int ret;
1649
1650         if (argc < 2) {
1651                 ret = -E_GET_STREAM;
1652                 name = get_current_stream();
1653         } else {
1654                 ret = -E_ESCAPE;
1655                 name = escaped_basename(argv[1]);
1656         }
1657         if (!name)
1658                 return ret;
1659         ret = -E_NORESULT;
1660         query = make_message("select def from streams where name='%s'", name);
1661         free(name);
1662         result = get_result(query);
1663         free(query);
1664         if (!result)
1665                 goto out;
1666         ret = -E_NOROW;
1667         row = mysql_fetch_row(result);
1668         if (!row || !row[0])
1669                 goto out;
1670         /* no '\n' needed */
1671         ret = send_buffer(fd, row[0]);
1672 out:
1673         if (result)
1674                 mysql_free_result(result);
1675         return ret;
1676 }
1677
1678 /* change stream / change stream and play */
1679 static int com_cs_csp(int fd, int argc, char *argv[])
1680 {
1681         int ret, stream_change;
1682         char *query, *stream = NULL;
1683         char *old_stream = get_current_stream();
1684         int csp = !strcmp(argv[0], "csp");
1685
1686         ret = -E_MYSQL_SYNTAX;
1687         if (argc > 2)
1688                 goto out;
1689         if (argc == 1) {
1690                 if (csp)
1691                         goto out;
1692                 ret = send_va_buffer(fd, "%s\n", old_stream);
1693                 goto out;
1694         }
1695         ret = -E_GET_QUERY;
1696         /* test if stream is valid, no need to escape argv[1] */
1697         query = get_query(argv[1], NULL, 0);
1698         if (!query)
1699                 goto out;
1700         free(query);
1701         /* stream is ok */
1702         stream = escape_str(argv[1]);
1703         if (!stream)
1704                 goto out;
1705         stream_change = strcmp(stream, old_stream);
1706         if (stream_change) {
1707                 ret = change_stream(stream);
1708                 if (ret < 0)
1709                         goto out;
1710                 refresh_selector_info();
1711         }
1712         if (csp) {
1713                 mmd_lock();
1714                 mmd->new_vss_status_flags |= VSS_PLAYING;
1715                 if (stream_change)
1716                         mmd->new_vss_status_flags |= VSS_NEXT;
1717                 mmd_unlock();
1718         }
1719         ret = 1;
1720 out:
1721         free(old_stream);
1722         free(stream);
1723         return ret;
1724 }
1725
1726 /* change stream */
1727 int com_cs(int fd, int argc, char *argv[])
1728 {
1729         return com_cs_csp(fd, argc, argv);
1730 }
1731
1732 /* change stream and play */
1733 int com_csp(int fd, int argc, char *argv[])
1734 {
1735         return com_cs_csp(fd, argc, argv);
1736 }
1737
1738 /* score list / skip */
1739 static int com_sl_skip(int fd, int argc, char *argv[])
1740 {
1741         void *result = NULL;
1742         MYSQL_ROW row;
1743         int ret, i, skip = !strcmp(argv[0], "skip");
1744         char *query, *stream, *tmp;
1745         unsigned int num_rows, num;
1746
1747         if (argc < 2)
1748                 return -E_MYSQL_SYNTAX;
1749         num = atoi(argv[1]);
1750         if (!num)
1751                 return -E_MYSQL_SYNTAX;
1752         if (argc == 2) {
1753                 stream = get_current_stream();
1754                 if (!stream)
1755                         return -E_GET_STREAM;
1756         } else {
1757                 stream = escape_str(argv[2]);
1758                 if (!stream)
1759                         return -E_ESCAPE;
1760         }
1761         tmp = get_query(stream, NULL, 0);
1762         free(stream);
1763         if (!tmp)
1764                 return -E_GET_QUERY;
1765         query = make_message("%s limit %d", tmp, num);
1766         free(tmp);
1767         ret = -E_NORESULT;
1768         result = get_result(query);
1769         free(query);
1770         if (!result)
1771                 goto out;
1772         ret = -E_EMPTY_RESULT;
1773         num_rows = mysql_num_rows(result);
1774         if (!num_rows)
1775                 goto out;
1776         for (i = 0; i < num_rows && i < num; i++) {
1777                 row = mysql_fetch_row(result);
1778                 if (skip) {
1779                         send_va_buffer(fd, "Skipping %s\n", row[0]);
1780                         update_audio_file(row[0]);
1781                 } else
1782                         send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
1783         }
1784         ret = 1;
1785 out:
1786         if (result)
1787                 mysql_free_result(result);
1788         return ret;
1789 }
1790
1791 /* score list */
1792 int com_sl(int fd, int argc, char *argv[])
1793 {
1794         return com_sl_skip(fd, argc, argv);
1795 }
1796
1797 /* skip */
1798 int com_skip(int fd, int argc, char *argv[])
1799 {
1800         return com_sl_skip(fd, argc, argv);
1801 }
1802
1803 /*
1804  * update attributes of name
1805  */
1806 static int update_atts(int fd, char *name, char *atts)
1807 {
1808         int ret;
1809         char *ebn, *q, *old, *new = NULL;
1810
1811         if (!mysql_ptr)
1812                 return -E_NOTCONN;
1813         ebn = escaped_basename(name);
1814         if (!ebn)
1815                 return -E_ESCAPE;
1816         q = make_message("update data set %s where name = '%s'", atts, ebn);
1817         old = get_atts(ebn, 0);
1818         send_va_buffer(fd, "old: %s\n", old);
1819         free(old);
1820         ret = real_query(q);
1821         free(q);
1822         if (ret < 0)
1823                 goto out;
1824         new = get_atts(ebn, 0);
1825         ret = send_va_buffer(fd, "new: %s\n", new);
1826         free(new);
1827 out:
1828         free(ebn);
1829         return ret;
1830 }
1831
1832 /*
1833  * set attributes
1834  */
1835 int com_sa(int fd, int argc, char *argv[])
1836 {
1837         int i, ret;
1838         char *atts = NULL, *name;
1839
1840         if (argc < 2)
1841                 return -E_MYSQL_SYNTAX;
1842         for (i = 1; i < argc; i++) {
1843                 int unset = 0;
1844                 char *esc, *tmp, *p =argv[i];
1845                 int len = strlen(p);
1846
1847                 if (!len)
1848                         continue;
1849                 switch (p[len - 1]) {
1850                         case '+':
1851                                 unset = 0;
1852                                 break;
1853                         case '-':
1854                                 unset = 1;
1855                                 break;
1856                         default:
1857                                 goto no_more_atts;
1858                 }
1859                 p[len - 1] = '\0';
1860                 esc = escape_str(p);
1861                 if (!esc)
1862                         return -E_ESCAPE;
1863                 tmp = make_message("%s%s='%s'", atts? "," : "", esc,
1864                         unset? "0" : "1");
1865                 free(esc);
1866                 atts = para_strcat(atts, tmp);
1867                 free(tmp);
1868         }
1869 no_more_atts:
1870         if (!atts)
1871                 return -E_NOATTS;
1872         if (i >= argc) { /* no name given, use current af */
1873                 ret = -E_GET_AUDIO_FILE;
1874                 if (!(name = get_current_audio_file()))
1875                         goto out;
1876                 ret = update_atts(fd, name, atts);
1877                 free(name);
1878         } else {
1879                 ret = 1;
1880                 for (; argv[i] && ret >= 0; i++)
1881                         ret = update_atts(fd, argv[i], atts);
1882         }
1883         refresh_selector_info();
1884 out:
1885         free(atts);
1886         return ret;
1887 }
1888
1889 /*
1890  * copy attributes
1891  */
1892 int com_cam(int fd, int argc, char *argv[])
1893 {
1894         char *name = NULL, *meta = NULL, *atts = NULL;
1895         int i, ret;
1896
1897         if (argc < 3)
1898                 return -E_MYSQL_SYNTAX;
1899         if (!(name = escaped_basename(argv[1])))
1900                 return -E_ESCAPE;
1901         ret = -E_NOATTS;
1902         if (!(atts = get_atts(name, 1)))
1903                 goto out;
1904         ret = -E_META;
1905         if (!(meta = get_meta(name, 0)))
1906                 goto out;
1907         for (i = 2; i < argc; i++) {
1908                 char *ebn, *q;
1909                 ret = -E_ESCAPE;
1910                 if (!(ebn = escaped_basename(argv[i])))
1911                         goto out;
1912                 ret = send_va_buffer(fd, "updating %s\n", ebn);
1913                 if (ret < 0) {
1914                         free(ebn);
1915                         goto out;
1916                 }
1917                 q = make_message("update data set %s where name = '%s'",
1918                         meta, ebn);
1919                 if ((ret = update_atts(fd, ebn, atts)) >= 0)
1920                         ret = real_query(q);
1921                 free(ebn);
1922                 free(q);
1923                 if (ret < 0)
1924                         goto out;
1925         }
1926         ret = 1;
1927 out:
1928         if (name)
1929                 free(name);
1930         if (meta)
1931                 free(meta);
1932         if (atts)
1933                 free(atts);
1934         return ret;
1935 }
1936
1937 /*
1938  * verify / clean
1939  */
1940 static int com_vrfy_clean(int fd, int argc, __a_unused char *argv[])
1941 {
1942         char *query;
1943         int ret, vrfy_mode = strcmp(argv[0], "clean");
1944         void *result = NULL;
1945         MYSQL_ROW row;
1946         char *escaped_name;
1947         my_ulonglong num_rows, top = 0, left = 0, right = 0;
1948
1949         if (argc != 1)
1950                 return -E_MYSQL_SYNTAX;
1951         ret = -E_NORESULT;
1952         result = get_result("select data.name from data left join dir on "
1953                 "dir.name = data.name where dir.name is NULL");
1954         if (!result)
1955                 goto out;
1956         num_rows = mysql_num_rows(result);
1957         if (!num_rows) {
1958                 ret = send_buffer(fd, "No invalid entries\n");
1959                 goto out;
1960         }
1961         if (vrfy_mode) {
1962                 send_va_buffer(fd, "found %i invalid entr%s\n", num_rows,
1963                         num_rows == 1? "y" : "ies");
1964                 ret = print_results(fd, result, top, left, num_rows - 1, right);
1965                 goto out;
1966         }
1967         while ((row = mysql_fetch_row(result))) {
1968                 ret = -E_NOROW;
1969                 if (!row[0])
1970                         goto out;
1971                 ret = -E_ESCAPE;
1972                 escaped_name = escape_str(row[0]);
1973                 if (!escaped_name)
1974                         goto out;
1975                 send_va_buffer(fd, "deleting %s\n", escaped_name);
1976                 query = make_message("delete from data where name = '%s'",
1977                         escaped_name);
1978                 ret = real_query(query);
1979                 free(query);
1980                 if (ret < 0)
1981                         goto out;
1982         }
1983
1984 out:
1985         if (result)
1986                 mysql_free_result(result);
1987         return ret;
1988 }
1989
1990 /*
1991  * verify
1992  */
1993 int com_vrfy(int fd, int argc, char **argv)
1994 {
1995         return com_vrfy_clean(fd, argc, argv);
1996 }
1997
1998 /*
1999  * clean
2000  */
2001 int com_clean(int fd, int argc, char **argv)
2002 {
2003         return com_vrfy_clean(fd, argc, argv);
2004 }
2005
2006 static FILE *out_file;
2007
2008 static int mysql_write_tmp_file(const char *dir, const char *name)
2009 {
2010         int ret = -E_TMPFILE;
2011         char *msg = make_message("%s\t%s\n", dir, name);
2012         if (fputs(msg, out_file) != EOF)
2013                 ret = 1;
2014         free(msg);
2015         return ret;
2016 }
2017
2018 /*
2019  * update database
2020  */
2021 int com_upd(int fd, int argc, __a_unused char *argv[])
2022 {
2023         char *tempname = NULL, *query = NULL;
2024         int ret, out_fd = -1, num = 0;
2025         void *result = NULL;
2026         unsigned int num_rows;
2027         MYSQL_ROW row;
2028
2029         if (argc != 1)
2030                 return -E_MYSQL_SYNTAX;
2031         out_file = NULL;
2032         tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
2033         ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
2034         if (ret < 0)
2035                 goto out;
2036         out_fd = ret;
2037         out_file = fdopen(out_fd, "w");
2038         if (!out_file) {
2039                 close(out_fd);
2040                 goto out;
2041         }
2042         if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
2043                 goto out;
2044         num = ftell(out_file);
2045         /*
2046          * we have to make sure the file hit the disk before we call
2047          * real_query
2048          */
2049         fclose(out_file);
2050         out_file = NULL;
2051         PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
2052         if (!num)
2053                 goto out;
2054         if ((ret = real_query("delete from dir")) < 0)
2055                 goto out;
2056         query = make_message("load data infile '%s' ignore into table dir "
2057                 "fields terminated by '\t' lines terminated by '\n' "
2058                 "(dir, name)", tempname);
2059         ret = real_query(query);
2060         free(query);
2061         if (ret < 0)
2062                 goto out;
2063         result = get_result("select dir.name from dir left join data on "
2064                 "data.name = dir.name where data.name is NULL");
2065         ret = -E_NORESULT;
2066         if (!result)
2067                 goto out;
2068         num_rows = mysql_num_rows(result);
2069         if (!num_rows) {
2070                 ret = send_buffer(fd, "no new entries\n");
2071                 goto out;
2072         }
2073         while ((row = mysql_fetch_row(result))) {
2074                 char *erow;
2075                 ret = -E_NOROW;
2076                 if (!row[0])
2077                         goto out;
2078                 send_va_buffer(fd, "new entry: %s\n", row[0]);
2079                 erow = escape_str(row[0]);
2080                 if (!erow)
2081                         goto out;
2082                 query = make_message("insert into data (name, pic_id) values "
2083                         "('%s','%s')", erow, "1");
2084                 free(erow);
2085                 ret = real_query(query);
2086                 free(query);
2087                 if (ret < 0)
2088                         goto out;
2089         }
2090         ret = 1;
2091 out:
2092         if (out_fd >= 0)
2093                 unlink(tempname);
2094         free(tempname);
2095         if (out_file)
2096                 fclose(out_file);
2097         if (result)
2098                 mysql_free_result(result);
2099         return ret;
2100 }
2101
2102 static char **server_get_audio_file_list(unsigned int num)
2103 {
2104         char **list = para_malloc((num + 1) * sizeof(char *));
2105         char *tmp, *query, *stream = get_current_stream();
2106         void *result = NULL;
2107         unsigned int num_rows;
2108         int i = 0;
2109         MYSQL_ROW row;
2110
2111         tmp = get_query(stream, NULL, 1);
2112         free(stream);
2113         if (!tmp)
2114                 goto err_out;
2115         query = make_message("%s limit %d", tmp, num);
2116         free(tmp);
2117         result = get_result(query);
2118         free(query);
2119         if (!result)
2120                 goto err_out;
2121         num_rows = mysql_num_rows(result);
2122         if (!num_rows)
2123                 goto err_out;
2124         for (i = 0; i < num_rows && i < num; i++) {
2125                 row = mysql_fetch_row(result);
2126                 if (!row || !row[0])
2127                         goto err_out;
2128                 list[i] = para_strdup(row[0]);
2129         }
2130         list[i] = NULL;
2131         goto success;
2132 err_out:
2133         while (i > 0) {
2134                 i--;
2135                 free(list[i]);
2136         }
2137         free(list);
2138         list = NULL;
2139 success:
2140         if (result)
2141                 mysql_free_result(result);
2142         return list;
2143 }
2144
2145 /*
2146  * connect to mysql server, return mysql pointer on success, -E_NOTCONN
2147  * on errors. Called from parent on startup and also from com_cdb().
2148  */
2149 static int init_mysql_server(void)
2150 {
2151         char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
2152         unsigned int port;
2153
2154         mysql_ptr = mysql_init(NULL);
2155         if (!mysql_ptr) {
2156                 PARA_CRIT_LOG("%s", "mysql init error\n");
2157                 return -E_NOTCONN;
2158         }
2159         if (conf.mysql_port_arg < 0)
2160                 return -E_MYSQL_SYNTAX;
2161         port = conf.mysql_port_arg;
2162         PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg, port);
2163         if (!conf.mysql_user_arg)
2164                 free(u);
2165         /*
2166          * If host is NULL a connection to the local host is assumed,
2167          * If user is NULL, the current user is assumed
2168          */
2169         if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
2170                         conf.mysql_host_arg,
2171                         conf.mysql_user_arg,
2172                         conf.mysql_passwd_arg,
2173                         conf.mysql_database_arg,
2174                         port, NULL, 0))) {
2175                 PARA_CRIT_LOG("%s", "connect error\n");
2176                 return -E_NOTCONN;
2177         }
2178         PARA_INFO_LOG("%s", "success\n");
2179         return 1;
2180 }
2181
2182 /* mmd lock must be held */
2183 static void write_msg2mmd(int success)
2184 {
2185         sprintf(mmd->selector_info, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
2186                 success < 0? PARA_STRERROR(-success) :
2187                         "successfully connected to mysql server",
2188                 success < 0? "" : mysql_get_server_info(mysql_ptr));
2189 }
2190
2191 /* create database */
2192 int com_cdb(int fd, int argc, char *argv[])
2193 {
2194         char *query;
2195         int ret;
2196
2197         if (mysql_ptr) {
2198                 PARA_INFO_LOG("%s", "closing database\n");
2199                 mysql_close(mysql_ptr);
2200         }
2201         /* dont use any database */
2202         conf.mysql_database_arg = NULL; /* leak? */
2203         ret = -E_MYSQL_INIT;
2204         if (init_mysql_server() < 0 || !mysql_ptr)
2205                 goto out;
2206         if (argc < 2)
2207                 conf.mysql_database_arg = para_strdup("paraslash");
2208         else {
2209                 ret = -E_ESCAPE;
2210                 conf.mysql_database_arg = escape_str(argv[1]);
2211                 if (!conf.mysql_database_arg)
2212                         goto out;
2213         }
2214         query = make_message("create database %s", conf.mysql_database_arg);
2215         ret = real_query(query);
2216         free(query);
2217         if (ret < 0)
2218                 goto out;
2219         /* reconnect with database just created */
2220         mysql_close(mysql_ptr);
2221         ret = -E_MYSQL_INIT;
2222         if (init_mysql_server() < 0 || !mysql_ptr)
2223                 goto out;
2224         mmd_lock();
2225         write_msg2mmd(1);
2226         mmd_unlock();
2227         ret = -E_QFAILED;
2228         if (real_query("create table data (name varchar(255) binary not null "
2229                         "primary key, "
2230                         "lastplayed datetime not null default "
2231                                 "'1970-01-01', "
2232                         "numplayed int not null default 0, "
2233                         "pic_id bigint unsigned not null default 1)") < 0)
2234                 goto out;
2235         if (real_query("create table dir (name varchar(255) binary not null "
2236                         "primary key, dir varchar(255) default null)") < 0)
2237                 goto out;
2238         if (real_query("create table pics ("
2239                         "id bigint(20) unsigned not null primary key "
2240                         "auto_increment, "
2241                         "name varchar(255) binary not null, "
2242                         "pic mediumblob not null)") < 0)
2243                 goto out;
2244         if (real_query("create table streams ("
2245                         "name varchar(255) binary not null primary key, "
2246                         "def blob not null)") < 0)
2247                 goto out;
2248         if (real_query("insert into streams (name, def) values "
2249                         "('current_stream', '(none)')") < 0)
2250                 goto out;
2251         ret = send_va_buffer(fd, "successfully created database %s\n",
2252                 conf.mysql_database_arg);
2253 out:
2254         return ret;
2255 }
2256
2257 static void shutdown_connection(void)
2258 {
2259         if (mysql_ptr) {
2260                 PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
2261                 mysql_close(mysql_ptr);
2262                 mysql_ptr = NULL;
2263         }
2264 }
2265
2266 /**
2267  * the init function of the mysql-based audio file selector
2268  *
2269  * \param db pointer to the struct to initialize
2270  *
2271  * Check the command line options and initialize all function pointers of \a
2272  * db.  Connect to the mysql server and initialize the info string.
2273  *
2274  * \return This function returns success even if it could not connect
2275  * to the mysql server. This is because the connect is expected to fail
2276  * if there the paraslash database is not yet created. This gives the
2277  * user a chance to send the "cdb" to create the database.
2278  *
2279  * \sa struct audio_file_selector, misc_meta_data::selector_info,
2280  * random_selector.c
2281  */
2282 int mysql_selector_init(struct audio_file_selector *db)
2283 {
2284         int ret;
2285
2286         if (!conf.mysql_passwd_given)
2287                 return -E_NO_MYSQL_PASSWD;
2288         if (!conf.mysql_audio_file_dir_given)
2289                 return -E_NO_AF_DIR;
2290         db->name = "mysql";
2291         db->cmd_list = mysql_selector_cmds;
2292         db->get_audio_file_list = server_get_audio_file_list;
2293         db->update_audio_file = update_audio_file_server_handler;
2294         db->shutdown = shutdown_connection;
2295         ret = init_mysql_server();
2296         if (ret < 0)
2297                 PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
2298         write_msg2mmd(ret);
2299         return 1;       /* return success even if connect failed to give the
2300                          * user the chance to exec com_cdb
2301                          */
2302 }