XMMS2

src/xmms/medialib.c

Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2009 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 #include "xmms_configuration.h"
00018 #include "xmmspriv/xmms_medialib.h"
00019 #include "xmmspriv/xmms_xform.h"
00020 #include "xmmspriv/xmms_utils.h"
00021 #include "xmms/xmms_error.h"
00022 #include "xmms/xmms_config.h"
00023 #include "xmms/xmms_object.h"
00024 #include "xmms/xmms_ipc.h"
00025 #include "xmms/xmms_log.h"
00026 
00027 #include <string.h>
00028 #include <stdlib.h>
00029 
00030 #include <glib.h>
00031 #include <time.h>
00032 
00033 #include <sqlite3.h>
00034 
00035 /**
00036  * @file
00037  * Medialib is a metainfo cache that is searchable.
00038  */
00039 
00040 
00041 static void xmms_medialib_client_entry_remove (xmms_medialib_t *medialib, gint32 entry, xmms_error_t *error);
00042 gchar *xmms_medialib_url_encode (const gchar *path);
00043 static gboolean xmms_medialib_check_id_in_session (xmms_medialib_entry_t entry, xmms_medialib_session_t *session);
00044 
00045 static void xmms_medialib_client_add_entry (xmms_medialib_t *, const gchar *, xmms_error_t *);
00046 static void xmms_medialib_client_move_entry (xmms_medialib_t *, gint32 entry, const gchar *, xmms_error_t *);
00047 static void xmms_medialib_client_path_import (xmms_medialib_t *medialib, const gchar *path, xmms_error_t *error);
00048 static void xmms_medialib_client_rehash (xmms_medialib_t *medialib, gint32 id, xmms_error_t *error);
00049 static void xmms_medialib_client_property_set_str (xmms_medialib_t *medialib, gint32 entry, const gchar *source, const gchar *key, const gchar *value, xmms_error_t *error);
00050 static void xmms_medialib_client_property_set_str (xmms_medialib_t *medialib, gint32 entry, const gchar *source, const gchar *key, const gchar *value, xmms_error_t *error);
00051 static void xmms_medialib_client_property_set_int (xmms_medialib_t *medialib, gint32 entry, const gchar *source, const gchar *key, gint32 value, xmms_error_t *error);
00052 static void xmms_medialib_client_property_remove (xmms_medialib_t *medialib, gint32 entry, const gchar *source, const gchar *key, xmms_error_t *error);
00053 static GTree *xmms_medialib_client_info (xmms_medialib_t *medialib, gint32 id, xmms_error_t *err);
00054 static gint32 xmms_medialib_client_entry_get_id (xmms_medialib_t *medialib, const gchar *url, xmms_error_t *error);
00055 
00056 
00057 XMMS_CMD_DEFINE (info, xmms_medialib_client_info, xmms_medialib_t *, DICT, INT32, NONE);
00058 XMMS_CMD_DEFINE (mlib_add, xmms_medialib_client_add_entry, xmms_medialib_t *, NONE, STRING, NONE);
00059 XMMS_CMD_DEFINE (mlib_remove, xmms_medialib_client_entry_remove, xmms_medialib_t *, NONE, INT32, NONE);
00060 XMMS_CMD_DEFINE (mlib_move, xmms_medialib_client_move_entry, xmms_medialib_t *, NONE, INT32, STRING);
00061 XMMS_CMD_DEFINE (path_import, xmms_medialib_client_path_import, xmms_medialib_t *, NONE, STRING, NONE);
00062 XMMS_CMD_DEFINE (rehash, xmms_medialib_client_rehash, xmms_medialib_t *, NONE, INT32, NONE);
00063 XMMS_CMD_DEFINE (get_id, xmms_medialib_client_entry_get_id, xmms_medialib_t *, INT32, STRING, NONE);
00064 
00065 XMMS_CMD_DEFINE4 (set_property_str, xmms_medialib_client_property_set_str, xmms_medialib_t *, NONE, INT32, STRING, STRING, STRING);
00066 XMMS_CMD_DEFINE4 (set_property_int, xmms_medialib_client_property_set_int, xmms_medialib_t *, NONE, INT32, STRING, STRING, INT32);
00067 
00068 XMMS_CMD_DEFINE3 (remove_property, xmms_medialib_client_property_remove, xmms_medialib_t *, NONE, INT32, STRING, STRING);
00069 
00070 /**
00071  *
00072  * @defgroup Medialib Medialib
00073  * @ingroup XMMSServer
00074  * @brief Medialib caches metadata
00075  *
00076  * Controls metadata storage.
00077  *
00078  * @{
00079  */
00080 
00081 /**
00082  * Medialib structure
00083  */
00084 struct xmms_medialib_St {
00085     xmms_object_t object;
00086     /** The current playlist */
00087     xmms_playlist_t *playlist;
00088 
00089     GMutex *source_lock;
00090     GHashTable *sources;
00091 };
00092 
00093 /**
00094  * This is handed out by xmms_medialib_begin()
00095  */
00096 struct xmms_medialib_session_St {
00097     xmms_medialib_t *medialib;
00098 
00099     /** The SQLite handler */
00100     sqlite3 *sql;
00101 
00102     /** debug file */
00103     const gchar *file;
00104     /** debug line number */
00105     gint line;
00106 
00107     /* Write or read lock, true if write */
00108     gboolean write;
00109 
00110     gint next_id;
00111 };
00112 
00113 
00114 /**
00115   * Ok, so the functions are written with reentrency in mind, but
00116   * we choose to have a global medialib object here. It will be
00117   * much easier, and I don't see the real use of multiple medialibs
00118   * right now. This could be changed by removing this global one
00119   * and altering the function callers...
00120   */
00121 static xmms_medialib_t *medialib;
00122 
00123 static const char source_pref[] = "server:client/*:plugin/id3v2:plugin/*";
00124 
00125 /**
00126   * This is only used if we are using a older version of sqlite.
00127   * The reason for this is that we must have a global session, due to some
00128   * strange limitiations in older sqlite libraries.
00129   */
00130 static xmms_medialib_session_t *global_medialib_session;
00131 
00132 /** This protects the above global session */
00133 static GMutex *global_medialib_session_mutex;
00134 
00135 static GMutex *xmms_medialib_debug_mutex;
00136 static GHashTable *xmms_medialib_debug_hash;
00137 
00138 static void
00139 xmms_medialib_destroy (xmms_object_t *object)
00140 {
00141     xmms_medialib_t *mlib = (xmms_medialib_t *)object;
00142     if (global_medialib_session) {
00143         xmms_sqlite_close (global_medialib_session->sql);
00144         g_free (global_medialib_session);
00145     }
00146     g_mutex_free (mlib->source_lock);
00147     g_hash_table_destroy (mlib->sources);
00148     g_mutex_free (global_medialib_session_mutex);
00149     xmms_ipc_broadcast_unregister (XMMS_IPC_SIGNAL_MEDIALIB_ENTRY_UPDATE);
00150     xmms_ipc_object_unregister (XMMS_IPC_OBJECT_PLAYBACK);
00151 }
00152 
00153 #define XMMS_MEDIALIB_SOURCE_SERVER "server"
00154 #define XMMS_MEDIALIB_SOURCE_SERVER_ID 1
00155 
00156 static gint
00157 source_match_pattern (const gchar *source, const gchar *pattern,
00158                       gint pattern_len)
00159 {
00160     /* check whether we need to keep a wildcard in mind when matching
00161      * the strings.
00162      */
00163     if (pattern_len > 0 && pattern[pattern_len - 1] == '*') {
00164         /* if the asterisk is the first character of the pattern,
00165          * it obviously accepts anything.
00166          */
00167         if (pattern_len == 1) {
00168             return TRUE;
00169         }
00170 
00171         /* otherwise we have to compare the characters just up to the
00172          * asterisk.
00173          */
00174         return !g_ascii_strncasecmp (source, pattern, pattern_len - 1);
00175     }
00176 
00177     /* there's no wildcards, so just compare all of the characters. */
00178     return !g_ascii_strncasecmp (pattern, source, pattern_len);
00179 }
00180 
00181 static int
00182 xmms_find_match_index (gint source, const gchar *pref, xmms_medialib_t *mlib)
00183 {
00184     gchar *source_name, *colon;
00185     gint i = 0;
00186 
00187     g_mutex_lock (mlib->source_lock);
00188     source_name = g_hash_table_lookup (mlib->sources, GINT_TO_POINTER (source));
00189     g_mutex_unlock (mlib->source_lock);
00190 
00191     do {
00192         gsize len;
00193 
00194         colon = strchr (pref, ':');
00195 
00196         /* get the length of this substring */
00197         len = colon ? colon - pref : strlen (pref);
00198 
00199         /* check whether the substring matches */
00200         if (source_match_pattern (source_name, pref, len)) {
00201             return i;
00202         }
00203 
00204         /* prepare for next iteration */
00205         if (colon) {
00206             pref = colon + 1;
00207         }
00208         i++;
00209 
00210         /* if we just processed the final substring, then we're done */
00211     } while (colon);
00212 
00213     return i;
00214 }
00215 
00216 static void
00217 xmms_sqlite_source_pref_binary (sqlite3_context *context, int args,
00218                                 sqlite3_value **val)
00219 {
00220     gint source;
00221     const gchar *pref;
00222     xmms_medialib_t *mlib;
00223 
00224     mlib = sqlite3_user_data (context);
00225 
00226     if (sqlite3_value_type (val[0]) != SQLITE_INTEGER) {
00227         sqlite3_result_error (context, "First argument to xmms_source_pref "
00228                                        "should be a integer", -1);
00229         return;
00230     }
00231     if (sqlite3_value_type (val[1]) != SQLITE3_TEXT) {
00232         sqlite3_result_error (context, "Second argument to xmms_source_pref "
00233                                        "should be a string", -1);
00234         return;
00235     }
00236 
00237     source = sqlite3_value_int (val[0]);
00238     pref = (const gchar *) sqlite3_value_text (val[1]);
00239 
00240     sqlite3_result_int (context, xmms_find_match_index (source, pref, mlib));
00241 }
00242 
00243 static void
00244 xmms_sqlite_source_pref_unary (sqlite3_context *context, int args,
00245                                sqlite3_value **val)
00246 {
00247     gint source;
00248     xmms_medialib_t *mlib;
00249 
00250     mlib = sqlite3_user_data (context);
00251 
00252     if (sqlite3_value_type (val[0]) != SQLITE_INTEGER) {
00253         sqlite3_result_error (context, "First argument to xmms_source_pref "
00254                                        "should be a integer", -1);
00255         return;
00256     }
00257 
00258     source = sqlite3_value_int (val[0]);
00259 
00260     sqlite3_result_int (context,
00261                         xmms_find_match_index (source, source_pref, mlib));
00262 }
00263 
00264 static int
00265 add_to_source (void *hash, int columns, char **vals, char **cols)
00266 {
00267     int source = strtol (vals[0], NULL, 10);
00268     g_hash_table_insert ((GHashTable*)hash, GINT_TO_POINTER (source), g_strdup (vals[1]));
00269     return 0;
00270 }
00271 
00272 guint32
00273 xmms_medialib_source_to_id (xmms_medialib_session_t *session,
00274                             const gchar *source)
00275 {
00276     gint32 ret = 0;
00277     g_return_val_if_fail (source, 0);
00278 
00279     xmms_sqlite_query_int (session->sql, &ret,
00280                            "SELECT id FROM Sources WHERE source=%Q",
00281                            source);
00282     if (ret == 0) {
00283         xmms_sqlite_exec (session->sql,
00284                           "INSERT INTO Sources (source) VALUES (%Q)", source);
00285         xmms_sqlite_query_int (session->sql, &ret,
00286                                "SELECT id FROM Sources WHERE source=%Q",
00287                                source);
00288         XMMS_DBG ("Added source %s with id %d", source, ret);
00289         g_mutex_lock (session->medialib->source_lock);
00290         g_hash_table_insert (session->medialib->sources, GUINT_TO_POINTER (ret), g_strdup (source));
00291         g_mutex_unlock (session->medialib->source_lock);
00292     }
00293 
00294     return ret;
00295 }
00296 
00297 
00298 static xmms_medialib_session_t *
00299 xmms_medialib_session_new (const char *file, int line)
00300 {
00301     xmms_medialib_session_t *session;
00302 
00303     session = g_new0 (xmms_medialib_session_t, 1);
00304     session->medialib = medialib;
00305     session->file = file;
00306     session->line = line;
00307     session->sql = xmms_sqlite_open ();
00308 
00309     sqlite3_create_function (session->sql, "xmms_source_pref", 2, SQLITE_UTF8,
00310                              session->medialib, xmms_sqlite_source_pref_binary, NULL, NULL);
00311     sqlite3_create_function (session->sql, "xmms_source_pref", 1, SQLITE_UTF8,
00312                              session->medialib, xmms_sqlite_source_pref_unary, NULL, NULL);
00313 
00314     return session;
00315 }
00316 
00317 
00318 
00319 /**
00320  * Initialize the medialib and open the database file.
00321  *
00322  * @param playlist the current playlist pointer
00323  * @returns TRUE if successful and FALSE if there was a problem
00324  */
00325 
00326 xmms_medialib_t *
00327 xmms_medialib_init (xmms_playlist_t *playlist)
00328 {
00329     gchar *path;
00330     xmms_medialib_session_t *session;
00331     gboolean create;
00332 
00333     medialib = xmms_object_new (xmms_medialib_t, xmms_medialib_destroy);
00334     medialib->playlist = playlist;
00335 
00336     xmms_ipc_object_register (XMMS_IPC_OBJECT_MEDIALIB, XMMS_OBJECT (medialib));
00337     xmms_ipc_broadcast_register (XMMS_OBJECT (medialib), XMMS_IPC_SIGNAL_MEDIALIB_ENTRY_ADDED);
00338     xmms_ipc_broadcast_register (XMMS_OBJECT (medialib), XMMS_IPC_SIGNAL_MEDIALIB_ENTRY_UPDATE);
00339 
00340     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00341                          XMMS_IPC_CMD_INFO,
00342                          XMMS_CMD_FUNC (info));
00343     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00344                          XMMS_IPC_CMD_MLIB_ADD_URL,
00345                          XMMS_CMD_FUNC (mlib_add));
00346     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00347                          XMMS_IPC_CMD_REMOVE_ID,
00348                          XMMS_CMD_FUNC (mlib_remove));
00349     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00350                          XMMS_IPC_CMD_PATH_IMPORT,
00351                          XMMS_CMD_FUNC (path_import));
00352     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00353                          XMMS_IPC_CMD_REHASH,
00354                          XMMS_CMD_FUNC (rehash));
00355     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00356                          XMMS_IPC_CMD_GET_ID,
00357                          XMMS_CMD_FUNC (get_id));
00358     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00359                          XMMS_IPC_CMD_PROPERTY_SET_STR,
00360                          XMMS_CMD_FUNC (set_property_str));
00361     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00362                          XMMS_IPC_CMD_PROPERTY_SET_INT,
00363                          XMMS_CMD_FUNC (set_property_int));
00364     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00365                          XMMS_IPC_CMD_PROPERTY_REMOVE,
00366                          XMMS_CMD_FUNC (remove_property));
00367     xmms_object_cmd_add (XMMS_OBJECT (medialib),
00368                          XMMS_IPC_CMD_MOVE_URL,
00369                          XMMS_CMD_FUNC (mlib_move));
00370 
00371     path = XMMS_BUILD_PATH ("medialib.db");
00372 
00373     xmms_config_property_register ("medialib.path", path, NULL, NULL);
00374     xmms_config_property_register ("medialib.analyze_on_startup", "0", NULL, NULL);
00375     xmms_config_property_register ("medialib.allow_remote_fs",
00376                                    "0", NULL, NULL);
00377 
00378     g_free (path);
00379 
00380 
00381     xmms_medialib_debug_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
00382     xmms_medialib_debug_mutex = g_mutex_new ();
00383     global_medialib_session = NULL;
00384 
00385     /* init the database */
00386     xmms_sqlite_create (&create);
00387 
00388     if (!sqlite3_threadsafe ()) {
00389         xmms_log_info ("********************************************************************");
00390         xmms_log_info ("* Using thread hack to compensate for sqlite without threadsafety! *");
00391         xmms_log_info ("* This can be a huge performance penalty - upgrade or recompile    *");
00392         xmms_log_info ("********************************************************************");
00393         /** Create a global session, this is only used when the sqlite version
00394         * doesn't support concurrent sessions */
00395         global_medialib_session = xmms_medialib_session_new ("global", 0);
00396     }
00397 
00398     global_medialib_session_mutex = g_mutex_new ();
00399 
00400     /**
00401      * this dummy just wants to put the default song in the playlist
00402      */
00403     medialib->source_lock = g_mutex_new ();
00404     medialib->sources = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
00405 
00406     session = xmms_medialib_begin_write ();
00407     sqlite3_exec (session->sql, "SELECT id, source FROM Sources",
00408                   add_to_source, medialib->sources, NULL);
00409 
00410     if (create) {
00411         xmms_error_t error;
00412 
00413         xmms_medialib_entry_new (session,
00414                                  "file://" SHAREDDIR
00415                                  "/mind.in.a.box-lament_snipplet.ogg",
00416                                  &error);
00417         /* A default playlist containing that song has been created
00418          * with the mlib.
00419          */
00420     }
00421 
00422     xmms_medialib_end (session);
00423 
00424     return medialib;
00425 }
00426 
00427 /** Session handling */
00428 
00429 xmms_medialib_session_t *
00430 _xmms_medialib_begin (gboolean write, const char *file, int line)
00431 {
00432     xmms_medialib_session_t *session;
00433 
00434     {
00435         gchar *r;
00436         void *me = g_thread_self ();
00437         g_mutex_lock (xmms_medialib_debug_mutex);
00438         r = g_hash_table_lookup (xmms_medialib_debug_hash, me);
00439         if (r) {
00440             xmms_log_fatal ("Medialib session begun recursivly at %s:%d, after %s", file, line, r);
00441         } else {
00442             g_hash_table_insert (xmms_medialib_debug_hash, me,
00443                                  g_strdup_printf ("%s:%d", file, line));
00444         }
00445         g_mutex_unlock (xmms_medialib_debug_mutex);
00446     }
00447     if (global_medialib_session) {
00448         /** This will only happen when OLD_SQLITE_VERSION is set. */
00449         g_mutex_lock (global_medialib_session_mutex);
00450         return global_medialib_session;
00451     }
00452 
00453     session = xmms_medialib_session_new (file, line);
00454     xmms_object_ref (XMMS_OBJECT (medialib));
00455     session->write = write;
00456 
00457     if (write) {
00458         /* Start a exclusive transaction */
00459         if (!xmms_sqlite_exec (session->sql, "BEGIN EXCLUSIVE TRANSACTION")) {
00460             xmms_log_error ("transaction failed!");
00461         }
00462     }
00463 
00464     session->next_id = -1;
00465 
00466     return session;
00467 }
00468 
00469 void
00470 xmms_medialib_end (xmms_medialib_session_t *session)
00471 {
00472     g_return_if_fail (session);
00473 
00474     {
00475         void *me = g_thread_self ();
00476         g_mutex_lock (xmms_medialib_debug_mutex);
00477         g_hash_table_remove (xmms_medialib_debug_hash, me);
00478         g_mutex_unlock (xmms_medialib_debug_mutex);
00479     }
00480 
00481     if (session->write) {
00482         xmms_sqlite_exec (session->sql, "COMMIT");
00483     }
00484 
00485     if (session == global_medialib_session) {
00486         g_mutex_unlock (global_medialib_session_mutex);
00487         return;
00488     }
00489 
00490     xmms_sqlite_close (session->sql);
00491     xmms_object_unref (XMMS_OBJECT (session->medialib));
00492     g_free (session);
00493 }
00494 
00495 static int
00496 xmms_medialib_string_cb (xmmsv_t **row, gpointer udata)
00497 {
00498     gchar **str = udata;
00499     const gchar *buf;
00500 
00501     if (row && row[0] && xmmsv_get_type (row[0]) == XMMSV_TYPE_STRING) {
00502         xmmsv_get_string (row[0], &buf);
00503         *str = g_strdup (buf);
00504     } else
00505         XMMS_DBG ("Expected string but got something else!");
00506 
00507     return 0;
00508 }
00509 
00510 static int
00511 xmms_medialib_value_cb (xmmsv_t **row, gpointer udata)
00512 {
00513     xmmsv_t **ret = udata;
00514 
00515     *ret = xmmsv_ref (row[0]);
00516 
00517     return 0;
00518 }
00519 
00520 /**
00521  * Retrieve a property from an entry
00522  *
00523  * @see xmms_medialib_entry_property_get_str
00524  */
00525 
00526 #define XMMS_MEDIALIB_RETRV_PROPERTY_SQL "SELECT IFNULL (intval, value) FROM Media WHERE key=%Q AND id=%d ORDER BY xmms_source_pref(source, %Q) LIMIT 1"
00527 
00528 xmmsv_t *
00529 xmms_medialib_entry_property_get_value (xmms_medialib_session_t *session,
00530                                         xmms_medialib_entry_t entry,
00531                                         const gchar *property)
00532 {
00533     xmmsv_t *ret = NULL;
00534 
00535     g_return_val_if_fail (property, NULL);
00536     g_return_val_if_fail (session, NULL);
00537 
00538     if (!strcmp (property, XMMS_MEDIALIB_ENTRY_PROPERTY_ID)) {
00539         ret = xmmsv_new_int (entry);
00540     } else {
00541         xmms_sqlite_query_array (session->sql, xmms_medialib_value_cb,
00542                                  &ret, XMMS_MEDIALIB_RETRV_PROPERTY_SQL,
00543                                  property, entry, source_pref);
00544     }
00545 
00546     return ret;
00547 }
00548 
00549 /**
00550  * Retrieve a property from an entry.
00551  *
00552  * @param session The medialib session to be used for the transaction.
00553  * @param entry Entry to query.
00554  * @param property The property to extract. Strings passed should
00555  * be defined in medialib.h
00556  *
00557  * @returns Newly allocated gchar that needs to be freed with g_free
00558  */
00559 
00560 gchar *
00561 xmms_medialib_entry_property_get_str (xmms_medialib_session_t *session,
00562                                       xmms_medialib_entry_t entry,
00563                                       const gchar *property)
00564 {
00565     gchar *ret = NULL;
00566 
00567     g_return_val_if_fail (property, NULL);
00568     g_return_val_if_fail (session, NULL);
00569 
00570     xmms_sqlite_query_array (session->sql, xmms_medialib_string_cb, &ret,
00571                              XMMS_MEDIALIB_RETRV_PROPERTY_SQL,
00572                              property, entry, source_pref);
00573 
00574     return ret;
00575 }
00576 
00577 /**
00578  * Retrieve a property as a int from a entry.
00579  *
00580  * @param session The medialib session to be used for the transaction.
00581  * @param entry Entry to query.
00582  * @param property The property to extract. Strings passed should
00583  * be defined in medialib.h
00584  *
00585  * @returns Property as integer, or -1 if it doesn't exist.
00586  */
00587 gint
00588 xmms_medialib_entry_property_get_int (xmms_medialib_session_t *session,
00589                                       xmms_medialib_entry_t entry,
00590                                       const gchar *property)
00591 {
00592     gint32 ret = -1;
00593 
00594     g_return_val_if_fail (property, -1);
00595     g_return_val_if_fail (session, -1);
00596 
00597     xmms_sqlite_query_int (session->sql, &ret,
00598                            XMMS_MEDIALIB_RETRV_PROPERTY_SQL,
00599                            property, entry, source_pref);
00600 
00601     return ret;
00602 }
00603 
00604 /**
00605  * Set a entry property to a new value, overwriting the old value.
00606  *
00607  * @param session The medialib session to be used for the transaction.
00608  * @param entry Entry to alter.
00609  * @param property The property to extract. Strings passed should
00610  * be defined in medialib.h
00611  * @param value gint with the new value, will be copied in to the medialib
00612  *
00613  * @returns TRUE on success and FALSE on failure.
00614  */
00615 gboolean
00616 xmms_medialib_entry_property_set_int (xmms_medialib_session_t *session,
00617                                       xmms_medialib_entry_t entry,
00618                                       const gchar *property, gint value)
00619 {
00620     return xmms_medialib_entry_property_set_int_source (session, entry,
00621                                                         property, value,
00622                                                         XMMS_MEDIALIB_SOURCE_SERVER_ID);
00623 }
00624 
00625 
00626 gboolean
00627 xmms_medialib_entry_property_set_int_source (xmms_medialib_session_t *session,
00628                                              xmms_medialib_entry_t entry,
00629                                              const gchar *property, gint value,
00630                                              guint32 source)
00631 {
00632     gboolean ret;
00633 
00634     g_return_val_if_fail (property, FALSE);
00635     g_return_val_if_fail (session, FALSE);
00636 
00637     if (!xmms_medialib_check_id_in_session (entry, session)) {
00638         XMMS_DBG ("Trying to add property to id %d "
00639                   "that is not yet in the medialib. Denied.", entry);
00640 
00641         return FALSE;
00642     }
00643 
00644     ret = xmms_sqlite_exec (session->sql,
00645                             "INSERT OR REPLACE INTO Media "
00646                             "(id, value, intval, key, source) VALUES "
00647                             "(%d, '%d', %d, %Q, %d)",
00648                             entry, value, value, property, source);
00649 
00650     return ret;
00651 
00652 }
00653 
00654 /**
00655  * Set a entry property to a new value, overwriting the old value.
00656  *
00657  * @param session The medialib session to be used for the transaction.
00658  * @param entry Entry to alter.
00659  * @param property The property to extract. Strings passed should
00660  * be defined in medialib.h
00661  * @param value gchar with the new value, will be copied in to the medialib
00662  *
00663  * @returns TRUE on success and FALSE on failure.
00664  */
00665 gboolean
00666 xmms_medialib_entry_property_set_str (xmms_medialib_session_t *session,
00667                                       xmms_medialib_entry_t entry,
00668                                       const gchar *property, const gchar *value)
00669 {
00670     return xmms_medialib_entry_property_set_str_source (session, entry,
00671                                                         property, value,
00672                                                         XMMS_MEDIALIB_SOURCE_SERVER_ID);
00673 }
00674 
00675 
00676 gboolean
00677 xmms_medialib_entry_property_set_str_source (xmms_medialib_session_t *session,
00678                                              xmms_medialib_entry_t entry,
00679                                              const gchar *property, const gchar *value,
00680                                              guint32 source)
00681 {
00682     gboolean ret;
00683 
00684     g_return_val_if_fail (property, FALSE);
00685     g_return_val_if_fail (session, FALSE);
00686 
00687     if (value && !g_utf8_validate (value, -1, NULL)) {
00688         XMMS_DBG ("OOOOOPS! Trying to set property %s to a NON UTF-8 string (%s) I will deny that!", property, value);
00689         return FALSE;
00690     }
00691 
00692     if (!xmms_medialib_check_id_in_session (entry, session)) {
00693         XMMS_DBG ("Trying to add property to id %d "
00694                   "that is not yet in the medialib. Denied.", entry);
00695 
00696         return FALSE;
00697     }
00698 
00699     ret = xmms_sqlite_exec (session->sql,
00700                             "INSERT OR REPLACE INTO Media "
00701                             "(id, value, intval, key, source) VALUES "
00702                             "(%d, %Q, NULL, %Q, %d)",
00703                             entry, value, property, source);
00704 
00705     return ret;
00706 
00707 }
00708 
00709 
00710 /**
00711  * Trigger a update signal to the client. This should be called
00712  * when important information in the entry has been changed and
00713  * should be visible to the user.
00714  *
00715  * @param entry Entry to signal a update for.
00716  */
00717 
00718 void
00719 xmms_medialib_entry_send_update (xmms_medialib_entry_t entry)
00720 {
00721     xmms_object_emit_f (XMMS_OBJECT (medialib),
00722                         XMMS_IPC_SIGNAL_MEDIALIB_ENTRY_UPDATE,
00723                         XMMSV_TYPE_INT32, entry);
00724 }
00725 
00726 /**
00727  * Trigger an added siginal to the client. This should be
00728  * called when a new entry has been added to the medialib
00729  *
00730  * @param entry Entry to signal an add for.
00731  */
00732 void
00733 xmms_medialib_entry_send_added (xmms_medialib_entry_t entry)
00734 {
00735     xmms_object_emit_f (XMMS_OBJECT (medialib),
00736                         XMMS_IPC_SIGNAL_MEDIALIB_ENTRY_ADDED,
00737                         XMMSV_TYPE_INT32, entry);
00738 }
00739 
00740 static void
00741 xmms_medialib_client_entry_remove (xmms_medialib_t *medialib, gint32 entry, xmms_error_t *error)
00742 {
00743     xmms_medialib_entry_remove (entry);
00744 }
00745 
00746 /**
00747  * Remove a medialib entry from the database
00748  *
00749  * @param session The medialib session to be used for the transaction.
00750  * @param entry Entry to remove
00751  */
00752 
00753 void
00754 xmms_medialib_entry_remove (xmms_medialib_entry_t entry)
00755 {
00756     xmms_medialib_session_t *session;
00757 
00758     session = xmms_medialib_begin_write ();
00759     xmms_sqlite_exec (session->sql, "DELETE FROM Media WHERE id=%d", entry);
00760     xmms_medialib_end (session);
00761 
00762     /** @todo safe ? */
00763     xmms_playlist_remove_by_entry (medialib->playlist, entry);
00764 }
00765 
00766 static xmms_medialib_entry_t xmms_medialib_entry_new_insert (xmms_medialib_session_t *session, guint32 id, const char *url, xmms_error_t *error);
00767 
00768 static void
00769 process_file (xmms_medialib_session_t *session,
00770               const gchar *playlist,
00771               gint32 pos,
00772               const gchar *path,
00773               xmms_error_t *error)
00774 {
00775     xmms_medialib_entry_t entry;
00776 
00777     entry = xmms_medialib_entry_new_encoded (session, path, error);
00778 
00779     if (entry && playlist != NULL) {
00780         if (pos >= 0) {
00781             xmms_playlist_insert_entry (session->medialib->playlist,
00782                                         playlist, pos, entry, error);
00783         } else {
00784             xmms_playlist_add_entry (session->medialib->playlist,
00785                                      playlist, entry, error);
00786         }
00787     }
00788 }
00789 
00790 static gint
00791 cmp_val (gconstpointer a, gconstpointer b)
00792 {
00793     xmmsv_t *v1, *v2;
00794     const gchar *s1, *s2;
00795     v1 = (xmmsv_t *) a;
00796     v2 = (xmmsv_t *) b;
00797     if (xmmsv_get_type (v1) != XMMSV_TYPE_DICT)
00798         return 0;
00799     if (xmmsv_get_type (v2) != XMMSV_TYPE_DICT)
00800         return 0;
00801 
00802     xmmsv_dict_entry_get_string (v1, "path", &s1);
00803     xmmsv_dict_entry_get_string (v2, "path", &s2);
00804 
00805     return strcmp (s1, s2);
00806 }
00807 
00808 /* code ported over from CLI's "radd" command. */
00809 /* note that the returned file list is reverse-sorted! */
00810 static gboolean
00811 process_dir (const gchar *directory,
00812              GList **ret,
00813              xmms_error_t *error)
00814 {
00815     GList *list;
00816 
00817     list = xmms_xform_browse (directory, error);
00818     if (!list) {
00819         return FALSE;
00820     }
00821 
00822     list = g_list_sort (list, cmp_val);
00823 
00824     while (list) {
00825         xmmsv_t *val = list->data;
00826         const gchar *str;
00827         gint isdir;
00828 
00829         xmmsv_dict_entry_get_string (val, "path", &str);
00830         xmmsv_dict_entry_get_int (val, "isdir", &isdir);
00831 
00832         if (isdir == 1) {
00833             process_dir (str, ret, error);
00834         } else {
00835             *ret = g_list_prepend (*ret, g_strdup (str));
00836         }
00837 
00838         xmmsv_unref (val);
00839         list = g_list_delete_link (list, list);
00840     }
00841 
00842     return TRUE;
00843 }
00844 
00845 void
00846 xmms_medialib_entry_cleanup (xmms_medialib_session_t *session,
00847                              xmms_medialib_entry_t entry)
00848 {
00849     xmms_sqlite_exec (session->sql,
00850                       "DELETE FROM Media WHERE id=%d AND source=%d "
00851                       "AND key NOT IN (%Q, %Q, %Q, %Q, %Q)",
00852                       entry,
00853                       XMMS_MEDIALIB_SOURCE_SERVER_ID,
00854                       XMMS_MEDIALIB_ENTRY_PROPERTY_URL,
00855                       XMMS_MEDIALIB_ENTRY_PROPERTY_ADDED,
00856                       XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS,
00857                       XMMS_MEDIALIB_ENTRY_PROPERTY_LMOD,
00858                       XMMS_MEDIALIB_ENTRY_PROPERTY_LASTSTARTED);
00859 
00860     xmms_sqlite_exec (session->sql,
00861                       "DELETE FROM Media WHERE id=%d AND source IN "
00862                       "(SELECT id FROM Sources WHERE source LIKE 'plugin/%%' "
00863                        "AND source != 'plugin/playlist')",
00864                       entry);
00865 
00866 }
00867 
00868 static void
00869 xmms_medialib_client_rehash (xmms_medialib_t *medialib, gint32 id, xmms_error_t *error)
00870 {
00871     xmms_mediainfo_reader_t *mr;
00872     xmms_medialib_session_t *session;
00873 
00874     session = xmms_medialib_begin_write ();
00875 
00876     if (id) {
00877         xmms_sqlite_exec (session->sql,
00878                           "UPDATE Media SET value = '%d', intval = %d "
00879                           "WHERE key='%s' AND id=%d",
00880                           XMMS_MEDIALIB_ENTRY_STATUS_REHASH,
00881                           XMMS_MEDIALIB_ENTRY_STATUS_REHASH,
00882                           XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS, id);
00883     } else {
00884         xmms_sqlite_exec (session->sql,
00885                           "UPDATE Media SET value = '%d', intval = %d "
00886                           "WHERE key='%s'",
00887                           XMMS_MEDIALIB_ENTRY_STATUS_REHASH,
00888                           XMMS_MEDIALIB_ENTRY_STATUS_REHASH,
00889                           XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS);
00890     }
00891 
00892     xmms_medialib_end (session);
00893 
00894     mr = xmms_playlist_mediainfo_reader_get (medialib->playlist);
00895     xmms_mediainfo_reader_wakeup (mr);
00896 
00897 }
00898 
00899 /* Recursively add entries under the given path to the medialib,
00900  * optionally adding them to a playlist if the playlist argument is
00901  * not NULL.
00902  */
00903 void
00904 xmms_medialib_add_recursive (xmms_medialib_t *medialib, const gchar *playlist,
00905                              const gchar *path, xmms_error_t *error)
00906 {
00907     /* Just called insert with negative pos to append */
00908     xmms_medialib_insert_recursive (medialib, playlist, -1, path, error);
00909 }
00910 
00911 /* Recursively adding entries under the given path to the medialib,
00912  * optionally insert them into a playlist at a given position if the
00913  * playlist argument is not NULL. If the position is negative, entries
00914  * are appended to the playlist.
00915  */
00916 void
00917 xmms_medialib_insert_recursive (xmms_medialib_t *medialib, const gchar *playlist,
00918                                 gint32 pos, const gchar *path,
00919                                 xmms_error_t *error)
00920 {
00921     xmms_medialib_session_t *session;
00922     GList *first, *list = NULL, *n;
00923 
00924     g_return_if_fail (medialib);
00925     g_return_if_fail (path);
00926 
00927     /* Allocate our first list node manually here. The following call
00928      * to process_dir() will prepend all other nodes, so afterwards
00929      * "first" will point to the last node of the list... see below.
00930      */
00931     first = list = g_list_alloc ();
00932 
00933     process_dir (path, &list, error);
00934 
00935     XMMS_DBG ("taking the transaction!");
00936     session = xmms_medialib_begin_write ();
00937 
00938     /* We now want to iterate the list in the order in which the nodes
00939      * were added, ie in reverse order. Thankfully we stored a pointer
00940      * to the last node in the list before, which saves us an expensive
00941      * g_list_last() call now. Increase pos each time to retain order.
00942      */
00943     for (n = first->prev; n; n = g_list_previous (n)) {
00944         process_file (session, playlist, pos, n->data, error);
00945         if (pos >= 0)
00946             pos++;
00947         g_free (n->data);
00948     }
00949 
00950     g_list_free (list);
00951 
00952     XMMS_DBG ("and we are done!");
00953     xmms_medialib_end (session);
00954 }
00955 
00956 static void
00957 xmms_medialib_client_path_import (xmms_medialib_t *medialib, const gchar *path,
00958                                   xmms_error_t *error)
00959 {
00960     xmms_medialib_add_recursive (medialib, NULL, path, error);
00961 }
00962 
00963 static xmms_medialib_entry_t
00964 xmms_medialib_entry_new_insert (xmms_medialib_session_t *session,
00965                                 guint32 id,
00966                                 const char *url,
00967                                 xmms_error_t *error)
00968 {
00969     xmms_mediainfo_reader_t *mr;
00970     guint source;
00971 
00972     source = XMMS_MEDIALIB_SOURCE_SERVER_ID;
00973 
00974     if (!xmms_sqlite_exec (session->sql,
00975                            "INSERT INTO Media (id, key, value, source) VALUES "
00976                                              "(%d, '%s', %Q, %d)",
00977                            id, XMMS_MEDIALIB_ENTRY_PROPERTY_URL, url,
00978                            source)) {
00979         xmms_error_set (error, XMMS_ERROR_GENERIC,
00980                         "Sql error/corruption inserting url");
00981         return 0;
00982     }
00983 
00984     xmms_medialib_entry_status_set (session, id, XMMS_MEDIALIB_ENTRY_STATUS_NEW);
00985     mr = xmms_playlist_mediainfo_reader_get (medialib->playlist);
00986     xmms_mediainfo_reader_wakeup (mr);
00987 
00988     return 1;
00989 
00990 }
00991 
00992 /**
00993  * @internal
00994  */
00995 xmms_medialib_entry_t
00996 xmms_medialib_entry_new_encoded (xmms_medialib_session_t *session,
00997                                  const char *url, xmms_error_t *error)
00998 {
00999     gint id = 0;
01000     guint ret = 0;
01001     guint source;
01002 
01003     g_return_val_if_fail (url, 0);
01004     g_return_val_if_fail (session, 0);
01005     g_return_val_if_fail (session->write, 0);
01006 
01007     source = XMMS_MEDIALIB_SOURCE_SERVER_ID;
01008 
01009     xmms_sqlite_query_int (session->sql, &id,
01010                            "SELECT id AS value FROM Media "
01011                            "WHERE key='%s' AND value=%Q AND source=%d",
01012                            XMMS_MEDIALIB_ENTRY_PROPERTY_URL, url,
01013                            source);
01014 
01015     if (id) {
01016         ret = id;
01017     } else {
01018         if (session->next_id <= 0 &&
01019             !xmms_sqlite_query_int (session->sql, &session->next_id,
01020                                     "SELECT IFNULL(MAX (id),0)+1 FROM Media")) {
01021             xmms_error_set (error, XMMS_ERROR_GENERIC,
01022                             "SQL error/corruption selecting max(id)");
01023             return 0;
01024         }
01025 
01026         ret = session->next_id++;
01027 
01028         if (!xmms_medialib_entry_new_insert (session, ret, url, error))
01029             return 0;
01030     }
01031 
01032     xmms_medialib_entry_send_added (ret);
01033     return ret;
01034 
01035 }
01036 
01037 /**
01038  * Welcome to a function that should be called something else.
01039  * Returns a entry for a URL, if the URL is already in the medialib
01040  * the current entry will be returned otherwise a new one will be
01041  * created and returned.
01042  *
01043  * @todo rename to something better?
01044  *
01045  * @param session The medialib session to be used for the transaction.
01046  * @param url URL to add/retrieve from the medialib
01047  * @param error If an error occurs, it will be stored in there.
01048  *
01049  * @returns Entry mapped to the URL
01050  */
01051 xmms_medialib_entry_t
01052 xmms_medialib_entry_new (xmms_medialib_session_t *session, const char *url, xmms_error_t *error)
01053 {
01054     gchar *enc_url;
01055     xmms_medialib_entry_t res;
01056 
01057     enc_url = xmms_medialib_url_encode (url);
01058     if (!enc_url)
01059         return 0;
01060 
01061     res = xmms_medialib_entry_new_encoded (session, enc_url, error);
01062 
01063     g_free (enc_url);
01064 
01065     return res;
01066 }
01067 
01068 gint32
01069 xmms_medialib_client_entry_get_id (xmms_medialib_t *medialib, const gchar *url,
01070                                    xmms_error_t *error)
01071 {
01072     gint32 id = 0;
01073     xmms_medialib_session_t *session = xmms_medialib_begin ();
01074 
01075     xmms_sqlite_query_int (session->sql, &id,
01076                            "SELECT id AS value FROM Media WHERE key='%s' AND value=%Q AND source=%d",
01077                            XMMS_MEDIALIB_ENTRY_PROPERTY_URL, url,
01078                            XMMS_MEDIALIB_SOURCE_SERVER_ID);
01079     xmms_medialib_end (session);
01080 
01081     return id;
01082 }
01083 
01084 static void
01085 xmms_medialib_tree_add_tuple (GTree *tree, const char *key,
01086                               const char *source, xmmsv_t *value)
01087 {
01088     xmmsv_t *keytreeval;
01089 
01090     /* Find (or insert) subtree matching the prop key */
01091     keytreeval = (xmmsv_t *) g_tree_lookup (tree, key);
01092     if (!keytreeval) {
01093         keytreeval = xmmsv_new_dict ();
01094         g_tree_insert (tree, g_strdup (key), keytreeval);
01095     }
01096 
01097     /* Replace (or insert) value matching the prop source */
01098     xmmsv_dict_set (keytreeval, source, value);
01099 }
01100 
01101 static gboolean
01102 xmms_medialib_list_cb (xmmsv_t **row, gpointer udata)
01103 {
01104     GList **list = (GList**)udata;
01105 
01106     /* Source */
01107     *list = g_list_prepend (*list, xmmsv_ref (row[0]));
01108 
01109     /* Key */
01110     *list = g_list_prepend (*list, xmmsv_ref (row[1]));
01111 
01112     /* Value */
01113     *list = g_list_prepend (*list, xmmsv_ref (row[2]));
01114 
01115     return TRUE;
01116 }
01117 
01118 static gboolean
01119 xmms_medialib_tree_cb (xmmsv_t **row, gpointer udata)
01120 {
01121     const char *key, *source;
01122     xmmsv_t *value;
01123     GTree **tree = (GTree**)udata;
01124 
01125     xmmsv_get_string (row[0], &source);
01126     xmmsv_get_string (row[1], &key);
01127     value = row[2];
01128 
01129     xmms_medialib_tree_add_tuple (*tree, key, source, value);
01130 
01131     return TRUE;
01132 }
01133 
01134 /**
01135  * Convert a entry and all properties to a hashtable that
01136  * could be feed to the client or somewhere else in the daemon.
01137  *
01138  * @param session The medialib session to be used for the transaction.
01139  * @param entry Entry to convert.
01140  *
01141  * @returns Newly allocated hashtable with newly allocated strings
01142  * make sure to free them all.
01143  */
01144 
01145 static GList *
01146 xmms_medialib_entry_to_list (xmms_medialib_session_t *session, xmms_medialib_entry_t entry)
01147 {
01148     GList *ret = NULL;
01149     gboolean s;
01150 
01151     g_return_val_if_fail (session, NULL);
01152     g_return_val_if_fail (entry, NULL);
01153 
01154     s = xmms_sqlite_query_array (session->sql, xmms_medialib_list_cb, &ret,
01155                                  "SELECT s.source, m.key, "
01156                                         "IFNULL (m.intval, m.value) "
01157                                  "FROM Media m LEFT JOIN "
01158                                  "Sources s ON m.source = s.id "
01159                                  "WHERE m.id=%d",
01160                                  entry);
01161     if (!s || !ret) {
01162         return NULL;
01163     }
01164 
01165     /* Source */
01166     ret = g_list_prepend (ret, xmmsv_new_string ("server"));
01167 
01168     /* Key */
01169     ret = g_list_prepend (ret, xmmsv_new_string ("id"));
01170 
01171     /* Value */
01172     ret = g_list_prepend (ret, xmmsv_new_int (entry));
01173 
01174     return g_list_reverse (ret);
01175 }
01176 
01177 /**
01178  * Convert a entry and all properties to a key-source-value tree that
01179  * could be feed to the client or somewhere else in the daemon.
01180  *
01181  * @param session The medialib session to be used for the transaction.
01182  * @param entry Entry to convert.
01183  *
01184  * @returns Newly allocated tree with newly allocated strings
01185  * make sure to free them all.
01186  */
01187 
01188 static GTree *
01189 xmms_medialib_entry_to_tree (xmms_medialib_session_t *session, xmms_medialib_entry_t entry)
01190 {
01191     GTree *ret;
01192     xmmsv_t *v_entry;
01193     gboolean s;
01194 
01195     g_return_val_if_fail (session, NULL);
01196     g_return_val_if_fail (entry, NULL);
01197 
01198     if (!xmms_medialib_check_id_in_session (entry, session)) {
01199         return NULL;
01200     }
01201 
01202     ret = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
01203                            (GDestroyNotify) xmmsv_unref);
01204 
01205     s = xmms_sqlite_query_array (session->sql, xmms_medialib_tree_cb,
01206                                  &ret,
01207                                  "SELECT s.source, m.key, "
01208                                          "IFNULL (m.intval, m.value) "
01209                                  "FROM Media m LEFT JOIN "
01210                                  "Sources s ON m.source = s.id "
01211                                  "WHERE m.id=%d",
01212                                  entry);
01213     if (!s || !ret) {
01214         return NULL;
01215     }
01216 
01217     v_entry = xmmsv_new_int (entry);
01218     xmms_medialib_tree_add_tuple (ret, "id", "server", v_entry);
01219     xmmsv_unref (v_entry);
01220 
01221     return ret;
01222 }
01223 
01224 /* Legacy, still used by collections. */
01225 GList *
01226 xmms_medialib_info_list (xmms_medialib_t *medialib, guint32 id, xmms_error_t *err)
01227 {
01228     xmms_medialib_session_t *session;
01229     GList *ret = NULL;
01230 
01231     if (!id) {
01232         xmms_error_set (err, XMMS_ERROR_NOENT, "No such entry, 0");
01233     } else {
01234         session = xmms_medialib_begin ();
01235         ret = xmms_medialib_entry_to_list (session, id);
01236         xmms_medialib_end (session);
01237 
01238         if (!ret) {
01239             xmms_error_set (err, XMMS_ERROR_NOENT,
01240                             "Could not retrieve info for that entry!");
01241         }
01242     }
01243 
01244     return ret;
01245 }
01246 
01247 static GTree *
01248 xmms_medialib_client_info (xmms_medialib_t *medialib, gint32 id, xmms_error_t *err)
01249 {
01250     xmms_medialib_session_t *session;
01251     GTree *ret = NULL;
01252 
01253     if (!id) {
01254         xmms_error_set (err, XMMS_ERROR_NOENT, "No such entry, 0");
01255     } else {
01256         session = xmms_medialib_begin ();
01257         ret = xmms_medialib_entry_to_tree (session, id);
01258         xmms_medialib_end (session);
01259 
01260         if (!ret) {
01261             xmms_error_set (err, XMMS_ERROR_NOENT,
01262                             "Could not retrieve info for that entry!");
01263         }
01264     }
01265 
01266     return ret;
01267 }
01268 
01269 static gboolean
01270 select_callback (xmmsv_t *row, gpointer udata)
01271 {
01272     GList **l = (GList **) udata;
01273     *l = g_list_prepend (*l, row);
01274     return TRUE;
01275 }
01276 
01277 /**
01278  * Add a entry to the medialib. Calls #xmms_medialib_entry_new and then
01279  * wakes up the mediainfo_reader in order to resolve the metadata.
01280  *
01281  * @param medialib Medialib pointer
01282  * @param url URL to add
01283  * @param error In case of error this will be filled.
01284  */
01285 
01286 static void
01287 xmms_medialib_client_add_entry (xmms_medialib_t *medialib, const gchar *url,
01288                                 xmms_error_t *error)
01289 {
01290     xmms_medialib_entry_t entry;
01291     xmms_medialib_session_t *session;
01292 
01293     g_return_if_fail (medialib);
01294     g_return_if_fail (url);
01295 
01296     session = xmms_medialib_begin_write ();
01297 
01298     entry = xmms_medialib_entry_new_encoded (session, url, error);
01299 
01300     xmms_medialib_end (session);
01301 }
01302 
01303 /**
01304  * Changes the URL of an entry in the medialib.
01305  *
01306  * @param medialib Medialib pointer
01307  * @param entry entry to modify
01308  * @param url URL to change to
01309  * @param error In case of error this will be filled.
01310  */
01311 static void
01312 xmms_medialib_client_move_entry (xmms_medialib_t *medialib, gint32 entry,
01313                                  const gchar *url, xmms_error_t *error)
01314 {
01315     const gchar *key = XMMS_MEDIALIB_ENTRY_PROPERTY_URL;
01316     guint32 sourceid = XMMS_MEDIALIB_SOURCE_SERVER_ID;
01317     gchar *enc_url;
01318 
01319     xmms_medialib_session_t *session;
01320 
01321     enc_url = xmms_medialib_url_encode (url);
01322 
01323     session = xmms_medialib_begin_write ();
01324     xmms_medialib_entry_property_set_str_source (session, entry, key, enc_url,
01325                                                  sourceid);
01326     xmms_medialib_end (session);
01327 
01328     g_free (enc_url);
01329 
01330     xmms_medialib_entry_send_update (entry);
01331 }
01332 
01333 static void
01334 xmms_medialib_client_property_set_str (xmms_medialib_t *medialib, gint32 entry,
01335                                        const gchar *source, const gchar *key,
01336                                        const gchar *value, xmms_error_t *error)
01337 {
01338     guint32 sourceid;
01339     xmms_medialib_session_t *session;
01340 
01341     if (g_ascii_strcasecmp (source, "server") == 0) {
01342         xmms_error_set (error, XMMS_ERROR_GENERIC,
01343                         "Can't write to source server!");
01344         return;
01345     }
01346 
01347     session = xmms_medialib_begin_write ();
01348     sourceid = xmms_medialib_source_to_id (session, source);
01349 
01350     xmms_medialib_entry_property_set_str_source (session, entry, key, value,
01351                                                  sourceid);
01352     xmms_medialib_end (session);
01353 
01354     xmms_medialib_entry_send_update (entry);
01355 }
01356 
01357 static void
01358 xmms_medialib_client_property_set_int (xmms_medialib_t *medialib, gint32 entry,
01359                                        const gchar *source, const gchar *key,
01360                                        gint32 value, xmms_error_t *error)
01361 {
01362     guint32 sourceid;
01363     xmms_medialib_session_t *session;
01364 
01365     if (g_ascii_strcasecmp (source, "server") == 0) {
01366         xmms_error_set (error, XMMS_ERROR_GENERIC,
01367                         "Can't write to source server!");
01368         return;
01369     }
01370 
01371     session = xmms_medialib_begin_write ();
01372     sourceid = xmms_medialib_source_to_id (session, source);
01373     xmms_medialib_entry_property_set_int_source (session, entry, key, value,
01374                                                  sourceid);
01375     xmms_medialib_end (session);
01376 
01377     xmms_medialib_entry_send_update (entry);
01378 }
01379 
01380 static void
01381 xmms_medialib_property_remove (xmms_medialib_t *medialib, gint32 entry,
01382                                const gchar *source, const gchar *key,
01383                                xmms_error_t *error)
01384 {
01385     guint32 sourceid;
01386 
01387     xmms_medialib_session_t *session = xmms_medialib_begin_write ();
01388     sourceid = xmms_medialib_source_to_id (session, source);
01389     xmms_sqlite_exec (session->sql,
01390                       "DELETE FROM Media WHERE source=%d AND key='%s' AND "
01391                                               "id=%d",
01392                       sourceid, key, entry);
01393     xmms_medialib_end (session);
01394 
01395     xmms_medialib_entry_send_update (entry);
01396 }
01397 
01398 static void
01399 xmms_medialib_client_property_remove (xmms_medialib_t *medialib, gint32 entry,
01400                                       const gchar *source, const gchar *key,
01401                                       xmms_error_t *error)
01402 {
01403     if (g_ascii_strcasecmp (source, "server") == 0) {
01404         xmms_error_set (error, XMMS_ERROR_GENERIC,
01405                         "Can't remove properties set by the server!");
01406         return;
01407     }
01408 
01409     return xmms_medialib_property_remove (medialib, entry, source, key, error);
01410 }
01411 
01412 /**
01413  * Get a list of GHashTables 's that matches the query.
01414  *
01415  * @param session The medialib session to be used for the transaction.
01416  * @param query SQL query that should be executed.
01417  * @param error In case of error this will be filled.
01418  * @returns GList containing GHashTables. Caller are responsible to
01419  * free all memory.
01420  */
01421 GList *
01422 xmms_medialib_select (xmms_medialib_session_t *session,
01423                       const gchar *query, xmms_error_t *error)
01424 {
01425     GList *res = NULL;
01426     gint ret;
01427 
01428     g_return_val_if_fail (query, 0);
01429     g_return_val_if_fail (session, 0);
01430 
01431     ret = xmms_sqlite_query_table (session->sql, select_callback,
01432                                    (void *)&res, error, "%s", query);
01433 
01434     return ret ? g_list_reverse (res) : NULL;
01435 }
01436 
01437 /** @} */
01438 
01439 /**
01440  * @internal
01441  */
01442 
01443 gboolean
01444 xmms_medialib_check_id (xmms_medialib_entry_t entry)
01445 {
01446     xmms_medialib_session_t *session;
01447     gboolean ret;
01448 
01449     session = xmms_medialib_begin ();
01450     ret = xmms_medialib_check_id_in_session (entry, session);
01451     xmms_medialib_end (session);
01452 
01453     return ret;
01454 }
01455 
01456 /**
01457  * @internal
01458  */
01459 
01460 static gboolean
01461 xmms_medialib_check_id_in_session (xmms_medialib_entry_t entry,
01462                                    xmms_medialib_session_t *session)
01463 {
01464     gint c = 0;
01465 
01466     if (!xmms_sqlite_query_int (session->sql, &c,
01467                                 "SELECT COUNT(id) FROM Media WHERE id=%d",
01468                                 entry)) {
01469         return FALSE;
01470     }
01471 
01472     return c > 0;
01473 }
01474 
01475 
01476 /**
01477  * @internal
01478  * Get the next unresolved entry. Used by the mediainfo reader..
01479  */
01480 
01481 xmms_medialib_entry_t
01482 xmms_medialib_entry_not_resolved_get (xmms_medialib_session_t *session)
01483 {
01484     gint32 ret = 0;
01485 
01486     g_return_val_if_fail (session, 0);
01487 
01488     xmms_sqlite_query_int (session->sql, &ret,
01489                            "SELECT id FROM Media WHERE key='%s' "
01490                            "AND source=%d AND intval IN (%d, %d) LIMIT 1",
01491                            XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS,
01492                            XMMS_MEDIALIB_SOURCE_SERVER_ID,
01493                            XMMS_MEDIALIB_ENTRY_STATUS_NEW,
01494                            XMMS_MEDIALIB_ENTRY_STATUS_REHASH);
01495 
01496     return ret;
01497 }
01498 
01499 guint
01500 xmms_medialib_num_not_resolved (xmms_medialib_session_t *session)
01501 {
01502     gint ret;
01503     g_return_val_if_fail (session, 0);
01504 
01505     xmms_sqlite_query_int (session->sql, &ret,
01506                            "SELECT COUNT(id) AS value FROM Media WHERE "
01507                            "key='%s' AND intval IN (%d, %d) AND source=%d",
01508                            XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS,
01509                            XMMS_MEDIALIB_ENTRY_STATUS_NEW,
01510                            XMMS_MEDIALIB_ENTRY_STATUS_REHASH,
01511                            XMMS_MEDIALIB_SOURCE_SERVER_ID);
01512 
01513     return ret;
01514 }
01515 
01516 gboolean
01517 xmms_medialib_decode_url (char *url)
01518 {
01519     int i = 0, j = 0;
01520 
01521     g_return_val_if_fail (url, TRUE);
01522 
01523     while (url[i]) {
01524         unsigned char chr = url[i++];
01525 
01526         if (chr == '+') {
01527             url[j++] = ' ';
01528         } else if (chr == '%') {
01529             char ts[3];
01530             char *t;
01531 
01532             ts[0] = url[i++];
01533             if (!ts[0])
01534                 return FALSE;
01535             ts[1] = url[i++];
01536             if (!ts[1])
01537                 return FALSE;
01538             ts[2] = '\0';
01539 
01540             url[j++] = strtoul (ts, &t, 16);
01541             if (t != &ts[2])
01542                 return FALSE;
01543         } else {
01544             url[j++] = chr;
01545         }
01546     }
01547 
01548     url[j] = '\0';
01549 
01550     return TRUE;
01551 }
01552 
01553 
01554 #define GOODCHAR(a) ((((a) >= 'a') && ((a) <= 'z')) || \
01555                      (((a) >= 'A') && ((a) <= 'Z')) || \
01556                      (((a) >= '0') && ((a) <= '9')) || \
01557                      ((a) == ':') || \
01558                      ((a) == '/') || \
01559                      ((a) == '-') || \
01560                      ((a) == '.') || \
01561                      ((a) == '_'))
01562 
01563 /* we don't share code here with medialib because we want to use g_malloc :( */
01564 gchar *
01565 xmms_medialib_url_encode (const gchar *path)
01566 {
01567     static gchar hex[16] = "0123456789abcdef";
01568     gchar *res;
01569     int i = 0, j = 0;
01570 
01571     res = g_malloc (strlen (path) * 3 + 1);
01572     if (!res)
01573         return NULL;
01574 
01575     while (path[i]) {
01576         guchar chr = path[i++];
01577         if (GOODCHAR (chr)) {
01578             res[j++] = chr;
01579         } else if (chr == ' ') {
01580             res[j++] = '+';
01581         } else {
01582             res[j++] = '%';
01583             res[j++] = hex[((chr & 0xf0) >> 4)];
01584             res[j++] = hex[(chr & 0x0f)];
01585         }
01586     }
01587 
01588     res[j] = '\0';
01589 
01590     return res;
01591 }