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