Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
|
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 }