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