XMMS2
|
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 }