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-2010 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 <glib.h>
00023 #include <regex.h>
00024 #include <string.h>
00025 
00026 #include <libaudcore/audstrings.h>
00027 
00028 #include "audconfig.h"
00029 #include "misc.h"
00030 #include "playlist.h"
00031 #include "playlist-utils.h"
00032 
00033 static const gchar * aud_titlestring_presets[] =
00034 {
00035     "${title}",
00036     "${?artist:${artist} - }${title}",
00037     "${?artist:${artist} - }${?album:${album} - }${title}",
00038     "${?artist:${artist} - }${?album:${album} - }"
00039      "${?track-number:${track-number}. }${title}",
00040     "${?artist:${artist} }${?album:[ ${album} ] }${?artist:- }"
00041      "${?track-number:${track-number}. }${title}",
00042     "${?album:${album} - }${title}",
00043 };
00044 
00045 const gint n_titlestring_presets = G_N_ELEMENTS (aud_titlestring_presets);
00046 
00047 static const gchar * get_basename (const gchar * filename)
00048 {
00049     const gchar * slash = strrchr (filename, '/');
00050 
00051     return (slash == NULL) ? filename : slash + 1;
00052 }
00053 
00054 static gint filename_compare_basename (const gchar * a, const gchar * b)
00055 {
00056     return string_compare_encoded (get_basename (a), get_basename (b));
00057 }
00058 
00059 static gint tuple_compare_string (const Tuple * a, const Tuple * b, gint field)
00060 {
00061     const gchar * string_a = tuple_get_string (a, field, NULL);
00062     const gchar * string_b = tuple_get_string (b, field, NULL);
00063 
00064     if (string_a == NULL)
00065         return (string_b == NULL) ? 0 : -1;
00066     if (string_b == NULL)
00067         return 1;
00068 
00069     return string_compare (string_a, string_b);
00070 }
00071 
00072 static gint tuple_compare_int (const Tuple * a, const Tuple * b, gint field)
00073 {
00074     if (tuple_get_value_type (a, field, NULL) != TUPLE_INT)
00075         return (tuple_get_value_type (b, field, NULL) != TUPLE_INT) ? 0 : -1;
00076     if (tuple_get_value_type (b, field, NULL) != TUPLE_INT)
00077         return 1;
00078 
00079     gint int_a = tuple_get_int (a, field, NULL);
00080     gint int_b = tuple_get_int (b, field, NULL);
00081 
00082     return (int_a < int_b) ? -1 : (int_a > int_b);
00083 }
00084 
00085 static gint tuple_compare_title (const Tuple * a, const Tuple * b)
00086 {
00087     return tuple_compare_string (a, b, FIELD_TITLE);
00088 }
00089 
00090 static gint tuple_compare_album (const Tuple * a, const Tuple * b)
00091 {
00092     return tuple_compare_string (a, b, FIELD_ALBUM);
00093 }
00094 
00095 static gint tuple_compare_artist (const Tuple * a, const Tuple * b)
00096 {
00097     return tuple_compare_string (a, b, FIELD_ARTIST);
00098 }
00099 
00100 static gint tuple_compare_date (const Tuple * a, const Tuple * b)
00101 {
00102     return tuple_compare_int (a, b, FIELD_YEAR);
00103 }
00104 
00105 static gint tuple_compare_track (const Tuple * a, const Tuple * b)
00106 {
00107     return tuple_compare_int (a, b, FIELD_TRACK_NUMBER);
00108 }
00109 
00110 static const PlaylistStringCompareFunc filename_comparisons[] = {
00111  [PLAYLIST_SORT_PATH] = string_compare_encoded,
00112  [PLAYLIST_SORT_FILENAME] = filename_compare_basename,
00113  [PLAYLIST_SORT_TITLE] = NULL,
00114  [PLAYLIST_SORT_ALBUM] = NULL,
00115  [PLAYLIST_SORT_ARTIST] = NULL,
00116  [PLAYLIST_SORT_DATE] = NULL,
00117  [PLAYLIST_SORT_TRACK] = NULL,
00118  [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
00119 
00120 static const PlaylistTupleCompareFunc tuple_comparisons[] = {
00121  [PLAYLIST_SORT_PATH] = NULL,
00122  [PLAYLIST_SORT_FILENAME] = NULL,
00123  [PLAYLIST_SORT_TITLE] = tuple_compare_title,
00124  [PLAYLIST_SORT_ALBUM] = tuple_compare_album,
00125  [PLAYLIST_SORT_ARTIST] = tuple_compare_artist,
00126  [PLAYLIST_SORT_DATE] = tuple_compare_date,
00127  [PLAYLIST_SORT_TRACK] = tuple_compare_track,
00128  [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
00129 
00130 static const PlaylistStringCompareFunc title_comparisons[] = {
00131  [PLAYLIST_SORT_PATH] = NULL,
00132  [PLAYLIST_SORT_FILENAME] = NULL,
00133  [PLAYLIST_SORT_TITLE] = NULL,
00134  [PLAYLIST_SORT_ALBUM] = NULL,
00135  [PLAYLIST_SORT_ARTIST] = NULL,
00136  [PLAYLIST_SORT_DATE] = NULL,
00137  [PLAYLIST_SORT_TRACK] = NULL,
00138  [PLAYLIST_SORT_FORMATTED_TITLE] = string_compare};
00139 
00140 const gchar * get_gentitle_format (void)
00141 {
00142     if (cfg.titlestring_preset >= 0 && cfg.titlestring_preset <
00143      n_titlestring_presets)
00144         return aud_titlestring_presets[cfg.titlestring_preset];
00145 
00146     return cfg.gentitle_format;
00147 }
00148 
00149 void playlist_sort_by_scheme (gint playlist, gint scheme)
00150 {
00151     if (filename_comparisons[scheme] != NULL)
00152         playlist_sort_by_filename (playlist, filename_comparisons[scheme]);
00153     else if (tuple_comparisons[scheme] != NULL)
00154         playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
00155     else if (title_comparisons[scheme] != NULL)
00156         playlist_sort_by_title (playlist, title_comparisons[scheme]);
00157 }
00158 
00159 void playlist_sort_selected_by_scheme (gint playlist, gint scheme)
00160 {
00161     if (filename_comparisons[scheme] != NULL)
00162         playlist_sort_selected_by_filename (playlist,
00163          filename_comparisons[scheme]);
00164     else if (tuple_comparisons[scheme] != NULL)
00165         playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]);
00166     else if (title_comparisons[scheme] != NULL)
00167         playlist_sort_selected_by_title (playlist, title_comparisons[scheme]);
00168 }
00169 
00170 /* Fix me:  This considers empty fields as duplicates. */
00171 void playlist_remove_duplicates_by_scheme (gint playlist, gint scheme)
00172 {
00173     gint entries = playlist_entry_count (playlist);
00174     gint count;
00175 
00176     if (entries < 1)
00177         return;
00178 
00179     playlist_select_all (playlist, FALSE);
00180 
00181     if (filename_comparisons[scheme] != NULL)
00182     {
00183         gint (* compare) (const gchar * a, const gchar * b) =
00184          filename_comparisons[scheme];
00185 
00186         playlist_sort_by_filename (playlist, compare);
00187         gchar * last = playlist_entry_get_filename (playlist, 0);
00188 
00189         for (count = 1; count < entries; count ++)
00190         {
00191             gchar * current = playlist_entry_get_filename (playlist, count);
00192 
00193             if (compare (last, current) == 0)
00194                 playlist_entry_set_selected (playlist, count, TRUE);
00195 
00196             g_free (last);
00197             last = current;
00198         }
00199 
00200         g_free (last);
00201     }
00202     else if (tuple_comparisons[scheme] != NULL)
00203     {
00204         gint (* compare) (const Tuple * a, const Tuple * b) =
00205          tuple_comparisons[scheme];
00206 
00207         playlist_sort_by_tuple (playlist, compare);
00208         Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE);
00209 
00210         for (count = 1; count < entries; count ++)
00211         {
00212             Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE);
00213 
00214             if (last != NULL && current != NULL && compare (last, current) == 0)
00215                 playlist_entry_set_selected (playlist, count, TRUE);
00216 
00217             if (last)
00218                 tuple_free (last);
00219             last = current;
00220         }
00221 
00222         if (last)
00223             tuple_free (last);
00224     }
00225 
00226     playlist_delete_selected (playlist);
00227 }
00228 
00229 void playlist_remove_failed (gint playlist)
00230 {
00231     gint entries = playlist_entry_count (playlist);
00232     gint count;
00233 
00234     playlist_select_all (playlist, FALSE);
00235 
00236     for (count = 0; count < entries; count ++)
00237     {
00238         gchar * filename = playlist_entry_get_filename (playlist, count);
00239 
00240         /* vfs_file_test() only works for file:// URIs currently */
00241         if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename,
00242          G_FILE_TEST_EXISTS))
00243             playlist_entry_set_selected (playlist, count, TRUE);
00244 
00245         g_free (filename);
00246     }
00247 
00248     playlist_delete_selected (playlist);
00249 }
00250 
00251 void playlist_select_by_patterns (gint playlist, const Tuple * patterns)
00252 {
00253     const gint fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST,
00254      FIELD_FILE_NAME};
00255 
00256     gint entries = playlist_entry_count (playlist);
00257     gint field, entry;
00258 
00259     playlist_select_all (playlist, TRUE);
00260 
00261     for (field = 0; field < G_N_ELEMENTS (fields); field ++)
00262     {
00263         const gchar * pattern = tuple_get_string ((Tuple *) patterns,
00264          fields[field], NULL);
00265         regex_t regex;
00266 
00267         if (pattern == NULL || pattern[0] == 0)
00268             continue;
00269 
00270         if (regcomp (& regex, pattern, REG_ICASE) != 0)
00271             continue;
00272 
00273         for (entry = 0; entry < entries; entry ++)
00274         {
00275             const gchar * string;
00276 
00277             if (! playlist_entry_get_selected (playlist, entry))
00278                 continue;
00279 
00280             Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE);
00281             if (! tuple)
00282                 goto NO_MATCH;
00283 
00284             string = tuple_get_string (tuple, fields[field], NULL);
00285             if (! string)
00286                 goto NO_MATCH;
00287 
00288             if (regexec (& regex, string, 0, NULL, 0) == 0)
00289             {
00290                 tuple_free (tuple);
00291                 continue;
00292             }
00293 
00294         NO_MATCH:
00295             playlist_entry_set_selected (playlist, entry, FALSE);
00296             if (tuple)
00297                 tuple_free (tuple);
00298         }
00299 
00300         regfree (& regex);
00301     }
00302 }
00303 
00304 /* The algorithm is a bit quirky for historical reasons. -jlindgren */
00305 static gchar * make_playlist_path (gint playlist)
00306 {
00307     if (! playlist)
00308         return g_strdup (get_path (AUD_PATH_PLAYLIST_FILE));
00309 
00310     return g_strdup_printf ("%s/playlist_%02d.xspf",
00311      get_path (AUD_PATH_PLAYLISTS_DIR), 1 + playlist);
00312 }
00313 
00314 void load_playlists (void)
00315 {
00316     gboolean done = FALSE;
00317     gint count;
00318 
00319     for (count = 0; ! done; count ++)
00320     {
00321         gchar * path = make_playlist_path (count);
00322 
00323         if (g_file_test (path, G_FILE_TEST_EXISTS))
00324         {
00325             gchar * uri = filename_to_uri (path);
00326 
00327             if (count)
00328                 playlist_insert (count);
00329 
00330             playlist_insert_playlist_raw (count, 0, uri);
00331             g_free (uri);
00332         }
00333         else
00334             done = TRUE;
00335 
00336         g_free (path);
00337     }
00338 
00339     playlist_load_state ();
00340 }
00341 
00342 void save_playlists (void)
00343 {
00344     gint playlists = playlist_count ();
00345     gboolean done = FALSE;
00346     gint count;
00347 
00348     for (count = 0; ! done; count ++)
00349     {
00350         gchar * path = make_playlist_path (count);
00351 
00352         if (count < playlists)
00353         {
00354             gchar * uri = filename_to_uri (path);
00355 
00356             playlist_save (count, uri);
00357             g_free (uri);
00358         }
00359         else if (g_file_test (path, G_FILE_TEST_EXISTS))
00360             remove (path);
00361         else
00362             done = TRUE;
00363 
00364         g_free (path);
00365     }
00366 
00367     playlist_save_state ();
00368 }