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