Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
playlist-utils.c
Go to the documentation of this file.
00001 /*
00002  * playlist-utils.c
00003  * Copyright 2009-2011 John Lindgren
00004  *
00005  * This file is part of Audacious.
00006  *
00007  * Audacious is free software: you can redistribute it and/or modify it under
00008  * the terms of the GNU General Public License as published by the Free Software
00009  * Foundation, version 2 or version 3 of the License.
00010  *
00011  * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY
00012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
00013  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License along with
00016  * Audacious. If not, see <http://www.gnu.org/licenses/>.
00017  *
00018  * The Audacious team does not consider modular code linking to Audacious or
00019  * using our public API to be a derived work.
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 /* Fix me:  This considers empty fields as duplicates. */
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         /* vfs_file_test() only works for file:// URIs currently */
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     /* old (v3.1 and earlier) naming scheme */
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     /* unique ID-based naming scheme */
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     /* save playlists */
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     /* clean up deleted playlists and files from old naming scheme */
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     /* on exit, save resume time if resume feature is enabled */
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 }