00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
00037
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
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071 struct xmms_medialib_St {
00072 xmms_object_t object;
00073
00074 xmms_playlist_t *playlist;
00075
00076 GMutex *source_lock;
00077 GHashTable *sources;
00078 };
00079
00080
00081
00082
00083 struct xmms_medialib_session_St {
00084 xmms_medialib_t *medialib;
00085
00086
00087 sqlite3 *sql;
00088
00089
00090 const gchar *file;
00091
00092 gint line;
00093
00094
00095 gboolean write;
00096
00097 gint next_id;
00098 };
00099
00100
00101
00102
00103
00104
00105
00106
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
00115
00116
00117
00118 static xmms_medialib_session_t *global_medialib_session;
00119
00120
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
00149
00150
00151 if (pattern_len > 0 && pattern[pattern_len - 1] == '*') {
00152
00153
00154
00155 if (pattern_len == 1) {
00156 return TRUE;
00157 }
00158
00159
00160
00161
00162 return !g_ascii_strncasecmp (source, pattern, pattern_len - 1);
00163 }
00164
00165
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
00185 len = colon ? colon - pref : strlen (pref);
00186
00187
00188 if (source_match_pattern (source_name, pref, len)) {
00189 return i;
00190 }
00191
00192
00193 if (colon) {
00194 pref = colon + 1;
00195 }
00196 i++;
00197
00198
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
00309
00310
00311
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
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
00349
00350 global_medialib_session = xmms_medialib_session_new ("global", 0);
00351 }
00352
00353 global_medialib_session_mutex = g_mutex_new ();
00354
00355
00356
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
00373
00374
00375 }
00376
00377 xmms_medialib_end (session);
00378
00379 return medialib;
00380 }
00381
00382
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
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
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
00477
00478
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
00506
00507
00508
00509
00510
00511
00512
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
00534
00535
00536
00537
00538
00539
00540
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
00561
00562
00563
00564
00565
00566
00567
00568
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
00611
00612
00613
00614
00615
00616
00617
00618
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
00667
00668
00669
00670
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
00683
00684
00685
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
00704
00705
00706
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
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
00765
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
00856
00857
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
00864 xmms_medialib_insert_recursive (medialib, playlist, -1, path, error);
00865 }
00866
00867
00868
00869
00870
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
00884
00885
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
00895
00896
00897
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
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
00995
00996
00997
00998
00999
01000
01001
01002
01003
01004
01005
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
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
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
01063 *list = g_list_prepend (*list, xmmsv_ref (row[0]));
01064
01065
01066 *list = g_list_prepend (*list, xmmsv_ref (row[1]));
01067
01068
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
01092
01093
01094
01095
01096
01097
01098
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
01122 ret = g_list_prepend (ret, xmmsv_new_string ("server"));
01123
01124
01125 ret = g_list_prepend (ret, xmmsv_new_string ("id"));
01126
01127
01128 ret = g_list_prepend (ret, xmmsv_new_int (entry));
01129
01130 return g_list_reverse (ret);
01131 }
01132
01133
01134
01135
01136
01137
01138
01139
01140
01141
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
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
01236
01237
01238
01239
01240
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
01262
01263
01264
01265
01266
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
01372
01373
01374
01375
01376
01377
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
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
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
01436
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
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 }