17 #include "xmms_configuration.h"
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);
84 struct xmms_medialib_St {
96 struct xmms_medialib_session_St {
123 static const char source_pref[] =
"server:client/*:plugin/id3v2:plugin/*";
133 static GMutex *global_medialib_session_mutex;
135 static GMutex *xmms_medialib_debug_mutex;
136 static GHashTable *xmms_medialib_debug_hash;
142 if (global_medialib_session) {
144 g_free (global_medialib_session);
146 g_mutex_free (mlib->source_lock);
147 g_hash_table_destroy (mlib->sources);
148 g_mutex_free (global_medialib_session_mutex);
153 #define XMMS_MEDIALIB_SOURCE_SERVER "server"
154 #define XMMS_MEDIALIB_SOURCE_SERVER_ID 1
157 source_match_pattern (
const gchar *source,
const gchar *pattern,
163 if (pattern_len > 0 && pattern[pattern_len - 1] ==
'*') {
167 if (pattern_len == 1) {
174 return !g_ascii_strncasecmp (source, pattern, pattern_len - 1);
178 return !g_ascii_strncasecmp (pattern, source, pattern_len);
182 xmms_find_match_index (gint source,
const gchar *pref,
xmms_medialib_t *mlib)
184 gchar *source_name, *colon;
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);
194 colon = strchr (pref,
':');
197 len = colon ? colon - pref : strlen (pref);
200 if (source_match_pattern (source_name, pref, len)) {
217 xmms_sqlite_source_pref_binary (sqlite3_context *context,
int args,
224 mlib = sqlite3_user_data (context);
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);
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);
237 source = sqlite3_value_int (val[0]);
238 pref = (
const gchar *) sqlite3_value_text (val[1]);
240 sqlite3_result_int (context, xmms_find_match_index (source, pref, mlib));
244 xmms_sqlite_source_pref_unary (sqlite3_context *context,
int args,
250 mlib = sqlite3_user_data (context);
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);
258 source = sqlite3_value_int (val[0]);
260 sqlite3_result_int (context,
261 xmms_find_match_index (source, source_pref, mlib));
265 add_to_source (
void *hash,
int columns,
char **vals,
char **cols)
267 int source = strtol (vals[0], NULL, 10);
268 g_hash_table_insert ((GHashTable*)hash, GINT_TO_POINTER (source), g_strdup (vals[1]));
277 g_return_val_if_fail (source, 0);
280 "SELECT id FROM Sources WHERE source=%Q",
284 "INSERT INTO Sources (source) VALUES (%Q)", source);
286 "SELECT id FROM Sources WHERE source=%Q",
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);
299 xmms_medialib_session_new (
const char *file,
int line)
304 session->medialib = medialib;
305 session->file = file;
306 session->line = line;
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);
334 medialib->playlist = playlist;
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;
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 (
"********************************************************************");
395 global_medialib_session = xmms_medialib_session_new (
"global", 0);
398 global_medialib_session_mutex = g_mutex_new ();
403 medialib->source_lock = g_mutex_new ();
404 medialib->sources = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
407 sqlite3_exec (session->sql,
"SELECT id, source FROM Sources",
408 add_to_source, medialib->sources, NULL);
415 "/mind.in.a.box-lament_snipplet.ogg",
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);
440 xmms_log_fatal (
"Medialib session begun recursivly at %s:%d, after %s", file, line, r);
442 g_hash_table_insert (xmms_medialib_debug_hash, me,
443 g_strdup_printf (
"%s:%d", file, line));
445 g_mutex_unlock (xmms_medialib_debug_mutex);
447 if (global_medialib_session) {
449 g_mutex_lock (global_medialib_session_mutex);
450 return global_medialib_session;
453 session = xmms_medialib_session_new (file, line);
455 session->write = write;
464 session->next_id = -1;
472 g_return_if_fail (session);
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);
481 if (session->write) {
485 if (session == global_medialib_session) {
486 g_mutex_unlock (global_medialib_session_mutex);
496 xmms_medialib_string_cb (
xmmsv_t **row, gpointer udata)
503 *str = g_strdup (buf);
505 XMMS_DBG (
"Expected string but got something else!");
511 xmms_medialib_value_cb (
xmmsv_t **row, gpointer udata)
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"
531 const gchar *property)
535 g_return_val_if_fail (property, NULL);
536 g_return_val_if_fail (session, NULL);
543 property, entry, source_pref);
563 const gchar *property)
567 g_return_val_if_fail (property, NULL);
568 g_return_val_if_fail (session, NULL);
572 property, entry, source_pref);
590 const gchar *property)
594 g_return_val_if_fail (property, -1);
595 g_return_val_if_fail (session, -1);
599 property, entry, source_pref);
618 const gchar *property, gint value)
629 const gchar *property, gint value,
634 g_return_val_if_fail (property, FALSE);
635 g_return_val_if_fail (session, FALSE);
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);
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);
668 const gchar *property,
const gchar *value)
679 const gchar *property,
const gchar *value,
684 g_return_val_if_fail (property, FALSE);
685 g_return_val_if_fail (session, FALSE);
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);
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);
700 "INSERT OR REPLACE INTO Media "
701 "(id, value, intval, key, source) VALUES "
702 "(%d, %Q, NULL, %Q, %d)",
703 entry, value, property, source);
770 const gchar *playlist,
779 if (entry && playlist != NULL) {
782 playlist, pos, entry, error);
785 playlist, entry, error);
791 cmp_val (gconstpointer a, gconstpointer b)
794 const gchar *s1, *s2;
805 return strcmp (s1, s2);
811 process_dir (
const gchar *directory,
822 list = g_list_sort (list, cmp_val);
833 process_dir (str, ret, error);
835 *ret = g_list_prepend (*ret, g_strdup (str));
839 list = g_list_delete_link (list, list);
850 "DELETE FROM Media WHERE id=%d AND source=%d "
851 "AND key NOT IN (%Q, %Q, %Q, %Q, %Q)",
861 "DELETE FROM Media WHERE id=%d AND source IN "
862 "(SELECT id FROM Sources WHERE source LIKE 'plugin/%%' "
863 "AND source != 'plugin/playlist')",
878 "UPDATE Media SET value = '%d', intval = %d "
879 "WHERE key='%s' AND id=%d",
885 "UPDATE Media SET value = '%d', intval = %d "
918 gint32 pos,
const gchar *path,
922 GList *first, *list = NULL, *n;
924 g_return_if_fail (medialib);
925 g_return_if_fail (path);
931 first = list = g_list_alloc ();
933 process_dir (path, &list, error);
935 XMMS_DBG (
"taking the transaction!");
943 for (n = first->prev; n; n = g_list_previous (n)) {
944 process_file (session, playlist, pos, n->data, error);
957 xmms_medialib_client_path_import (
xmms_medialib_t *medialib,
const gchar *path,
975 "INSERT INTO Media (id, key, value, source) VALUES "
976 "(%d, '%s', %Q, %d)",
980 "Sql error/corruption inserting url");
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);
1010 "SELECT id AS value FROM Media "
1011 "WHERE key='%s' AND value=%Q AND source=%d",
1018 if (session->next_id <= 0 &&
1020 "SELECT IFNULL(MAX (id),0)+1 FROM Media")) {
1022 "SQL error/corruption selecting max(id)");
1026 ret = session->next_id++;
1028 if (!xmms_medialib_entry_new_insert (session, ret, url, error))
1069 xmms_medialib_client_entry_get_id (
xmms_medialib_t *medialib,
const gchar *url,
1076 "SELECT id AS value FROM Media WHERE key='%s' AND value=%Q AND source=%d",
1085 xmms_medialib_tree_add_tuple (GTree *tree,
const char *key,
1086 const char *source,
xmmsv_t *value)
1091 keytreeval = (
xmmsv_t *) g_tree_lookup (tree, key);
1094 g_tree_insert (tree, g_strdup (key), keytreeval);
1102 xmms_medialib_list_cb (
xmmsv_t **row, gpointer udata)
1104 GList **list = (GList**)udata;
1107 *list = g_list_prepend (*list,
xmmsv_ref (row[0]));
1110 *list = g_list_prepend (*list,
xmmsv_ref (row[1]));
1113 *list = g_list_prepend (*list,
xmmsv_ref (row[2]));
1119 xmms_medialib_tree_cb (
xmmsv_t **row, gpointer udata)
1121 const char *key, *source;
1123 GTree **tree = (GTree**)udata;
1129 xmms_medialib_tree_add_tuple (*tree, key, source, value);
1151 g_return_val_if_fail (session, NULL);
1152 g_return_val_if_fail (entry, NULL);
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 "
1174 return g_list_reverse (ret);
1195 g_return_val_if_fail (session, NULL);
1196 g_return_val_if_fail (entry, NULL);
1198 if (!xmms_medialib_check_id_in_session (entry, session)) {
1202 ret = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
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 "
1218 xmms_medialib_tree_add_tuple (ret,
"id",
"server", v_entry);
1235 ret = xmms_medialib_entry_to_list (session,
id);
1240 "Could not retrieve info for that entry!");
1257 ret = xmms_medialib_entry_to_tree (session,
id);
1262 "Could not retrieve info for that entry!");
1270 select_callback (
xmmsv_t *row, gpointer udata)
1272 GList **l = (GList **) udata;
1273 *l = g_list_prepend (*l, row);
1287 xmms_medialib_client_add_entry (
xmms_medialib_t *medialib,
const gchar *url,
1293 g_return_if_fail (medialib);
1294 g_return_if_fail (url);
1312 xmms_medialib_client_move_entry (
xmms_medialib_t *medialib, gint32 entry,
1334 xmms_medialib_client_property_set_str (
xmms_medialib_t *medialib, gint32 entry,
1335 const gchar *source,
const gchar *key,
1341 if (g_ascii_strcasecmp (source,
"server") == 0) {
1343 "Can't write to source server!");
1358 xmms_medialib_client_property_set_int (
xmms_medialib_t *medialib, gint32 entry,
1359 const gchar *source,
const gchar *key,
1365 if (g_ascii_strcasecmp (source,
"server") == 0) {
1367 "Can't write to source server!");
1381 xmms_medialib_property_remove (
xmms_medialib_t *medialib, gint32 entry,
1382 const gchar *source,
const gchar *key,
1390 "DELETE FROM Media WHERE source=%d AND key='%s' AND "
1392 sourceid, key, entry);
1399 xmms_medialib_client_property_remove (
xmms_medialib_t *medialib, gint32 entry,
1400 const gchar *source,
const gchar *key,
1403 if (g_ascii_strcasecmp (source,
"server") == 0) {
1405 "Can't remove properties set by the server!");
1409 return xmms_medialib_property_remove (medialib, entry, source, key, error);
1428 g_return_val_if_fail (query, 0);
1429 g_return_val_if_fail (session, 0);
1432 (
void *)&res, error,
"%s", query);
1434 return ret ? g_list_reverse (res) : NULL;
1450 ret = xmms_medialib_check_id_in_session (entry, session);
1467 "SELECT COUNT(id) FROM Media WHERE id=%d",
1486 g_return_val_if_fail (session, 0);
1489 "SELECT id FROM Media WHERE key='%s' "
1490 "AND source=%d AND intval IN (%d, %d) LIMIT 1",
1503 g_return_val_if_fail (session, 0);
1506 "SELECT COUNT(id) AS value FROM Media WHERE "
1507 "key='%s' AND intval IN (%d, %d) AND source=%d",
1521 g_return_val_if_fail (url, TRUE);
1524 unsigned char chr = url[i++];
1528 }
else if (chr ==
'%') {
1540 url[j++] = strtoul (ts, &t, 16);
1554 #define GOODCHAR(a) ((((a) >= 'a') && ((a) <= 'z')) || \
1555 (((a) >= 'A') && ((a) <= 'Z')) || \
1556 (((a) >= '0') && ((a) <= '9')) || \
1567 static gchar hex[16] =
"0123456789abcdef";
1571 res = g_malloc (strlen (path) * 3 + 1);
1576 guchar chr = path[i++];
1579 }
else if (chr ==
' ') {
1583 res[j++] = hex[((chr & 0xf0) >> 4)];
1584 res[j++] = hex[(chr & 0x0f)];
#define XMMS_CMD_FUNC(cmdid)
xmmsv_t * xmmsv_new_dict(void)
Allocates a new dict xmmsv_t.
sqlite3 * xmms_sqlite_open()
Open a database or create a new one.
gboolean xmms_sqlite_create(gboolean *create)
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,...)
void xmmsv_unref(xmmsv_t *val)
Decreases the references for the xmmsv_t When the number of references reaches 0 it will be freed...
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.
#define xmms_object_unref(obj)
int xmmsv_dict_entry_get_string(xmmsv_t *val, const char *key, const char **r)
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.
void xmms_ipc_broadcast_register(xmms_object_t *object, xmms_ipc_signals_t signalid)
Register a broadcast signal.
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.
xmmsv_t * xmmsv_new_string(const char *s)
Allocates a new string xmmsv_t.
#define XMMS_CMD_DEFINE4(cmdid, realfunc, argtype0, _rettype, argtype1, argtype2, argtype3, argtype4)
gboolean xmms_sqlite_query_int(sqlite3 *sql, gint32 *out, const gchar *query,...)
#define xmms_log_error(fmt,...)
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.
void xmms_object_emit_f(xmms_object_t *object, guint32 signalid, xmmsv_type_t type,...)
Emits a signal on the current object.
struct xmms_playlist_St xmms_playlist_t
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.
gboolean xmms_playlist_remove_by_entry(xmms_playlist_t *playlist, xmms_medialib_entry_t entry)
Remove all additions of entry in the playlist.
#define XMMS_CMD_DEFINE3(cmdid, realfunc, argtype0, _rettype, argtype1, argtype2, argtype3)
xmms_mediainfo_reader_t * xmms_playlist_mediainfo_reader_get(xmms_playlist_t *playlist)
returns pointer to mediainfo reader.
xmmsv_type_t xmmsv_get_type(const xmmsv_t *val)
Get the type of the value.
xmmsv_t * xmmsv_new_int(int32_t i)
Allocates a new integer xmmsv_t.
#define XMMS_CMD_DEFINE(cmdid, realfunc, argtype0, _rettype, argtype1, argtype2)
void xmms_ipc_object_unregister(xmms_ipc_objects_t objectid)
Remove a object from the IPC core.
xmmsv_t * xmmsv_ref(xmmsv_t *val)
References the xmmsv_t.
#define xmms_log_info(fmt,...)
#define xmms_object_ref(obj)
#define XMMS_DBG(fmt,...)
void xmms_ipc_broadcast_unregister(xmms_ipc_signals_t signalid)
Unregister a broadcast signal.
int xmmsv_get_string(const xmmsv_t *val, const char **r)
Retrieves a string from the value.
#define xmms_object_new(objtype, destroyfunc)
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.
gboolean xmms_sqlite_exec(sqlite3 *sql, const char *query,...)
A query that can't retrieve results.
#define xmms_log_fatal(fmt,...)
void xmms_ipc_object_register(xmms_ipc_objects_t objectid, xmms_object_t *object)
Register a object to the IPC core.
#define XMMS_BUILD_PATH(...)
void xmms_sqlite_close(sqlite3 *sql)
Close database and free all resources used.