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