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