00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include <dirent.h>
00023 #include <glib.h>
00024 #include <regex.h>
00025 #include <stdio.h>
00026 #include <stdlib.h>
00027 #include <string.h>
00028
00029 #include <libaudcore/audstrings.h>
00030 #include <libaudcore/hook.h>
00031
00032 #include "misc.h"
00033 #include "playlist.h"
00034
00035 static const char * get_basename (const char * filename)
00036 {
00037 const char * slash = strrchr (filename, '/');
00038
00039 return (slash == NULL) ? filename : slash + 1;
00040 }
00041
00042 static int filename_compare_basename (const char * a, const char * b)
00043 {
00044 return string_compare_encoded (get_basename (a), get_basename (b));
00045 }
00046
00047 static int tuple_compare_string (const Tuple * a, const Tuple * b, int field)
00048 {
00049 char * string_a = tuple_get_str (a, field, NULL);
00050 char * string_b = tuple_get_str (b, field, NULL);
00051 int ret;
00052
00053 if (string_a == NULL)
00054 ret = (string_b == NULL) ? 0 : -1;
00055 else if (string_b == NULL)
00056 ret = 1;
00057 else
00058 ret = string_compare (string_a, string_b);
00059
00060 str_unref (string_a);
00061 str_unref (string_b);
00062 return ret;
00063 }
00064
00065 static int tuple_compare_int (const Tuple * a, const Tuple * b, int field)
00066 {
00067 if (tuple_get_value_type (a, field, NULL) != TUPLE_INT)
00068 return (tuple_get_value_type (b, field, NULL) != TUPLE_INT) ? 0 : -1;
00069 if (tuple_get_value_type (b, field, NULL) != TUPLE_INT)
00070 return 1;
00071
00072 int int_a = tuple_get_int (a, field, NULL);
00073 int int_b = tuple_get_int (b, field, NULL);
00074
00075 return (int_a < int_b) ? -1 : (int_a > int_b);
00076 }
00077
00078 static int tuple_compare_title (const Tuple * a, const Tuple * b)
00079 {
00080 return tuple_compare_string (a, b, FIELD_TITLE);
00081 }
00082
00083 static int tuple_compare_album (const Tuple * a, const Tuple * b)
00084 {
00085 return tuple_compare_string (a, b, FIELD_ALBUM);
00086 }
00087
00088 static int tuple_compare_artist (const Tuple * a, const Tuple * b)
00089 {
00090 return tuple_compare_string (a, b, FIELD_ARTIST);
00091 }
00092
00093 static int tuple_compare_date (const Tuple * a, const Tuple * b)
00094 {
00095 return tuple_compare_int (a, b, FIELD_YEAR);
00096 }
00097
00098 static int tuple_compare_track (const Tuple * a, const Tuple * b)
00099 {
00100 return tuple_compare_int (a, b, FIELD_TRACK_NUMBER);
00101 }
00102
00103 static const PlaylistStringCompareFunc filename_comparisons[] = {
00104 [PLAYLIST_SORT_PATH] = string_compare_encoded,
00105 [PLAYLIST_SORT_FILENAME] = filename_compare_basename,
00106 [PLAYLIST_SORT_TITLE] = NULL,
00107 [PLAYLIST_SORT_ALBUM] = NULL,
00108 [PLAYLIST_SORT_ARTIST] = NULL,
00109 [PLAYLIST_SORT_DATE] = NULL,
00110 [PLAYLIST_SORT_TRACK] = NULL,
00111 [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
00112
00113 static const PlaylistTupleCompareFunc tuple_comparisons[] = {
00114 [PLAYLIST_SORT_PATH] = NULL,
00115 [PLAYLIST_SORT_FILENAME] = NULL,
00116 [PLAYLIST_SORT_TITLE] = tuple_compare_title,
00117 [PLAYLIST_SORT_ALBUM] = tuple_compare_album,
00118 [PLAYLIST_SORT_ARTIST] = tuple_compare_artist,
00119 [PLAYLIST_SORT_DATE] = tuple_compare_date,
00120 [PLAYLIST_SORT_TRACK] = tuple_compare_track,
00121 [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
00122
00123 static const PlaylistStringCompareFunc title_comparisons[] = {
00124 [PLAYLIST_SORT_PATH] = NULL,
00125 [PLAYLIST_SORT_FILENAME] = NULL,
00126 [PLAYLIST_SORT_TITLE] = NULL,
00127 [PLAYLIST_SORT_ALBUM] = NULL,
00128 [PLAYLIST_SORT_ARTIST] = NULL,
00129 [PLAYLIST_SORT_DATE] = NULL,
00130 [PLAYLIST_SORT_TRACK] = NULL,
00131 [PLAYLIST_SORT_FORMATTED_TITLE] = string_compare};
00132
00133 void playlist_sort_by_scheme (int playlist, int scheme)
00134 {
00135 if (filename_comparisons[scheme] != NULL)
00136 playlist_sort_by_filename (playlist, filename_comparisons[scheme]);
00137 else if (tuple_comparisons[scheme] != NULL)
00138 playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
00139 else if (title_comparisons[scheme] != NULL)
00140 playlist_sort_by_title (playlist, title_comparisons[scheme]);
00141 }
00142
00143 void playlist_sort_selected_by_scheme (int playlist, int scheme)
00144 {
00145 if (filename_comparisons[scheme] != NULL)
00146 playlist_sort_selected_by_filename (playlist,
00147 filename_comparisons[scheme]);
00148 else if (tuple_comparisons[scheme] != NULL)
00149 playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]);
00150 else if (title_comparisons[scheme] != NULL)
00151 playlist_sort_selected_by_title (playlist, title_comparisons[scheme]);
00152 }
00153
00154
00155 void playlist_remove_duplicates_by_scheme (int playlist, int scheme)
00156 {
00157 int entries = playlist_entry_count (playlist);
00158 int count;
00159
00160 if (entries < 1)
00161 return;
00162
00163 playlist_select_all (playlist, FALSE);
00164
00165 if (filename_comparisons[scheme] != NULL)
00166 {
00167 int (* compare) (const char * a, const char * b) =
00168 filename_comparisons[scheme];
00169
00170 playlist_sort_by_filename (playlist, compare);
00171 char * last = playlist_entry_get_filename (playlist, 0);
00172
00173 for (count = 1; count < entries; count ++)
00174 {
00175 char * current = playlist_entry_get_filename (playlist, count);
00176
00177 if (compare (last, current) == 0)
00178 playlist_entry_set_selected (playlist, count, TRUE);
00179
00180 str_unref (last);
00181 last = current;
00182 }
00183
00184 str_unref (last);
00185 }
00186 else if (tuple_comparisons[scheme] != NULL)
00187 {
00188 int (* compare) (const Tuple * a, const Tuple * b) =
00189 tuple_comparisons[scheme];
00190
00191 playlist_sort_by_tuple (playlist, compare);
00192 Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE);
00193
00194 for (count = 1; count < entries; count ++)
00195 {
00196 Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE);
00197
00198 if (last != NULL && current != NULL && compare (last, current) == 0)
00199 playlist_entry_set_selected (playlist, count, TRUE);
00200
00201 if (last)
00202 tuple_unref (last);
00203 last = current;
00204 }
00205
00206 if (last)
00207 tuple_unref (last);
00208 }
00209
00210 playlist_delete_selected (playlist);
00211 }
00212
00213 void playlist_remove_failed (int playlist)
00214 {
00215 int entries = playlist_entry_count (playlist);
00216 int count;
00217
00218 playlist_select_all (playlist, FALSE);
00219
00220 for (count = 0; count < entries; count ++)
00221 {
00222 char * filename = playlist_entry_get_filename (playlist, count);
00223
00224
00225 if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename,
00226 G_FILE_TEST_EXISTS))
00227 playlist_entry_set_selected (playlist, count, TRUE);
00228
00229 str_unref (filename);
00230 }
00231
00232 playlist_delete_selected (playlist);
00233 }
00234
00235 void playlist_select_by_patterns (int playlist, const Tuple * patterns)
00236 {
00237 const int fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST,
00238 FIELD_FILE_NAME};
00239
00240 int entries = playlist_entry_count (playlist);
00241 int field, entry;
00242
00243 playlist_select_all (playlist, TRUE);
00244
00245 for (field = 0; field < G_N_ELEMENTS (fields); field ++)
00246 {
00247 char * pattern = tuple_get_str (patterns, fields[field], NULL);
00248 regex_t regex;
00249
00250 if (! pattern || ! pattern[0] || regcomp (& regex, pattern, REG_ICASE))
00251 {
00252 str_unref (pattern);
00253 continue;
00254 }
00255
00256 for (entry = 0; entry < entries; entry ++)
00257 {
00258 if (! playlist_entry_get_selected (playlist, entry))
00259 continue;
00260
00261 Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE);
00262 char * string = tuple ? tuple_get_str (tuple, fields[field], NULL) : NULL;
00263
00264 if (! string || regexec (& regex, string, 0, NULL, 0))
00265 playlist_entry_set_selected (playlist, entry, FALSE);
00266
00267 str_unref (string);
00268 if (tuple)
00269 tuple_unref (tuple);
00270 }
00271
00272 regfree (& regex);
00273 str_unref (pattern);
00274 }
00275 }
00276
00277 static char * make_playlist_path (int playlist)
00278 {
00279 if (! playlist)
00280 return g_strdup_printf ("%s/playlist.xspf", get_path (AUD_PATH_USER_DIR));
00281
00282 return g_strdup_printf ("%s/playlist_%02d.xspf",
00283 get_path (AUD_PATH_PLAYLISTS_DIR), 1 + playlist);
00284 }
00285
00286 static void load_playlists_real (void)
00287 {
00288
00289
00290 int count;
00291 for (count = 0; ; count ++)
00292 {
00293 char * path = make_playlist_path (count);
00294
00295 if (! g_file_test (path, G_FILE_TEST_EXISTS))
00296 {
00297 g_free (path);
00298 break;
00299 }
00300
00301 char * uri = filename_to_uri (path);
00302
00303 playlist_insert (count);
00304 playlist_insert_playlist_raw (count, 0, uri);
00305 playlist_set_modified (count, TRUE);
00306
00307 g_free (path);
00308 g_free (uri);
00309 }
00310
00311
00312
00313 char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
00314 char * order_string;
00315 g_file_get_contents (order_path, & order_string, NULL, NULL);
00316 g_free (order_path);
00317
00318 if (! order_string)
00319 goto DONE;
00320
00321 char * * order = g_strsplit (order_string, " ", -1);
00322 g_free (order_string);
00323
00324 for (int i = 0; order[i]; i ++)
00325 {
00326 char * path = g_strdup_printf ("%s/%s.audpl", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
00327
00328 if (! g_file_test (path, G_FILE_TEST_EXISTS))
00329 {
00330 g_free (path);
00331 path = g_strdup_printf ("%s/%s.xspf", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
00332 }
00333
00334 char * uri = filename_to_uri (path);
00335
00336 playlist_insert_with_id (count + i, atoi (order[i]));
00337 playlist_insert_playlist_raw (count + i, 0, uri);
00338 playlist_set_modified (count + i, FALSE);
00339
00340 if (g_str_has_suffix (path, ".xspf"))
00341 playlist_set_modified (count + i, TRUE);
00342
00343 g_free (path);
00344 g_free (uri);
00345 }
00346
00347 g_strfreev (order);
00348
00349 DONE:
00350 if (! playlist_count ())
00351 playlist_insert (0);
00352
00353 playlist_set_active (0);
00354 }
00355
00356 static void save_playlists_real (void)
00357 {
00358 int lists = playlist_count ();
00359 const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR);
00360
00361
00362
00363 char * * order = g_malloc (sizeof (char *) * (lists + 1));
00364 GHashTable * saved = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
00365
00366 for (int i = 0; i < lists; i ++)
00367 {
00368 int id = playlist_get_unique_id (i);
00369 order[i] = g_strdup_printf ("%d", id);
00370
00371 if (playlist_get_modified (i))
00372 {
00373 char * path = g_strdup_printf ("%s/%d.audpl", folder, id);
00374 char * uri = filename_to_uri (path);
00375
00376 playlist_save (i, uri);
00377 playlist_set_modified (i, FALSE);
00378
00379 g_free (path);
00380 g_free (uri);
00381 }
00382
00383 g_hash_table_insert (saved, g_strdup_printf ("%d.audpl", id), NULL);
00384 }
00385
00386 order[lists] = NULL;
00387 char * order_string = g_strjoinv (" ", order);
00388 g_strfreev (order);
00389
00390 GError * error = NULL;
00391 char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
00392
00393 char * old_order_string;
00394 g_file_get_contents (order_path, & old_order_string, NULL, NULL);
00395
00396 if (! old_order_string || strcmp (old_order_string, order_string))
00397 {
00398 if (! g_file_set_contents (order_path, order_string, -1, & error))
00399 {
00400 fprintf (stderr, "Cannot write to %s: %s\n", order_path, error->message);
00401 g_error_free (error);
00402 }
00403 }
00404
00405 g_free (order_string);
00406 g_free (order_path);
00407 g_free (old_order_string);
00408
00409
00410
00411 char * path = make_playlist_path (0);
00412 remove (path);
00413 g_free (path);
00414
00415 DIR * dir = opendir (folder);
00416 if (! dir)
00417 goto DONE;
00418
00419 struct dirent * entry;
00420 while ((entry = readdir (dir)))
00421 {
00422 if (! g_str_has_suffix (entry->d_name, ".audpl")
00423 && ! g_str_has_suffix (entry->d_name, ".xspf"))
00424 continue;
00425
00426 if (! g_hash_table_lookup_extended (saved, entry->d_name, NULL, NULL))
00427 {
00428 char * path = g_strdup_printf ("%s/%s", folder, entry->d_name);
00429 remove (path);
00430 g_free (path);
00431 }
00432 }
00433
00434 closedir (dir);
00435
00436 DONE:
00437 g_hash_table_destroy (saved);
00438 }
00439
00440 static bool_t hooks_added, state_changed;
00441
00442 static void update_cb (void * data, void * user)
00443 {
00444 if (GPOINTER_TO_INT (data) < PLAYLIST_UPDATE_METADATA)
00445 return;
00446
00447 state_changed = TRUE;
00448 }
00449
00450 static void state_cb (void * data, void * user)
00451 {
00452 state_changed = TRUE;
00453 }
00454
00455 void load_playlists (void)
00456 {
00457 load_playlists_real ();
00458 playlist_load_state ();
00459
00460 state_changed = FALSE;
00461
00462 if (! hooks_added)
00463 {
00464 hook_associate ("playlist update", update_cb, NULL);
00465 hook_associate ("playlist activate", state_cb, NULL);
00466 hook_associate ("playlist position", state_cb, NULL);
00467
00468 hooks_added = TRUE;
00469 }
00470 }
00471
00472 void save_playlists (bool_t exiting)
00473 {
00474 save_playlists_real ();
00475
00476
00477 if (state_changed || (exiting && get_bool (NULL, "resume_playback_on_startup")))
00478 {
00479 playlist_save_state ();
00480 state_changed = FALSE;
00481 }
00482
00483 if (exiting && hooks_added)
00484 {
00485 hook_dissociate ("playlist update", update_cb);
00486 hook_dissociate ("playlist activate", state_cb);
00487 hook_dissociate ("playlist position", state_cb);
00488
00489 hooks_added = FALSE;
00490 }
00491 }