XMMS2
medialib.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2009 XMMS2 Team
3  *
4  * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  */
16 
17 #include "xmms_configuration.h"
18 #include "xmmspriv/xmms_medialib.h"
19 #include "xmmspriv/xmms_xform.h"
20 #include "xmmspriv/xmms_utils.h"
21 #include "xmms/xmms_error.h"
22 #include "xmms/xmms_config.h"
23 #include "xmms/xmms_object.h"
24 #include "xmms/xmms_ipc.h"
25 #include "xmms/xmms_log.h"
26 
27 #include <string.h>
28 #include <stdlib.h>
29 
30 #include <glib.h>
31 #include <time.h>
32 
33 #include <sqlite3.h>
34 
35 /**
36  * @file
37  * Medialib is a metainfo cache that is searchable.
38  */
39 
40 
41 static void xmms_medialib_client_entry_remove (xmms_medialib_t *medialib, gint32 entry, xmms_error_t *error);
42 gchar *xmms_medialib_url_encode (const gchar *path);
43 static gboolean xmms_medialib_check_id_in_session (xmms_medialib_entry_t entry, xmms_medialib_session_t *session);
44 
45 static void xmms_medialib_client_add_entry (xmms_medialib_t *, const gchar *, xmms_error_t *);
46 static void xmms_medialib_client_move_entry (xmms_medialib_t *, gint32 entry, const gchar *, xmms_error_t *);
47 static void xmms_medialib_client_path_import (xmms_medialib_t *medialib, const gchar *path, xmms_error_t *error);
48 static void xmms_medialib_client_rehash (xmms_medialib_t *medialib, gint32 id, xmms_error_t *error);
49 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);
50 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);
51 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);
52 static void xmms_medialib_client_property_remove (xmms_medialib_t *medialib, gint32 entry, const gchar *source, const gchar *key, xmms_error_t *error);
53 static GTree *xmms_medialib_client_info (xmms_medialib_t *medialib, gint32 id, xmms_error_t *err);
54 static gint32 xmms_medialib_client_entry_get_id (xmms_medialib_t *medialib, const gchar *url, xmms_error_t *error);
55 
56 
57 XMMS_CMD_DEFINE (info, xmms_medialib_client_info, xmms_medialib_t *, DICT, INT32, NONE);
58 XMMS_CMD_DEFINE (mlib_add, xmms_medialib_client_add_entry, xmms_medialib_t *, NONE, STRING, NONE);
59 XMMS_CMD_DEFINE (mlib_remove, xmms_medialib_client_entry_remove, xmms_medialib_t *, NONE, INT32, NONE);
60 XMMS_CMD_DEFINE (mlib_move, xmms_medialib_client_move_entry, xmms_medialib_t *, NONE, INT32, STRING);
61 XMMS_CMD_DEFINE (path_import, xmms_medialib_client_path_import, xmms_medialib_t *, NONE, STRING, NONE);
62 XMMS_CMD_DEFINE (rehash, xmms_medialib_client_rehash, xmms_medialib_t *, NONE, INT32, NONE);
63 XMMS_CMD_DEFINE (get_id, xmms_medialib_client_entry_get_id, xmms_medialib_t *, INT32, STRING, NONE);
64 
65 XMMS_CMD_DEFINE4 (set_property_str, xmms_medialib_client_property_set_str, xmms_medialib_t *, NONE, INT32, STRING, STRING, STRING);
66 XMMS_CMD_DEFINE4 (set_property_int, xmms_medialib_client_property_set_int, xmms_medialib_t *, NONE, INT32, STRING, STRING, INT32);
67 
68 XMMS_CMD_DEFINE3 (remove_property, xmms_medialib_client_property_remove, xmms_medialib_t *, NONE, INT32, STRING, STRING);
69 
70 /**
71  *
72  * @defgroup Medialib Medialib
73  * @ingroup XMMSServer
74  * @brief Medialib caches metadata
75  *
76  * Controls metadata storage.
77  *
78  * @{
79  */
80 
81 /**
82  * Medialib structure
83  */
84 struct xmms_medialib_St {
85  xmms_object_t object;
86  /** The current playlist */
87  xmms_playlist_t *playlist;
88 
89  GMutex *source_lock;
90  GHashTable *sources;
91 };
92 
93 /**
94  * This is handed out by xmms_medialib_begin()
95  */
96 struct xmms_medialib_session_St {
97  xmms_medialib_t *medialib;
98 
99  /** The SQLite handler */
100  sqlite3 *sql;
101 
102  /** debug file */
103  const gchar *file;
104  /** debug line number */
105  gint line;
106 
107  /* Write or read lock, true if write */
108  gboolean write;
109 
110  gint next_id;
111 };
112 
113 
114 /**
115  * Ok, so the functions are written with reentrency in mind, but
116  * we choose to have a global medialib object here. It will be
117  * much easier, and I don't see the real use of multiple medialibs
118  * right now. This could be changed by removing this global one
119  * and altering the function callers...
120  */
121 static xmms_medialib_t *medialib;
122 
123 static const char source_pref[] = "server:client/*:plugin/id3v2:plugin/*";
124 
125 /**
126  * This is only used if we are using a older version of sqlite.
127  * The reason for this is that we must have a global session, due to some
128  * strange limitiations in older sqlite libraries.
129  */
130 static xmms_medialib_session_t *global_medialib_session;
131 
132 /** This protects the above global session */
133 static GMutex *global_medialib_session_mutex;
134 
135 static GMutex *xmms_medialib_debug_mutex;
136 static GHashTable *xmms_medialib_debug_hash;
137 
138 static void
139 xmms_medialib_destroy (xmms_object_t *object)
140 {
141  xmms_medialib_t *mlib = (xmms_medialib_t *)object;
142  if (global_medialib_session) {
143  xmms_sqlite_close (global_medialib_session->sql);
144  g_free (global_medialib_session);
145  }
146  g_mutex_free (mlib->source_lock);
147  g_hash_table_destroy (mlib->sources);
148  g_mutex_free (global_medialib_session_mutex);
151 }
152 
153 #define XMMS_MEDIALIB_SOURCE_SERVER "server"
154 #define XMMS_MEDIALIB_SOURCE_SERVER_ID 1
155 
156 static gint
157 source_match_pattern (const gchar *source, const gchar *pattern,
158  gint pattern_len)
159 {
160  /* check whether we need to keep a wildcard in mind when matching
161  * the strings.
162  */
163  if (pattern_len > 0 && pattern[pattern_len - 1] == '*') {
164  /* if the asterisk is the first character of the pattern,
165  * it obviously accepts anything.
166  */
167  if (pattern_len == 1) {
168  return TRUE;
169  }
170 
171  /* otherwise we have to compare the characters just up to the
172  * asterisk.
173  */
174  return !g_ascii_strncasecmp (source, pattern, pattern_len - 1);
175  }
176 
177  /* there's no wildcards, so just compare all of the characters. */
178  return !g_ascii_strncasecmp (pattern, source, pattern_len);
179 }
180 
181 static int
182 xmms_find_match_index (gint source, const gchar *pref, xmms_medialib_t *mlib)
183 {
184  gchar *source_name, *colon;
185  gint i = 0;
186 
187  g_mutex_lock (mlib->source_lock);
188  source_name = g_hash_table_lookup (mlib->sources, GINT_TO_POINTER (source));
189  g_mutex_unlock (mlib->source_lock);
190 
191  do {
192  gsize len;
193 
194  colon = strchr (pref, ':');
195 
196  /* get the length of this substring */
197  len = colon ? colon - pref : strlen (pref);
198 
199  /* check whether the substring matches */
200  if (source_match_pattern (source_name, pref, len)) {
201  return i;
202  }
203 
204  /* prepare for next iteration */
205  if (colon) {
206  pref = colon + 1;
207  }
208  i++;
209 
210  /* if we just processed the final substring, then we're done */
211  } while (colon);
212 
213  return i;
214 }
215 
216 static void
217 xmms_sqlite_source_pref_binary (sqlite3_context *context, int args,
218  sqlite3_value **val)
219 {
220  gint source;
221  const gchar *pref;
222  xmms_medialib_t *mlib;
223 
224  mlib = sqlite3_user_data (context);
225 
226  if (sqlite3_value_type (val[0]) != SQLITE_INTEGER) {
227  sqlite3_result_error (context, "First argument to xmms_source_pref "
228  "should be a integer", -1);
229  return;
230  }
231  if (sqlite3_value_type (val[1]) != SQLITE3_TEXT) {
232  sqlite3_result_error (context, "Second argument to xmms_source_pref "
233  "should be a string", -1);
234  return;
235  }
236 
237  source = sqlite3_value_int (val[0]);
238  pref = (const gchar *) sqlite3_value_text (val[1]);
239 
240  sqlite3_result_int (context, xmms_find_match_index (source, pref, mlib));
241 }
242 
243 static void
244 xmms_sqlite_source_pref_unary (sqlite3_context *context, int args,
245  sqlite3_value **val)
246 {
247  gint source;
248  xmms_medialib_t *mlib;
249 
250  mlib = sqlite3_user_data (context);
251 
252  if (sqlite3_value_type (val[0]) != SQLITE_INTEGER) {
253  sqlite3_result_error (context, "First argument to xmms_source_pref "
254  "should be a integer", -1);
255  return;
256  }
257 
258  source = sqlite3_value_int (val[0]);
259 
260  sqlite3_result_int (context,
261  xmms_find_match_index (source, source_pref, mlib));
262 }
263 
264 static int
265 add_to_source (void *hash, int columns, char **vals, char **cols)
266 {
267  int source = strtol (vals[0], NULL, 10);
268  g_hash_table_insert ((GHashTable*)hash, GINT_TO_POINTER (source), g_strdup (vals[1]));
269  return 0;
270 }
271 
272 guint32
274  const gchar *source)
275 {
276  gint32 ret = 0;
277  g_return_val_if_fail (source, 0);
278 
279  xmms_sqlite_query_int (session->sql, &ret,
280  "SELECT id FROM Sources WHERE source=%Q",
281  source);
282  if (ret == 0) {
283  xmms_sqlite_exec (session->sql,
284  "INSERT INTO Sources (source) VALUES (%Q)", source);
285  xmms_sqlite_query_int (session->sql, &ret,
286  "SELECT id FROM Sources WHERE source=%Q",
287  source);
288  XMMS_DBG ("Added source %s with id %d", source, ret);
289  g_mutex_lock (session->medialib->source_lock);
290  g_hash_table_insert (session->medialib->sources, GUINT_TO_POINTER (ret), g_strdup (source));
291  g_mutex_unlock (session->medialib->source_lock);
292  }
293 
294  return ret;
295 }
296 
297 
299 xmms_medialib_session_new (const char *file, int line)
300 {
301  xmms_medialib_session_t *session;
302 
303  session = g_new0 (xmms_medialib_session_t, 1);
304  session->medialib = medialib;
305  session->file = file;
306  session->line = line;
307  session->sql = xmms_sqlite_open ();
308 
309  sqlite3_create_function (session->sql, "xmms_source_pref", 2, SQLITE_UTF8,
310  session->medialib, xmms_sqlite_source_pref_binary, NULL, NULL);
311  sqlite3_create_function (session->sql, "xmms_source_pref", 1, SQLITE_UTF8,
312  session->medialib, xmms_sqlite_source_pref_unary, NULL, NULL);
313 
314  return session;
315 }
316 
317 
318 
319 /**
320  * Initialize the medialib and open the database file.
321  *
322  * @param playlist the current playlist pointer
323  * @returns TRUE if successful and FALSE if there was a problem
324  */
325 
328 {
329  gchar *path;
330  xmms_medialib_session_t *session;
331  gboolean create;
332 
333  medialib = xmms_object_new (xmms_medialib_t, xmms_medialib_destroy);
334  medialib->playlist = playlist;
335 
339 
340  xmms_object_cmd_add (XMMS_OBJECT (medialib),
342  XMMS_CMD_FUNC (info));
343  xmms_object_cmd_add (XMMS_OBJECT (medialib),
345  XMMS_CMD_FUNC (mlib_add));
346  xmms_object_cmd_add (XMMS_OBJECT (medialib),
348  XMMS_CMD_FUNC (mlib_remove));
349  xmms_object_cmd_add (XMMS_OBJECT (medialib),
351  XMMS_CMD_FUNC (path_import));
352  xmms_object_cmd_add (XMMS_OBJECT (medialib),
354  XMMS_CMD_FUNC (rehash));
355  xmms_object_cmd_add (XMMS_OBJECT (medialib),
357  XMMS_CMD_FUNC (get_id));
358  xmms_object_cmd_add (XMMS_OBJECT (medialib),
360  XMMS_CMD_FUNC (set_property_str));
361  xmms_object_cmd_add (XMMS_OBJECT (medialib),
363  XMMS_CMD_FUNC (set_property_int));
364  xmms_object_cmd_add (XMMS_OBJECT (medialib),
366  XMMS_CMD_FUNC (remove_property));
367  xmms_object_cmd_add (XMMS_OBJECT (medialib),
369  XMMS_CMD_FUNC (mlib_move));
370 
371  path = XMMS_BUILD_PATH ("medialib.db");
372 
373  xmms_config_property_register ("medialib.path", path, NULL, NULL);
374  xmms_config_property_register ("medialib.analyze_on_startup", "0", NULL, NULL);
375  xmms_config_property_register ("medialib.allow_remote_fs",
376  "0", NULL, NULL);
377 
378  g_free (path);
379 
380 
381  xmms_medialib_debug_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
382  xmms_medialib_debug_mutex = g_mutex_new ();
383  global_medialib_session = NULL;
384 
385  /* init the database */
386  xmms_sqlite_create (&create);
387 
388  if (!sqlite3_threadsafe ()) {
389  xmms_log_info ("********************************************************************");
390  xmms_log_info ("* Using thread hack to compensate for sqlite without threadsafety! *");
391  xmms_log_info ("* This can be a huge performance penalty - upgrade or recompile *");
392  xmms_log_info ("********************************************************************");
393  /** Create a global session, this is only used when the sqlite version
394  * doesn't support concurrent sessions */
395  global_medialib_session = xmms_medialib_session_new ("global", 0);
396  }
397 
398  global_medialib_session_mutex = g_mutex_new ();
399 
400  /**
401  * this dummy just wants to put the default song in the playlist
402  */
403  medialib->source_lock = g_mutex_new ();
404  medialib->sources = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
405 
406  session = xmms_medialib_begin_write ();
407  sqlite3_exec (session->sql, "SELECT id, source FROM Sources",
408  add_to_source, medialib->sources, NULL);
409 
410  if (create) {
411  xmms_error_t error;
412 
413  xmms_medialib_entry_new (session,
414  "file://" SHAREDDIR
415  "/mind.in.a.box-lament_snipplet.ogg",
416  &error);
417  /* A default playlist containing that song has been created
418  * with the mlib.
419  */
420  }
421 
422  xmms_medialib_end (session);
423 
424  return medialib;
425 }
426 
427 /** Session handling */
428 
430 _xmms_medialib_begin (gboolean write, const char *file, int line)
431 {
432  xmms_medialib_session_t *session;
433 
434  {
435  gchar *r;
436  void *me = g_thread_self ();
437  g_mutex_lock (xmms_medialib_debug_mutex);
438  r = g_hash_table_lookup (xmms_medialib_debug_hash, me);
439  if (r) {
440  xmms_log_fatal ("Medialib session begun recursivly at %s:%d, after %s", file, line, r);
441  } else {
442  g_hash_table_insert (xmms_medialib_debug_hash, me,
443  g_strdup_printf ("%s:%d", file, line));
444  }
445  g_mutex_unlock (xmms_medialib_debug_mutex);
446  }
447  if (global_medialib_session) {
448  /** This will only happen when OLD_SQLITE_VERSION is set. */
449  g_mutex_lock (global_medialib_session_mutex);
450  return global_medialib_session;
451  }
452 
453  session = xmms_medialib_session_new (file, line);
454  xmms_object_ref (XMMS_OBJECT (medialib));
455  session->write = write;
456 
457  if (write) {
458  /* Start a exclusive transaction */
459  if (!xmms_sqlite_exec (session->sql, "BEGIN EXCLUSIVE TRANSACTION")) {
460  xmms_log_error ("transaction failed!");
461  }
462  }
463 
464  session->next_id = -1;
465 
466  return session;
467 }
468 
469 void
471 {
472  g_return_if_fail (session);
473 
474  {
475  void *me = g_thread_self ();
476  g_mutex_lock (xmms_medialib_debug_mutex);
477  g_hash_table_remove (xmms_medialib_debug_hash, me);
478  g_mutex_unlock (xmms_medialib_debug_mutex);
479  }
480 
481  if (session->write) {
482  xmms_sqlite_exec (session->sql, "COMMIT");
483  }
484 
485  if (session == global_medialib_session) {
486  g_mutex_unlock (global_medialib_session_mutex);
487  return;
488  }
489 
490  xmms_sqlite_close (session->sql);
491  xmms_object_unref (XMMS_OBJECT (session->medialib));
492  g_free (session);
493 }
494 
495 static int
496 xmms_medialib_string_cb (xmmsv_t **row, gpointer udata)
497 {
498  gchar **str = udata;
499  const gchar *buf;
500 
501  if (row && row[0] && xmmsv_get_type (row[0]) == XMMSV_TYPE_STRING) {
502  xmmsv_get_string (row[0], &buf);
503  *str = g_strdup (buf);
504  } else
505  XMMS_DBG ("Expected string but got something else!");
506 
507  return 0;
508 }
509 
510 static int
511 xmms_medialib_value_cb (xmmsv_t **row, gpointer udata)
512 {
513  xmmsv_t **ret = udata;
514 
515  *ret = xmmsv_ref (row[0]);
516 
517  return 0;
518 }
519 
520 /**
521  * Retrieve a property from an entry
522  *
523  * @see xmms_medialib_entry_property_get_str
524  */
525 
526 #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"
527 
528 xmmsv_t *
530  xmms_medialib_entry_t entry,
531  const gchar *property)
532 {
533  xmmsv_t *ret = NULL;
534 
535  g_return_val_if_fail (property, NULL);
536  g_return_val_if_fail (session, NULL);
537 
538  if (!strcmp (property, XMMS_MEDIALIB_ENTRY_PROPERTY_ID)) {
539  ret = xmmsv_new_int (entry);
540  } else {
541  xmms_sqlite_query_array (session->sql, xmms_medialib_value_cb,
543  property, entry, source_pref);
544  }
545 
546  return ret;
547 }
548 
549 /**
550  * Retrieve a property from an entry.
551  *
552  * @param session The medialib session to be used for the transaction.
553  * @param entry Entry to query.
554  * @param property The property to extract. Strings passed should
555  * be defined in medialib.h
556  *
557  * @returns Newly allocated gchar that needs to be freed with g_free
558  */
559 
560 gchar *
562  xmms_medialib_entry_t entry,
563  const gchar *property)
564 {
565  gchar *ret = NULL;
566 
567  g_return_val_if_fail (property, NULL);
568  g_return_val_if_fail (session, NULL);
569 
570  xmms_sqlite_query_array (session->sql, xmms_medialib_string_cb, &ret,
572  property, entry, source_pref);
573 
574  return ret;
575 }
576 
577 /**
578  * Retrieve a property as a int from a entry.
579  *
580  * @param session The medialib session to be used for the transaction.
581  * @param entry Entry to query.
582  * @param property The property to extract. Strings passed should
583  * be defined in medialib.h
584  *
585  * @returns Property as integer, or -1 if it doesn't exist.
586  */
587 gint
589  xmms_medialib_entry_t entry,
590  const gchar *property)
591 {
592  gint32 ret = -1;
593 
594  g_return_val_if_fail (property, -1);
595  g_return_val_if_fail (session, -1);
596 
597  xmms_sqlite_query_int (session->sql, &ret,
599  property, entry, source_pref);
600 
601  return ret;
602 }
603 
604 /**
605  * Set a entry property to a new value, overwriting the old value.
606  *
607  * @param session The medialib session to be used for the transaction.
608  * @param entry Entry to alter.
609  * @param property The property to extract. Strings passed should
610  * be defined in medialib.h
611  * @param value gint with the new value, will be copied in to the medialib
612  *
613  * @returns TRUE on success and FALSE on failure.
614  */
615 gboolean
617  xmms_medialib_entry_t entry,
618  const gchar *property, gint value)
619 {
620  return xmms_medialib_entry_property_set_int_source (session, entry,
621  property, value,
623 }
624 
625 
626 gboolean
628  xmms_medialib_entry_t entry,
629  const gchar *property, gint value,
630  guint32 source)
631 {
632  gboolean ret;
633 
634  g_return_val_if_fail (property, FALSE);
635  g_return_val_if_fail (session, FALSE);
636 
637  if (!xmms_medialib_check_id_in_session (entry, session)) {
638  XMMS_DBG ("Trying to add property to id %d "
639  "that is not yet in the medialib. Denied.", entry);
640 
641  return FALSE;
642  }
643 
644  ret = xmms_sqlite_exec (session->sql,
645  "INSERT OR REPLACE INTO Media "
646  "(id, value, intval, key, source) VALUES "
647  "(%d, '%d', %d, %Q, %d)",
648  entry, value, value, property, source);
649 
650  return ret;
651 
652 }
653 
654 /**
655  * Set a entry property to a new value, overwriting the old value.
656  *
657  * @param session The medialib session to be used for the transaction.
658  * @param entry Entry to alter.
659  * @param property The property to extract. Strings passed should
660  * be defined in medialib.h
661  * @param value gchar with the new value, will be copied in to the medialib
662  *
663  * @returns TRUE on success and FALSE on failure.
664  */
665 gboolean
667  xmms_medialib_entry_t entry,
668  const gchar *property, const gchar *value)
669 {
670  return xmms_medialib_entry_property_set_str_source (session, entry,
671  property, value,
673 }
674 
675 
676 gboolean
678  xmms_medialib_entry_t entry,
679  const gchar *property, const gchar *value,
680  guint32 source)
681 {
682  gboolean ret;
683 
684  g_return_val_if_fail (property, FALSE);
685  g_return_val_if_fail (session, FALSE);
686 
687  if (value && !g_utf8_validate (value, -1, NULL)) {
688  XMMS_DBG ("OOOOOPS! Trying to set property %s to a NON UTF-8 string (%s) I will deny that!", property, value);
689  return FALSE;
690  }
691 
692  if (!xmms_medialib_check_id_in_session (entry, session)) {
693  XMMS_DBG ("Trying to add property to id %d "
694  "that is not yet in the medialib. Denied.", entry);
695 
696  return FALSE;
697  }
698 
699  ret = xmms_sqlite_exec (session->sql,
700  "INSERT OR REPLACE INTO Media "
701  "(id, value, intval, key, source) VALUES "
702  "(%d, %Q, NULL, %Q, %d)",
703  entry, value, property, source);
704 
705  return ret;
706 
707 }
708 
709 
710 /**
711  * Trigger a update signal to the client. This should be called
712  * when important information in the entry has been changed and
713  * should be visible to the user.
714  *
715  * @param entry Entry to signal a update for.
716  */
717 
718 void
720 {
721  xmms_object_emit_f (XMMS_OBJECT (medialib),
723  XMMSV_TYPE_INT32, entry);
724 }
725 
726 /**
727  * Trigger an added siginal to the client. This should be
728  * called when a new entry has been added to the medialib
729  *
730  * @param entry Entry to signal an add for.
731  */
732 void
734 {
735  xmms_object_emit_f (XMMS_OBJECT (medialib),
737  XMMSV_TYPE_INT32, entry);
738 }
739 
740 static void
741 xmms_medialib_client_entry_remove (xmms_medialib_t *medialib, gint32 entry, xmms_error_t *error)
742 {
744 }
745 
746 /**
747  * Remove a medialib entry from the database
748  *
749  * @param session The medialib session to be used for the transaction.
750  * @param entry Entry to remove
751  */
752 
753 void
755 {
756  xmms_medialib_session_t *session;
757 
758  session = xmms_medialib_begin_write ();
759  xmms_sqlite_exec (session->sql, "DELETE FROM Media WHERE id=%d", entry);
760  xmms_medialib_end (session);
761 
762  /** @todo safe ? */
763  xmms_playlist_remove_by_entry (medialib->playlist, entry);
764 }
765 
766 static xmms_medialib_entry_t xmms_medialib_entry_new_insert (xmms_medialib_session_t *session, guint32 id, const char *url, xmms_error_t *error);
767 
768 static void
769 process_file (xmms_medialib_session_t *session,
770  const gchar *playlist,
771  gint32 pos,
772  const gchar *path,
773  xmms_error_t *error)
774 {
775  xmms_medialib_entry_t entry;
776 
777  entry = xmms_medialib_entry_new_encoded (session, path, error);
778 
779  if (entry && playlist != NULL) {
780  if (pos >= 0) {
781  xmms_playlist_insert_entry (session->medialib->playlist,
782  playlist, pos, entry, error);
783  } else {
784  xmms_playlist_add_entry (session->medialib->playlist,
785  playlist, entry, error);
786  }
787  }
788 }
789 
790 static gint
791 cmp_val (gconstpointer a, gconstpointer b)
792 {
793  xmmsv_t *v1, *v2;
794  const gchar *s1, *s2;
795  v1 = (xmmsv_t *) a;
796  v2 = (xmmsv_t *) b;
797  if (xmmsv_get_type (v1) != XMMSV_TYPE_DICT)
798  return 0;
799  if (xmmsv_get_type (v2) != XMMSV_TYPE_DICT)
800  return 0;
801 
802  xmmsv_dict_entry_get_string (v1, "path", &s1);
803  xmmsv_dict_entry_get_string (v2, "path", &s2);
804 
805  return strcmp (s1, s2);
806 }
807 
808 /* code ported over from CLI's "radd" command. */
809 /* note that the returned file list is reverse-sorted! */
810 static gboolean
811 process_dir (const gchar *directory,
812  GList **ret,
813  xmms_error_t *error)
814 {
815  GList *list;
816 
817  list = xmms_xform_browse (directory, error);
818  if (!list) {
819  return FALSE;
820  }
821 
822  list = g_list_sort (list, cmp_val);
823 
824  while (list) {
825  xmmsv_t *val = list->data;
826  const gchar *str;
827  gint isdir;
828 
829  xmmsv_dict_entry_get_string (val, "path", &str);
830  xmmsv_dict_entry_get_int (val, "isdir", &isdir);
831 
832  if (isdir == 1) {
833  process_dir (str, ret, error);
834  } else {
835  *ret = g_list_prepend (*ret, g_strdup (str));
836  }
837 
838  xmmsv_unref (val);
839  list = g_list_delete_link (list, list);
840  }
841 
842  return TRUE;
843 }
844 
845 void
847  xmms_medialib_entry_t entry)
848 {
849  xmms_sqlite_exec (session->sql,
850  "DELETE FROM Media WHERE id=%d AND source=%d "
851  "AND key NOT IN (%Q, %Q, %Q, %Q, %Q)",
852  entry,
859 
860  xmms_sqlite_exec (session->sql,
861  "DELETE FROM Media WHERE id=%d AND source IN "
862  "(SELECT id FROM Sources WHERE source LIKE 'plugin/%%' "
863  "AND source != 'plugin/playlist')",
864  entry);
865 
866 }
867 
868 static void
869 xmms_medialib_client_rehash (xmms_medialib_t *medialib, gint32 id, xmms_error_t *error)
870 {
872  xmms_medialib_session_t *session;
873 
874  session = xmms_medialib_begin_write ();
875 
876  if (id) {
877  xmms_sqlite_exec (session->sql,
878  "UPDATE Media SET value = '%d', intval = %d "
879  "WHERE key='%s' AND id=%d",
883  } else {
884  xmms_sqlite_exec (session->sql,
885  "UPDATE Media SET value = '%d', intval = %d "
886  "WHERE key='%s'",
890  }
891 
892  xmms_medialib_end (session);
893 
894  mr = xmms_playlist_mediainfo_reader_get (medialib->playlist);
896 
897 }
898 
899 /* Recursively add entries under the given path to the medialib,
900  * optionally adding them to a playlist if the playlist argument is
901  * not NULL.
902  */
903 void
904 xmms_medialib_add_recursive (xmms_medialib_t *medialib, const gchar *playlist,
905  const gchar *path, xmms_error_t *error)
906 {
907  /* Just called insert with negative pos to append */
908  xmms_medialib_insert_recursive (medialib, playlist, -1, path, error);
909 }
910 
911 /* Recursively adding entries under the given path to the medialib,
912  * optionally insert them into a playlist at a given position if the
913  * playlist argument is not NULL. If the position is negative, entries
914  * are appended to the playlist.
915  */
916 void
917 xmms_medialib_insert_recursive (xmms_medialib_t *medialib, const gchar *playlist,
918  gint32 pos, const gchar *path,
919  xmms_error_t *error)
920 {
921  xmms_medialib_session_t *session;
922  GList *first, *list = NULL, *n;
923 
924  g_return_if_fail (medialib);
925  g_return_if_fail (path);
926 
927  /* Allocate our first list node manually here. The following call
928  * to process_dir() will prepend all other nodes, so afterwards
929  * "first" will point to the last node of the list... see below.
930  */
931  first = list = g_list_alloc ();
932 
933  process_dir (path, &list, error);
934 
935  XMMS_DBG ("taking the transaction!");
936  session = xmms_medialib_begin_write ();
937 
938  /* We now want to iterate the list in the order in which the nodes
939  * were added, ie in reverse order. Thankfully we stored a pointer
940  * to the last node in the list before, which saves us an expensive
941  * g_list_last() call now. Increase pos each time to retain order.
942  */
943  for (n = first->prev; n; n = g_list_previous (n)) {
944  process_file (session, playlist, pos, n->data, error);
945  if (pos >= 0)
946  pos++;
947  g_free (n->data);
948  }
949 
950  g_list_free (list);
951 
952  XMMS_DBG ("and we are done!");
953  xmms_medialib_end (session);
954 }
955 
956 static void
957 xmms_medialib_client_path_import (xmms_medialib_t *medialib, const gchar *path,
958  xmms_error_t *error)
959 {
960  xmms_medialib_add_recursive (medialib, NULL, path, error);
961 }
962 
964 xmms_medialib_entry_new_insert (xmms_medialib_session_t *session,
965  guint32 id,
966  const char *url,
967  xmms_error_t *error)
968 {
970  guint source;
971 
973 
974  if (!xmms_sqlite_exec (session->sql,
975  "INSERT INTO Media (id, key, value, source) VALUES "
976  "(%d, '%s', %Q, %d)",
978  source)) {
979  xmms_error_set (error, XMMS_ERROR_GENERIC,
980  "Sql error/corruption inserting url");
981  return 0;
982  }
983 
985  mr = xmms_playlist_mediainfo_reader_get (medialib->playlist);
987 
988  return 1;
989 
990 }
991 
992 /**
993  * @internal
994  */
997  const char *url, xmms_error_t *error)
998 {
999  gint id = 0;
1000  guint ret = 0;
1001  guint source;
1002 
1003  g_return_val_if_fail (url, 0);
1004  g_return_val_if_fail (session, 0);
1005  g_return_val_if_fail (session->write, 0);
1006 
1008 
1009  xmms_sqlite_query_int (session->sql, &id,
1010  "SELECT id AS value FROM Media "
1011  "WHERE key='%s' AND value=%Q AND source=%d",
1013  source);
1014 
1015  if (id) {
1016  ret = id;
1017  } else {
1018  if (session->next_id <= 0 &&
1019  !xmms_sqlite_query_int (session->sql, &session->next_id,
1020  "SELECT IFNULL(MAX (id),0)+1 FROM Media")) {
1021  xmms_error_set (error, XMMS_ERROR_GENERIC,
1022  "SQL error/corruption selecting max(id)");
1023  return 0;
1024  }
1025 
1026  ret = session->next_id++;
1027 
1028  if (!xmms_medialib_entry_new_insert (session, ret, url, error))
1029  return 0;
1030  }
1031 
1033  return ret;
1034 
1035 }
1036 
1037 /**
1038  * Welcome to a function that should be called something else.
1039  * Returns a entry for a URL, if the URL is already in the medialib
1040  * the current entry will be returned otherwise a new one will be
1041  * created and returned.
1042  *
1043  * @todo rename to something better?
1044  *
1045  * @param session The medialib session to be used for the transaction.
1046  * @param url URL to add/retrieve from the medialib
1047  * @param error If an error occurs, it will be stored in there.
1048  *
1049  * @returns Entry mapped to the URL
1050  */
1053 {
1054  gchar *enc_url;
1056 
1057  enc_url = xmms_medialib_url_encode (url);
1058  if (!enc_url)
1059  return 0;
1060 
1061  res = xmms_medialib_entry_new_encoded (session, enc_url, error);
1062 
1063  g_free (enc_url);
1064 
1065  return res;
1066 }
1067 
1068 gint32
1069 xmms_medialib_client_entry_get_id (xmms_medialib_t *medialib, const gchar *url,
1070  xmms_error_t *error)
1071 {
1072  gint32 id = 0;
1074 
1075  xmms_sqlite_query_int (session->sql, &id,
1076  "SELECT id AS value FROM Media WHERE key='%s' AND value=%Q AND source=%d",
1079  xmms_medialib_end (session);
1080 
1081  return id;
1082 }
1083 
1084 static void
1085 xmms_medialib_tree_add_tuple (GTree *tree, const char *key,
1086  const char *source, xmmsv_t *value)
1087 {
1088  xmmsv_t *keytreeval;
1089 
1090  /* Find (or insert) subtree matching the prop key */
1091  keytreeval = (xmmsv_t *) g_tree_lookup (tree, key);
1092  if (!keytreeval) {
1093  keytreeval = xmmsv_new_dict ();
1094  g_tree_insert (tree, g_strdup (key), keytreeval);
1095  }
1096 
1097  /* Replace (or insert) value matching the prop source */
1098  xmmsv_dict_set (keytreeval, source, value);
1099 }
1100 
1101 static gboolean
1102 xmms_medialib_list_cb (xmmsv_t **row, gpointer udata)
1103 {
1104  GList **list = (GList**)udata;
1105 
1106  /* Source */
1107  *list = g_list_prepend (*list, xmmsv_ref (row[0]));
1108 
1109  /* Key */
1110  *list = g_list_prepend (*list, xmmsv_ref (row[1]));
1111 
1112  /* Value */
1113  *list = g_list_prepend (*list, xmmsv_ref (row[2]));
1114 
1115  return TRUE;
1116 }
1117 
1118 static gboolean
1119 xmms_medialib_tree_cb (xmmsv_t **row, gpointer udata)
1120 {
1121  const char *key, *source;
1122  xmmsv_t *value;
1123  GTree **tree = (GTree**)udata;
1124 
1125  xmmsv_get_string (row[0], &source);
1126  xmmsv_get_string (row[1], &key);
1127  value = row[2];
1128 
1129  xmms_medialib_tree_add_tuple (*tree, key, source, value);
1130 
1131  return TRUE;
1132 }
1133 
1134 /**
1135  * Convert a entry and all properties to a hashtable that
1136  * could be feed to the client or somewhere else in the daemon.
1137  *
1138  * @param session The medialib session to be used for the transaction.
1139  * @param entry Entry to convert.
1140  *
1141  * @returns Newly allocated hashtable with newly allocated strings
1142  * make sure to free them all.
1143  */
1144 
1145 static GList *
1146 xmms_medialib_entry_to_list (xmms_medialib_session_t *session, xmms_medialib_entry_t entry)
1147 {
1148  GList *ret = NULL;
1149  gboolean s;
1150 
1151  g_return_val_if_fail (session, NULL);
1152  g_return_val_if_fail (entry, NULL);
1153 
1154  s = xmms_sqlite_query_array (session->sql, xmms_medialib_list_cb, &ret,
1155  "SELECT s.source, m.key, "
1156  "IFNULL (m.intval, m.value) "
1157  "FROM Media m LEFT JOIN "
1158  "Sources s ON m.source = s.id "
1159  "WHERE m.id=%d",
1160  entry);
1161  if (!s || !ret) {
1162  return NULL;
1163  }
1164 
1165  /* Source */
1166  ret = g_list_prepend (ret, xmmsv_new_string ("server"));
1167 
1168  /* Key */
1169  ret = g_list_prepend (ret, xmmsv_new_string ("id"));
1170 
1171  /* Value */
1172  ret = g_list_prepend (ret, xmmsv_new_int (entry));
1173 
1174  return g_list_reverse (ret);
1175 }
1176 
1177 /**
1178  * Convert a entry and all properties to a key-source-value tree that
1179  * could be feed to the client or somewhere else in the daemon.
1180  *
1181  * @param session The medialib session to be used for the transaction.
1182  * @param entry Entry to convert.
1183  *
1184  * @returns Newly allocated tree with newly allocated strings
1185  * make sure to free them all.
1186  */
1187 
1188 static GTree *
1189 xmms_medialib_entry_to_tree (xmms_medialib_session_t *session, xmms_medialib_entry_t entry)
1190 {
1191  GTree *ret;
1192  xmmsv_t *v_entry;
1193  gboolean s;
1194 
1195  g_return_val_if_fail (session, NULL);
1196  g_return_val_if_fail (entry, NULL);
1197 
1198  if (!xmms_medialib_check_id_in_session (entry, session)) {
1199  return NULL;
1200  }
1201 
1202  ret = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
1203  (GDestroyNotify) xmmsv_unref);
1204 
1205  s = xmms_sqlite_query_array (session->sql, xmms_medialib_tree_cb,
1206  &ret,
1207  "SELECT s.source, m.key, "
1208  "IFNULL (m.intval, m.value) "
1209  "FROM Media m LEFT JOIN "
1210  "Sources s ON m.source = s.id "
1211  "WHERE m.id=%d",
1212  entry);
1213  if (!s || !ret) {
1214  return NULL;
1215  }
1216 
1217  v_entry = xmmsv_new_int (entry);
1218  xmms_medialib_tree_add_tuple (ret, "id", "server", v_entry);
1219  xmmsv_unref (v_entry);
1220 
1221  return ret;
1222 }
1223 
1224 /* Legacy, still used by collections. */
1225 GList *
1227 {
1228  xmms_medialib_session_t *session;
1229  GList *ret = NULL;
1230 
1231  if (!id) {
1232  xmms_error_set (err, XMMS_ERROR_NOENT, "No such entry, 0");
1233  } else {
1234  session = xmms_medialib_begin ();
1235  ret = xmms_medialib_entry_to_list (session, id);
1236  xmms_medialib_end (session);
1237 
1238  if (!ret) {
1239  xmms_error_set (err, XMMS_ERROR_NOENT,
1240  "Could not retrieve info for that entry!");
1241  }
1242  }
1243 
1244  return ret;
1245 }
1246 
1247 static GTree *
1248 xmms_medialib_client_info (xmms_medialib_t *medialib, gint32 id, xmms_error_t *err)
1249 {
1250  xmms_medialib_session_t *session;
1251  GTree *ret = NULL;
1252 
1253  if (!id) {
1254  xmms_error_set (err, XMMS_ERROR_NOENT, "No such entry, 0");
1255  } else {
1256  session = xmms_medialib_begin ();
1257  ret = xmms_medialib_entry_to_tree (session, id);
1258  xmms_medialib_end (session);
1259 
1260  if (!ret) {
1261  xmms_error_set (err, XMMS_ERROR_NOENT,
1262  "Could not retrieve info for that entry!");
1263  }
1264  }
1265 
1266  return ret;
1267 }
1268 
1269 static gboolean
1270 select_callback (xmmsv_t *row, gpointer udata)
1271 {
1272  GList **l = (GList **) udata;
1273  *l = g_list_prepend (*l, row);
1274  return TRUE;
1275 }
1276 
1277 /**
1278  * Add a entry to the medialib. Calls #xmms_medialib_entry_new and then
1279  * wakes up the mediainfo_reader in order to resolve the metadata.
1280  *
1281  * @param medialib Medialib pointer
1282  * @param url URL to add
1283  * @param error In case of error this will be filled.
1284  */
1285 
1286 static void
1287 xmms_medialib_client_add_entry (xmms_medialib_t *medialib, const gchar *url,
1288  xmms_error_t *error)
1289 {
1290  xmms_medialib_entry_t entry;
1291  xmms_medialib_session_t *session;
1292 
1293  g_return_if_fail (medialib);
1294  g_return_if_fail (url);
1295 
1296  session = xmms_medialib_begin_write ();
1297 
1298  entry = xmms_medialib_entry_new_encoded (session, url, error);
1299 
1300  xmms_medialib_end (session);
1301 }
1302 
1303 /**
1304  * Changes the URL of an entry in the medialib.
1305  *
1306  * @param medialib Medialib pointer
1307  * @param entry entry to modify
1308  * @param url URL to change to
1309  * @param error In case of error this will be filled.
1310  */
1311 static void
1312 xmms_medialib_client_move_entry (xmms_medialib_t *medialib, gint32 entry,
1313  const gchar *url, xmms_error_t *error)
1314 {
1315  const gchar *key = XMMS_MEDIALIB_ENTRY_PROPERTY_URL;
1316  guint32 sourceid = XMMS_MEDIALIB_SOURCE_SERVER_ID;
1317  gchar *enc_url;
1318 
1319  xmms_medialib_session_t *session;
1320 
1321  enc_url = xmms_medialib_url_encode (url);
1322 
1323  session = xmms_medialib_begin_write ();
1324  xmms_medialib_entry_property_set_str_source (session, entry, key, enc_url,
1325  sourceid);
1326  xmms_medialib_end (session);
1327 
1328  g_free (enc_url);
1329 
1331 }
1332 
1333 static void
1334 xmms_medialib_client_property_set_str (xmms_medialib_t *medialib, gint32 entry,
1335  const gchar *source, const gchar *key,
1336  const gchar *value, xmms_error_t *error)
1337 {
1338  guint32 sourceid;
1339  xmms_medialib_session_t *session;
1340 
1341  if (g_ascii_strcasecmp (source, "server") == 0) {
1342  xmms_error_set (error, XMMS_ERROR_GENERIC,
1343  "Can't write to source server!");
1344  return;
1345  }
1346 
1347  session = xmms_medialib_begin_write ();
1348  sourceid = xmms_medialib_source_to_id (session, source);
1349 
1350  xmms_medialib_entry_property_set_str_source (session, entry, key, value,
1351  sourceid);
1352  xmms_medialib_end (session);
1353 
1355 }
1356 
1357 static void
1358 xmms_medialib_client_property_set_int (xmms_medialib_t *medialib, gint32 entry,
1359  const gchar *source, const gchar *key,
1360  gint32 value, xmms_error_t *error)
1361 {
1362  guint32 sourceid;
1363  xmms_medialib_session_t *session;
1364 
1365  if (g_ascii_strcasecmp (source, "server") == 0) {
1366  xmms_error_set (error, XMMS_ERROR_GENERIC,
1367  "Can't write to source server!");
1368  return;
1369  }
1370 
1371  session = xmms_medialib_begin_write ();
1372  sourceid = xmms_medialib_source_to_id (session, source);
1373  xmms_medialib_entry_property_set_int_source (session, entry, key, value,
1374  sourceid);
1375  xmms_medialib_end (session);
1376 
1378 }
1379 
1380 static void
1381 xmms_medialib_property_remove (xmms_medialib_t *medialib, gint32 entry,
1382  const gchar *source, const gchar *key,
1383  xmms_error_t *error)
1384 {
1385  guint32 sourceid;
1386 
1388  sourceid = xmms_medialib_source_to_id (session, source);
1389  xmms_sqlite_exec (session->sql,
1390  "DELETE FROM Media WHERE source=%d AND key='%s' AND "
1391  "id=%d",
1392  sourceid, key, entry);
1393  xmms_medialib_end (session);
1394 
1396 }
1397 
1398 static void
1399 xmms_medialib_client_property_remove (xmms_medialib_t *medialib, gint32 entry,
1400  const gchar *source, const gchar *key,
1401  xmms_error_t *error)
1402 {
1403  if (g_ascii_strcasecmp (source, "server") == 0) {
1404  xmms_error_set (error, XMMS_ERROR_GENERIC,
1405  "Can't remove properties set by the server!");
1406  return;
1407  }
1408 
1409  return xmms_medialib_property_remove (medialib, entry, source, key, error);
1410 }
1411 
1412 /**
1413  * Get a list of GHashTables 's that matches the query.
1414  *
1415  * @param session The medialib session to be used for the transaction.
1416  * @param query SQL query that should be executed.
1417  * @param error In case of error this will be filled.
1418  * @returns GList containing GHashTables. Caller are responsible to
1419  * free all memory.
1420  */
1421 GList *
1423  const gchar *query, xmms_error_t *error)
1424 {
1425  GList *res = NULL;
1426  gint ret;
1427 
1428  g_return_val_if_fail (query, 0);
1429  g_return_val_if_fail (session, 0);
1430 
1431  ret = xmms_sqlite_query_table (session->sql, select_callback,
1432  (void *)&res, error, "%s", query);
1433 
1434  return ret ? g_list_reverse (res) : NULL;
1435 }
1436 
1437 /** @} */
1438 
1439 /**
1440  * @internal
1441  */
1442 
1443 gboolean
1445 {
1446  xmms_medialib_session_t *session;
1447  gboolean ret;
1448 
1449  session = xmms_medialib_begin ();
1450  ret = xmms_medialib_check_id_in_session (entry, session);
1451  xmms_medialib_end (session);
1452 
1453  return ret;
1454 }
1455 
1456 /**
1457  * @internal
1458  */
1459 
1460 static gboolean
1461 xmms_medialib_check_id_in_session (xmms_medialib_entry_t entry,
1462  xmms_medialib_session_t *session)
1463 {
1464  gint c = 0;
1465 
1466  if (!xmms_sqlite_query_int (session->sql, &c,
1467  "SELECT COUNT(id) FROM Media WHERE id=%d",
1468  entry)) {
1469  return FALSE;
1470  }
1471 
1472  return c > 0;
1473 }
1474 
1475 
1476 /**
1477  * @internal
1478  * Get the next unresolved entry. Used by the mediainfo reader..
1479  */
1480 
1483 {
1484  gint32 ret = 0;
1485 
1486  g_return_val_if_fail (session, 0);
1487 
1488  xmms_sqlite_query_int (session->sql, &ret,
1489  "SELECT id FROM Media WHERE key='%s' "
1490  "AND source=%d AND intval IN (%d, %d) LIMIT 1",
1495 
1496  return ret;
1497 }
1498 
1499 guint
1501 {
1502  gint ret;
1503  g_return_val_if_fail (session, 0);
1504 
1505  xmms_sqlite_query_int (session->sql, &ret,
1506  "SELECT COUNT(id) AS value FROM Media WHERE "
1507  "key='%s' AND intval IN (%d, %d) AND source=%d",
1512 
1513  return ret;
1514 }
1515 
1516 gboolean
1518 {
1519  int i = 0, j = 0;
1520 
1521  g_return_val_if_fail (url, TRUE);
1522 
1523  while (url[i]) {
1524  unsigned char chr = url[i++];
1525 
1526  if (chr == '+') {
1527  url[j++] = ' ';
1528  } else if (chr == '%') {
1529  char ts[3];
1530  char *t;
1531 
1532  ts[0] = url[i++];
1533  if (!ts[0])
1534  return FALSE;
1535  ts[1] = url[i++];
1536  if (!ts[1])
1537  return FALSE;
1538  ts[2] = '\0';
1539 
1540  url[j++] = strtoul (ts, &t, 16);
1541  if (t != &ts[2])
1542  return FALSE;
1543  } else {
1544  url[j++] = chr;
1545  }
1546  }
1547 
1548  url[j] = '\0';
1549 
1550  return TRUE;
1551 }
1552 
1553 
1554 #define GOODCHAR(a) ((((a) >= 'a') && ((a) <= 'z')) || \
1555  (((a) >= 'A') && ((a) <= 'Z')) || \
1556  (((a) >= '0') && ((a) <= '9')) || \
1557  ((a) == ':') || \
1558  ((a) == '/') || \
1559  ((a) == '-') || \
1560  ((a) == '.') || \
1561  ((a) == '_'))
1562 
1563 /* we don't share code here with medialib because we want to use g_malloc :( */
1564 gchar *
1565 xmms_medialib_url_encode (const gchar *path)
1566 {
1567  static gchar hex[16] = "0123456789abcdef";
1568  gchar *res;
1569  int i = 0, j = 0;
1570 
1571  res = g_malloc (strlen (path) * 3 + 1);
1572  if (!res)
1573  return NULL;
1574 
1575  while (path[i]) {
1576  guchar chr = path[i++];
1577  if (GOODCHAR (chr)) {
1578  res[j++] = chr;
1579  } else if (chr == ' ') {
1580  res[j++] = '+';
1581  } else {
1582  res[j++] = '%';
1583  res[j++] = hex[((chr & 0xf0) >> 4)];
1584  res[j++] = hex[(chr & 0x0f)];
1585  }
1586  }
1587 
1588  res[j] = '\0';
1589 
1590  return res;
1591 }
struct xmmsv_St xmmsv_t
Definition: xmmsv.h:51
#define XMMS_CMD_FUNC(cmdid)
Definition: xmms_object.h:181
gchar * xmms_medialib_url_encode(const gchar *path)
Definition: medialib.c:1565
xmmsv_t * xmmsv_new_dict(void)
Allocates a new dict xmmsv_t.
Definition: value.c:266
sqlite3 * xmms_sqlite_open()
Open a database or create a new one.
Definition: sqlite.c:513
xmms_medialib_session_t * _xmms_medialib_begin(gboolean write, const char *file, int line)
Session handling.
Definition: medialib.c:430
G_BEGIN_DECLS typedef gint32 xmms_medialib_entry_t
Definition: xmms_medialib.h:86
struct xmms_mediainfo_reader_St xmms_mediainfo_reader_t
gboolean xmms_sqlite_create(gboolean *create)
Definition: sqlite.c:365
int xmmsv_dict_entry_get_int(xmmsv_t *val, const char *key, int32_t *r)
gboolean xmms_sqlite_query_array(sqlite3 *sql, xmms_medialib_row_array_method_t method, gpointer udata, const gchar *query,...)
Definition: sqlite.c:747
#define XMMS_OBJECT(p)
Definition: xmms_object.h:84
void xmmsv_unref(xmmsv_t *val)
Decreases the references for the xmmsv_t When the number of references reaches 0 it will be freed...
Definition: value.c:301
void xmms_medialib_add_recursive(xmms_medialib_t *medialib, const gchar *playlist, const gchar *path, xmms_error_t *error)
Definition: medialib.c:904
int xmmsv_dict_set(xmmsv_t *dictv, const char *key, xmmsv_t *val)
Insert an element under the given key in the dict xmmsv_t.
Definition: value.c:1744
#define xmms_object_unref(obj)
Definition: xmms_object.h:193
int xmmsv_dict_entry_get_string(xmmsv_t *val, const char *key, const char **r)
void xmms_medialib_insert_recursive(xmms_medialib_t *medialib, const gchar *playlist, gint32 pos, const gchar *path, xmms_error_t *error)
Definition: medialib.c:917
gint xmms_medialib_entry_property_get_int(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property)
Retrieve a property as a int from a entry.
Definition: medialib.c:588
void xmms_object_cmd_add(xmms_object_t *object, guint cmdid, const xmms_object_cmd_desc_t *desc)
Add a command that could be called from the client API to a object.
Definition: object.c:321
#define XMMS_MEDIALIB_SOURCE_SERVER_ID
Definition: medialib.c:154
void xmms_medialib_entry_cleanup(xmms_medialib_session_t *session, xmms_medialib_entry_t entry)
Definition: medialib.c:846
void xmms_ipc_broadcast_register(xmms_object_t *object, xmms_ipc_signals_t signalid)
Register a broadcast signal.
Definition: ipc.c:694
void xmms_playlist_add_entry(xmms_playlist_t *playlist, const gchar *plname, xmms_medialib_entry_t file, xmms_error_t *err)
Add an entry to the playlist without validating it.
Definition: playlist.c:1154
xmmsv_t * xmmsv_new_string(const char *s)
Allocates a new string xmmsv_t.
Definition: value.c:178
gboolean xmms_medialib_entry_property_set_int_source(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property, gint value, guint32 source)
Definition: medialib.c:627
gboolean xmms_medialib_entry_property_set_str(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property, const gchar *value)
Set a entry property to a new value, overwriting the old value.
Definition: medialib.c:666
guint32 xmms_medialib_source_to_id(xmms_medialib_session_t *session, const gchar *source)
Definition: medialib.c:273
GList * xmms_medialib_select(xmms_medialib_session_t *session, const gchar *query, xmms_error_t *error)
Get a list of GHashTables &#39;s that matches the query.
Definition: medialib.c:1422
gboolean xmms_sqlite_query_int(sqlite3 *sql, gint32 *out, const gchar *query,...)
Definition: sqlite.c:773
XMMS_CMD_DEFINE(info, xmms_medialib_client_info, xmms_medialib_t *, DICT, INT32, NONE)
#define xmms_log_error(fmt,...)
Definition: xmms_log.h:35
xmmsv_t * xmms_medialib_entry_property_get_value(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property)
Definition: medialib.c:529
gboolean xmms_sqlite_query_table(sqlite3 *sql, xmms_medialib_row_table_method_t method, gpointer udata, xmms_error_t *error, const gchar *query,...)
Execute a query to the database.
Definition: sqlite.c:599
GList * xmms_medialib_info_list(xmms_medialib_t *medialib, guint32 id, xmms_error_t *err)
Definition: medialib.c:1226
void xmms_object_emit_f(xmms_object_t *object, guint32 signalid, xmmsv_type_t type,...)
Emits a signal on the current object.
Definition: object.c:257
#define XMMS_MEDIALIB_RETRV_PROPERTY_SQL
Retrieve a property from an entry.
Definition: medialib.c:526
xmms_medialib_t * xmms_medialib_init(xmms_playlist_t *playlist)
Initialize the medialib and open the database file.
Definition: medialib.c:327
xmms_medialib_entry_t xmms_medialib_entry_new_encoded(xmms_medialib_session_t *session, const char *url, xmms_error_t *error)
Definition: medialib.c:996
#define XMMS_MEDIALIB_ENTRY_PROPERTY_LMOD
Definition: xmms_medialib.h:44
#define XMMS_MEDIALIB_ENTRY_PROPERTY_ADDED
Definition: xmms_medialib.h:54
gboolean xmms_medialib_check_id(xmms_medialib_entry_t entry)
Definition: medialib.c:1444
void xmms_mediainfo_reader_wakeup(xmms_mediainfo_reader_t *mr)
Wake the reader thread and start process the entries.
Definition: mediainfo.c:117
struct xmms_playlist_St xmms_playlist_t
Definition: xmms_playlist.h:41
#define XMMS_MEDIALIB_ENTRY_PROPERTY_STATUS
Definition: xmms_medialib.h:67
void xmms_playlist_insert_entry(xmms_playlist_t *playlist, const gchar *plname, guint32 pos, xmms_medialib_entry_t file, xmms_error_t *err)
Insert an entry at a given position in the playlist without validating it.
Definition: playlist.c:991
#define GOODCHAR(a)
Definition: medialib.c:1554
GList * xmms_xform_browse(const gchar *url, xmms_error_t *error)
Definition: xform.c:279
gboolean xmms_playlist_remove_by_entry(xmms_playlist_t *playlist, xmms_medialib_entry_t entry)
Remove all additions of entry in the playlist.
Definition: playlist.c:778
xmms_mediainfo_reader_t * xmms_playlist_mediainfo_reader_get(xmms_playlist_t *playlist)
returns pointer to mediainfo reader.
Definition: playlist.c:1633
xmmsv_type_t xmmsv_get_type(const xmmsv_t *val)
Get the type of the value.
Definition: value.c:384
xmmsv_t * xmmsv_new_int(int32_t i)
Allocates a new integer xmmsv_t.
Definition: value.c:159
void xmms_ipc_object_unregister(xmms_ipc_objects_t objectid)
Remove a object from the IPC core.
Definition: ipc.c:769
XMMS_CMD_DEFINE4(set_property_str, xmms_medialib_client_property_set_str, xmms_medialib_t *, NONE, INT32, STRING, STRING, STRING)
#define XMMS_MEDIALIB_ENTRY_PROPERTY_LASTSTARTED
Definition: xmms_medialib.h:56
xmmsv_t * xmmsv_ref(xmmsv_t *val)
References the xmmsv_t.
Definition: value.c:286
struct xmms_medialib_session_St xmms_medialib_session_t
Definition: xmms_medialib.h:87
XMMS_CMD_DEFINE3(remove_property, xmms_medialib_client_property_remove, xmms_medialib_t *, NONE, INT32, STRING, STRING)
#define xmms_log_info(fmt,...)
Definition: xmms_log.h:34
void xmms_medialib_entry_send_added(xmms_medialib_entry_t entry)
Trigger an added siginal to the client.
Definition: medialib.c:733
void xmms_medialib_entry_send_update(xmms_medialib_entry_t entry)
Trigger a update signal to the client.
Definition: medialib.c:719
#define xmms_object_ref(obj)
Definition: xmms_object.h:187
#define XMMS_DBG(fmt,...)
Definition: xmms_log.h:32
#define xmms_medialib_begin_write()
#define xmms_medialib_begin()
struct xmms_medialib_St xmms_medialib_t
Definition: xmms_medialib.h:27
void xmms_ipc_broadcast_unregister(xmms_ipc_signals_t signalid)
Unregister a broadcast signal.
Definition: ipc.c:709
gboolean xmms_medialib_entry_property_set_str_source(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property, const gchar *value, guint32 source)
Definition: medialib.c:677
int xmmsv_get_string(const xmmsv_t *val, const char **r)
Retrieves a string from the value.
Definition: value.c:855
gboolean xmms_medialib_decode_url(char *url)
Definition: medialib.c:1517
#define xmms_object_new(objtype, destroyfunc)
Definition: xmms_object.h:199
#define xmms_medialib_entry_status_set(session, e, st)
G_BEGIN_DECLS struct xmms_error_St xmms_error_t
xmms_config_property_t * xmms_config_property_register(const gchar *path, const gchar *default_value, xmms_object_handler_t cb, gpointer userdata)
Register a new config property.
Definition: config.c:337
guint xmms_medialib_num_not_resolved(xmms_medialib_session_t *session)
Definition: medialib.c:1500
gboolean xmms_sqlite_exec(sqlite3 *sql, const char *query,...)
A query that can&#39;t retrieve results.
Definition: sqlite.c:564
gboolean xmms_medialib_entry_property_set_int(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property, gint value)
Set a entry property to a new value, overwriting the old value.
Definition: medialib.c:616
#define xmms_log_fatal(fmt,...)
Definition: xmms_log.h:33
void xmms_ipc_object_register(xmms_ipc_objects_t objectid, xmms_object_t *object)
Register a object to the IPC core.
Definition: ipc.c:758
void xmms_medialib_entry_remove(xmms_medialib_entry_t entry)
Remove a medialib entry from the database.
Definition: medialib.c:754
xmms_medialib_entry_t xmms_medialib_entry_new(xmms_medialib_session_t *session, const char *url, xmms_error_t *error)
Welcome to a function that should be called something else.
Definition: medialib.c:1052
#define XMMS_BUILD_PATH(...)
Definition: xmms_utils.h:4
#define XMMS_MEDIALIB_ENTRY_PROPERTY_URL
Definition: xmms_medialib.h:29
gchar * xmms_medialib_entry_property_get_str(xmms_medialib_session_t *session, xmms_medialib_entry_t entry, const gchar *property)
Retrieve a property from an entry.
Definition: medialib.c:561
void xmms_medialib_end(xmms_medialib_session_t *session)
Definition: medialib.c:470
void xmms_sqlite_close(sqlite3 *sql)
Close database and free all resources used.
Definition: sqlite.c:793
#define XMMS_MEDIALIB_ENTRY_PROPERTY_ID
Definition: xmms_medialib.h:28
xmms_medialib_entry_t xmms_medialib_entry_not_resolved_get(xmms_medialib_session_t *session)
Definition: medialib.c:1482