X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=mysql_selector.c;h=bbc05bdf39ddcf9a4f8e9c503d3d21efff72ae67;hp=f1c49aa5c7b9689a06f370bae72a85f42aa6738b;hb=de25f9d0d999b2a911ecc93d19511ff437211d18;hpb=d8f49a6a3eadcdbfb39f15bf9cff5f251fd16125 diff --git a/mysql_selector.c b/mysql_selector.c index f1c49aa5..bbc05bdf 100644 --- a/mysql_selector.c +++ b/mysql_selector.c @@ -1,19 +1,7 @@ /* * Copyright (C) 1999-2007 Andre Noll * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file mysql_selector.c para_server's mysql-based audio file selector */ @@ -25,21 +13,36 @@ #include "server.cmdline.h" #include "server.h" #include "vss.h" -#include "db.h" +#include "afs.h" #include #include +#include #include "error.h" #include "net.h" #include "string.h" #include "user_list.h" #include "mysql_selector_command_list.h" +#include "ipc.h" /** pointer to the shared memory area */ extern struct misc_meta_data *mmd; static void *mysql_ptr = NULL; +static int mysql_lock; -static struct para_macro macro_list[] = { +/** + * contains name/replacement pairs used by s_a_r_list() + * + * \sa s_a_r() + */ +struct para_macro { + /** the name of the macro */ + const char *name; + /** the replacement text */ + const char *replacement; +}; + +static const struct para_macro mysql_macro_list[] = { { .name = "IS_N_SET", .replacement = "(data.%s != '1')" }, { @@ -63,7 +66,96 @@ static struct para_macro macro_list[] = { } }; -static int real_query(const char *query) +/** + * Simple search and replace routine. + * + * \param src Source String. + * \param macro_name The name of the macro. + * \param replacement The replacement format string. + * + * In \p src, replace each occurence of \p macro_name(arg) by the string + * determined by the \p replacement format string. \p replacement may (but + * needs not) contain a single string conversion specifier (%s) which gets + * replaced by \p arg. + * + * \return A string in which all matches in \p src are replaced, or \p NULL if + * an error was encountered. Caller must free the result. + * + * \sa regcomp(3) + */ +__must_check __malloc static char *s_a_r(const char *src, const char* macro_name, + const char *replacement) +{ + regex_t preg; + size_t nmatch = 1; + regmatch_t pmatch[1]; + int eflags = 0; + char *dest = NULL; + const char *bufptr = src; + + if (!macro_name || !replacement || !src) + return para_strdup(src); + if (regcomp(&preg, macro_name, 0) != 0) + return NULL; + while (regexec(&preg, bufptr, nmatch, pmatch, eflags) + != REG_NOMATCH) { + char *tmp, *arg, *o_bracket, *c_bracket; + + o_bracket = strchr(bufptr + pmatch[0].rm_so, '('); + c_bracket = o_bracket? strchr(o_bracket, ')') : NULL; + if (!c_bracket) + goto out; + tmp = para_strdup(bufptr); + tmp[pmatch[0].rm_so] = '\0'; + dest = para_strcat(dest, tmp); + free(tmp); + + arg = para_strdup(o_bracket + 1); + arg[c_bracket - o_bracket - 1] = '\0'; + tmp = make_message(replacement, arg); + free(arg); + dest = para_strcat(dest, tmp); + free(tmp); + bufptr = c_bracket; + bufptr++; + } + dest = para_strcat(dest, bufptr); +// PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest); +out: + regfree(&preg); + return dest; +} + +/** + * replace a string according to a list of macros + * + * \param macro_list the array containing a macro/replacement pairs. + * \param src the source string + * + * This function just calls s_a_r() for each element of \p macro_list. + * + * \return \p NULL if one of the underlying calls to \p s_a_r returned \p NULL. + * Otherwise the completely expanded version of \p src is returned. + */ +__must_check __malloc static char *s_a_r_list(const struct para_macro *macro_list, + char *src) +{ + const struct para_macro *mp = macro_list; + char *ret = NULL, *tmp = para_strdup(src); + + while (mp->name) { + ret = s_a_r(tmp, mp->name, mp->replacement); + free(tmp); + if (!ret) /* syntax error */ + return NULL; + tmp = ret; + mp++; + } + //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest); + return ret; +} + +static int lockless_real_query(const char *query) { if (!mysql_ptr) return -E_NOTCONN; @@ -76,19 +168,32 @@ static int real_query(const char *query) return 1; } +static int real_query(const char *query) +{ + int ret; + + mutex_lock(mysql_lock); + ret = lockless_real_query(query); + mutex_unlock(mysql_lock); + return ret; +} + /* * Use open connection given by mysql_ptr to query server. Returns a * result pointer on succes and NULL on errors */ static struct MYSQL_RES *get_result(const char *query) { - void *result; + void *result = NULL; - if (real_query(query) < 0) - return NULL; + mutex_lock(mysql_lock); + if (lockless_real_query(query) < 0) + goto out; result = mysql_store_result(mysql_ptr); if (!result) PARA_ERROR_LOG("%s", "store_result error\n"); +out: + mutex_unlock(mysql_lock); return result; } /* @@ -127,11 +232,11 @@ out: return ret; } -static char *escape_blob(const char* old, int size) +static char *escape_blob(const char* old, size_t size) { char *new; - if (!mysql_ptr || size < 0) + if (!mysql_ptr) return NULL; new = para_malloc(2 * size * sizeof(char) + 1); mysql_real_escape_string(mysql_ptr, new, old, size); @@ -267,8 +372,8 @@ int com_picadd(int fd, int argc, char *argv[]) * print results to fd */ static int print_results(int fd, void *result, - unsigned int top, unsigned int left, - unsigned int bottom, unsigned int right) + my_ulonglong top, my_ulonglong left, + my_ulonglong bottom, my_ulonglong right) { unsigned int i,j; int ret; @@ -298,7 +403,7 @@ int com_verb(int fd, int argc, char *argv[]) { void *result = NULL; int ret; - unsigned int num_rows, num_fields; + my_ulonglong num_rows, num_fields, top = 0, left = 0; char *tmp; if (argc < 2) @@ -315,7 +420,7 @@ int com_verb(int fd, int argc, char *argv[]) num_rows = mysql_num_rows(result); ret = 1; if (num_fields && num_rows) - ret = print_results(fd, result, 0, 0, num_rows - 1, + ret = print_results(fd, result, top, left, num_rows - 1, num_fields - 1); mysql_free_result(result); return ret; @@ -334,7 +439,7 @@ static void *get_all_attributes(void) mysql_free_result(result); return NULL; } - mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */ + mysql_data_seek(result, (my_ulonglong)4); /* skip Lastplayed, Numplayed... */ return result; } @@ -345,13 +450,18 @@ int com_laa(int fd, int argc, __a_unused char *argv[]) { void *result; int ret; + my_ulonglong top = 0, left = 0, bottom, right = 0; if (argc != 1) return -E_MYSQL_SYNTAX; result = get_all_attributes(); if (!result) return -E_NOATTS; - ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0); + bottom = mysql_num_rows(result); + if (bottom < 5) + return -E_MYSQL_SYNTAX; + bottom -= 5; + ret = print_results(fd, result, top, left, bottom, right); mysql_free_result(result); return ret; } @@ -359,11 +469,12 @@ int com_laa(int fd, int argc, __a_unused char *argv[]) /* * history */ -int com_hist(int fd, int argc, char *argv[]) { +int com_hist(int fd, int argc, char *argv[]) +{ int ret; void *result = NULL; char *q, *atts; - unsigned int num_rows; + my_ulonglong num_rows, top = 0, left = 0, right = 1; if (argc > 3) return -E_MYSQL_SYNTAX; @@ -386,7 +497,7 @@ int com_hist(int fd, int argc, char *argv[]) { num_rows = mysql_num_rows(result); ret = 1; if (num_rows) - ret = print_results(fd, result, 0, 0, num_rows - 1, 1); + ret = print_results(fd, result, top, left, num_rows - 1, right); mysql_free_result(result); return ret; } @@ -399,6 +510,7 @@ int com_last(int fd, int argc, char *argv[]) void *result = NULL; char *q; int num, ret; + my_ulonglong top = 0, left = 0, right = 0; if (argc < 2) num = 10; @@ -412,7 +524,8 @@ int com_last(int fd, int argc, char *argv[]) free(q); if (!result) return -E_NORESULT; - ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0); + ret = print_results(fd, result, top, left, mysql_num_rows(result) - 1, + right); mysql_free_result(result); return ret; } @@ -422,7 +535,7 @@ int com_mbox(int fd, int argc, char *argv[]) void *result; MYSQL_ROW row; int ret; - unsigned int num_rows, num_fields; + my_ulonglong num_rows, num_fields, top = 0, left = 0; char *query = para_strdup("select concat('From foo@localhost ', " "date_format(Lastplayed, '%a %b %e %T %Y'), " "'\nReceived: from\nTo: bar\n"); @@ -437,7 +550,7 @@ int com_mbox(int fd, int argc, char *argv[]) if (!row[0]) goto out; - tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query, + tmp = make_message("%sX-Attribute-%s: ', %s, '\n", query, row[0], row[0]); free(query); query = tmp; @@ -469,7 +582,8 @@ int com_mbox(int fd, int argc, char *argv[]) num_rows = mysql_num_rows(result); if (!num_fields || !num_rows) goto out; - ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1); + ret = print_results(fd, result, top, left, num_rows - 1, + num_fields - 1); out: free(query); if (result) @@ -477,40 +591,39 @@ out: return ret; } -/* get attributes by name. If verbose is not 0, get_a writes a string - * into atts of the form 'att1="0",att2="1"', which is used in com_cam - * for contructing a mysql update query. - * never returns NULL in *NON VERBOSE* mode +/* + * get attributes by name. If verbose is not 0, this function returns a string + * of the form 'att1="0",att2="1"'... which is used in com_cam() for + * constructing a mysql update query. Otherwise the space-separated list of all + * attributes which are set in the audio file given by name is returned. Never + * returns NULL in *NON VERBOSE* mode. */ static char *get_atts(char *name, int verbose) { char *atts = NULL, *buf, *ebn; void *result = NULL, *result2 = NULL; MYSQL_ROW row, row2; - int i, ret; - unsigned int num_fields; + int i; + my_ulonglong num_fields, offset = 4; /* skip Lastplayed, Numplayed... */ + - ret = -E_NOATTS; result2 = get_all_attributes(); if (!result2) goto out; - ret = -E_ESCAPE; - if (!(ebn = escaped_basename(name))) + ebn = escaped_basename(name); + if (!ebn) goto out; buf = make_message("select * from data where name='%s'", ebn); free(ebn); - ret = -E_NORESULT; result = get_result(buf); free(buf); if (!result) goto out; - ret = -E_EMPTY_RESULT; num_fields = mysql_num_fields(result); if (num_fields < 5) goto out; - mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */ + mysql_data_seek(result2, offset); row = mysql_fetch_row(result); - ret = -E_NOROW; if (!row) goto out; for (i = 4; i < num_fields; i++) { @@ -525,7 +638,6 @@ static char *get_atts(char *name, int verbose) if (verbose) atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\""); } - ret = 1; out: if (result2) mysql_free_result(result2); @@ -687,7 +799,7 @@ static char *get_query(char *streamname, char *filename, int with_path) continue; arg = line + n; if (!strcmp(command, "accept:")) { - char *tmp2 = s_a_r_list(macro_list, arg); + char *tmp2 = s_a_r_list(mysql_macro_list, arg); if (accept_opts) accept_opts = para_strcat( accept_opts, " or "); @@ -696,7 +808,7 @@ static char *get_query(char *streamname, char *filename, int with_path) continue; } if (!strcmp(command, "deny:")) { - char *tmp2 = s_a_r_list(macro_list, arg); + char *tmp2 = s_a_r_list(mysql_macro_list, arg); if (deny_opts) deny_opts = para_strcat(deny_opts, " or "); deny_opts = para_strcat(deny_opts, tmp2); @@ -704,10 +816,10 @@ static char *get_query(char *streamname, char *filename, int with_path) continue; } if (!score && !strcmp(command, "score:")) - score = s_a_r_list(macro_list, arg); + score = s_a_r_list(mysql_macro_list, arg); } if (!score) { - score = s_a_r_list(macro_list, conf.mysql_default_score_arg); + score = s_a_r_list(mysql_macro_list, conf.mysql_default_score_arg); if (!score) goto out; } @@ -766,7 +878,7 @@ out: */ static char *get_selector_info(char *name) { - char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL; + char *meta, *atts, *info, *dir, *query, *stream; void *result = NULL; MYSQL_ROW row = NULL; @@ -792,20 +904,15 @@ write: stream, meta, (result && row && row[0])? row[0] : "(no score)", atts); - if (dir) - free(dir); - if (meta) - free(meta); - if (atts) - free(atts); - if (stream) - free(stream); + free(dir); + free(meta); + free(atts); + free(stream); if (result) mysql_free_result(result); return info; } - /* might return NULL */ static char *get_current_audio_file(void) { @@ -816,11 +923,34 @@ static char *get_current_audio_file(void) return name; } +/* If called as child, mmd_lock must be held */ +static void update_mmd(char *info) +{ + PARA_DEBUG_LOG("%s", "updating shared memory area\n"); + strncpy(mmd->selector_info, info, MMD_INFO_SIZE - 1); + mmd->selector_info[MMD_INFO_SIZE - 1] = '\0'; +} + +static void refresh_selector_info(void) +{ + char *name = get_current_audio_file(); + char *info; + + if (!name) + return; + info = get_selector_info(name); + free(name); + mmd_lock(); + update_mmd(info); + mmd_unlock(); + free(info); +} + /* list attributes / print database info */ static int com_la_info(int fd, int argc, char *argv[]) { char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL; - int ret, com_la = strcmp(argv[0], "info"); + int ret, la = strcmp(argv[0], "info"); if (argc < 2) { ret = -E_GET_AUDIO_FILE; @@ -837,7 +967,7 @@ static int com_la_info(int fd, int argc, char *argv[]) meta = get_meta(name, 1); atts = get_atts(name, 0); dir = get_dir(name); - if (com_la) + if (la) ret = send_va_buffer(fd, "%s\n", atts); else ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n", @@ -877,7 +1007,7 @@ static int get_pic_id_by_name(char *name) { char *q, *ebn; void *result = NULL; - long unsigned ret; + int ret; MYSQL_ROW row; if (!(ebn = escaped_basename(name))) @@ -891,7 +1021,7 @@ static int get_pic_id_by_name(char *name) row = mysql_fetch_row(result); ret = -E_NOROW; if (row && row[0]) - ret = atol(row[0]); + ret = atoi(row[0]); mysql_free_result(result); return ret; } @@ -1091,7 +1221,10 @@ int com_picass(int fd, int argc, char *argv[]) */ int com_snp(int fd, int argc, char *argv[]) { - return com_set(fd, argc, argv); + int ret = com_set(fd, argc, argv); + if (ret >= 0) + refresh_selector_info(); + return ret; } /* @@ -1247,7 +1380,7 @@ int com_ls(int fd, int argc, char *argv[]) char *q; void *result; int ret; - unsigned int num_rows; + my_ulonglong num_rows, top = 0, left = 0, right = 0; if (argc > 2) return -E_MYSQL_SYNTAX; @@ -1267,7 +1400,7 @@ int com_ls(int fd, int argc, char *argv[]) num_rows = mysql_num_rows(result); ret = 1; if (num_rows) - ret = print_results(fd, result, 0, 0, num_rows - 1, 0); + ret = print_results(fd, result, top, left, num_rows - 1, right); mysql_free_result(result); return ret; } @@ -1368,14 +1501,6 @@ out: return ret; } -/* If called as child, mmd_lock must be held */ -static void update_mmd(char *info) -{ - PARA_DEBUG_LOG("%s", "updating shared memory area\n"); - strncpy(mmd->selector_info, info, MMD_INFO_SIZE - 1); - mmd->selector_info[MMD_INFO_SIZE - 1] = '\0'; -} - static void update_audio_file_server_handler(char *name) { char *info; @@ -1397,35 +1522,23 @@ int com_us(__a_unused int fd, int argc, char *argv[]) return -E_ESCAPE; ret = update_audio_file(argv[1]); free(tmp); + if (ret >= 0) + refresh_selector_info(); return ret; } -static void refresh_selector_info(void) -{ - char *name = get_current_audio_file(); - char *info; - - if (!name) - return; - info = get_selector_info(name); - free(name); - mmd_lock(); - update_mmd(info); - mmd_unlock(); - free(info); -} - /* select previous / next stream */ static int com_ps_ns(__a_unused int fd, int argc, char *argv[]) { char *query, *stream = get_current_stream(); void *result = get_result("select name from streams"); MYSQL_ROW row; - int match = -1, ret, i; - unsigned int num_rows; + int ret; + my_ulonglong num_rows, match, i; + ret = -E_MYSQL_SYNTAX; if (argc != 1) - return -E_MYSQL_SYNTAX; + goto out; ret = -E_NORESULT; if (!result) goto out; @@ -1440,14 +1553,13 @@ static int com_ps_ns(__a_unused int fd, int argc, char *argv[]) goto out; if (!strcmp(row[0], "current_stream")) continue; - if (!strcmp(row[0], stream)) { - match = i; + if (!strcmp(row[0], stream)) break; - } } ret = -E_NO_STREAM; - if (match < 0) + if (i == num_rows) goto out; + match = i; if (!strcmp(argv[0], "ps")) i = match > 0? match - 1 : num_rows - 1; else @@ -1459,8 +1571,7 @@ static int com_ps_ns(__a_unused int fd, int argc, char *argv[]) goto out; if (!strcmp(row[0], "current_stream")) { if (!strcmp(argv[0], "ps")) { - i = match - 2; - i = i < 0? i + num_rows : i; + i = match < 2? match + num_rows - 2 : match - 2; } else { i = match + 2; i = i > num_rows - 1? i - num_rows : i; @@ -1835,9 +1946,9 @@ static int com_vrfy_clean(int fd, int argc, __a_unused char *argv[]) char *query; int ret, vrfy_mode = strcmp(argv[0], "clean"); void *result = NULL; - unsigned int num_rows; MYSQL_ROW row; char *escaped_name; + my_ulonglong num_rows, top = 0, left = 0, right = 0; if (argc != 1) return -E_MYSQL_SYNTAX; @@ -1852,9 +1963,9 @@ static int com_vrfy_clean(int fd, int argc, __a_unused char *argv[]) goto out; } if (vrfy_mode) { - send_va_buffer(fd, "found %i invalid entr%s\n", num_rows, + send_va_buffer(fd, "found %lli invalid entr%s\n", num_rows, num_rows == 1? "y" : "ies"); - ret = print_results(fd, result, 0, 0, num_rows - 1, 0); + ret = print_results(fd, result, top, left, num_rows - 1, right); goto out; } while ((row = mysql_fetch_row(result))) { @@ -2042,14 +2153,17 @@ success: static int init_mysql_server(void) { char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname(); + unsigned int port; mysql_ptr = mysql_init(NULL); if (!mysql_ptr) { PARA_CRIT_LOG("%s", "mysql init error\n"); return -E_NOTCONN; } - PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg, - conf.mysql_port_arg); + if (conf.mysql_port_arg < 0) + return -E_MYSQL_SYNTAX; + port = conf.mysql_port_arg; + PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg, port); if (!conf.mysql_user_arg) free(u); /* @@ -2061,7 +2175,7 @@ static int init_mysql_server(void) conf.mysql_user_arg, conf.mysql_passwd_arg, conf.mysql_database_arg, - conf.mysql_port_arg, NULL, 0))) { + port, NULL, 0))) { PARA_CRIT_LOG("%s", "connect error\n"); return -E_NOTCONN; } @@ -2146,18 +2260,33 @@ out: static void shutdown_connection(void) { + int ret; + if (mysql_ptr) { PARA_NOTICE_LOG("%s", "shutting down mysql connection\n"); mysql_close(mysql_ptr); mysql_ptr = NULL; } + if (mysql_lock) { + ret = mutex_destroy(mysql_lock); + if (ret < 0) + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + mysql_lock = 0; + } } /** * the init function of the mysql-based audio file selector * - * Check the command line options and initialize all function pointers of \a db. - * Connect to the mysql server and initialize the info string. + * \param db pointer to the struct to initialize + * + * Check the command line options and initialize all function pointers of \a + * db. Connect to the mysql server and initialize the info string. + * + * \return This function returns success even if it could not connect + * to the mysql server. This is because the connect is expected to fail + * if there the paraslash database is not yet created. This gives the + * user a chance to send the "cdb" to create the database. * * \sa struct audio_file_selector, misc_meta_data::selector_info, * random_selector.c @@ -2171,10 +2300,14 @@ int mysql_selector_init(struct audio_file_selector *db) if (!conf.mysql_audio_file_dir_given) return -E_NO_AF_DIR; db->name = "mysql"; - db->cmd_list = cmds; + db->cmd_list = mysql_selector_cmds; db->get_audio_file_list = server_get_audio_file_list; db->update_audio_file = update_audio_file_server_handler; db->shutdown = shutdown_connection; + ret = mutex_new(); + if (ret < 0) + return ret; + mysql_lock = ret; ret = init_mysql_server(); if (ret < 0) PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));