• Main Page
  • Related Pages
  • Modules
  • Data Structures
  • Files
  • File List
  • Globals

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

Generated on Wed Feb 9 2011 for XMMS2 by  doxygen 1.7.1