gui.c: make command pipe a nonblocking fd
[paraslash.git] / mysql_selector.c
index f1c49aa5c7b9689a06f370bae72a85f42aa6738b..f8885f9a0f50ea0dd185e4cbecbc3a0ab17d1d0e 100644 (file)
@@ -28,6 +28,7 @@
 #include "db.h"
 #include <mysql/mysql.h>
 #include <mysql/mysql_version.h>
+#include <regex.h>
 #include "error.h"
 #include "net.h"
 #include "string.h"
@@ -39,6 +40,18 @@ extern struct misc_meta_data *mmd;
 
 static void *mysql_ptr = NULL;
 
+/**
+ * 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 struct para_macro macro_list[] = {
        {       .name = "IS_N_SET",
                .replacement = "(data.%s != '1')"
@@ -63,6 +76,93 @@ static struct para_macro macro_list[] = {
        }
 };
 
+/**
+ * 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 syntax 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);
+       regcomp(&preg, macro_name, 0);
+       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(struct para_macro *macro_list, char *src)
+{
+       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 real_query(const char *query)
 {
        if (!mysql_ptr)
@@ -766,7 +866,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,14 +892,10 @@ 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;
@@ -2156,8 +2252,15 @@ static void shutdown_connection(void)
 /**
  * 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,7 +2274,7 @@ 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;