Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
playlist-new.c
Go to the documentation of this file.
00001 /*
00002  * playlist-new.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 <assert.h>
00023 #include <stdlib.h>
00024 #include <time.h>
00025 
00026 #include <glib.h>
00027 
00028 #include <libaudcore/audstrings.h>
00029 #include <libaudcore/hook.h>
00030 #include <libaudcore/stringpool.h>
00031 #include <libaudcore/tuple_formatter.h>
00032 
00033 #include "audconfig.h"
00034 #include "config.h"
00035 #include "i18n.h"
00036 #include "misc.h"
00037 #include "playback.h"
00038 #include "playlist.h"
00039 #include "playlist-utils.h"
00040 #include "plugins.h"
00041 #include "util.h"
00042 
00043 #define SCAN_THREADS 4
00044 #define STATE_FILE "playlist-state"
00045 
00046 #define DECLARE_PLAYLIST \
00047     Playlist * playlist
00048 
00049 #define DECLARE_PLAYLIST_ENTRY \
00050     Playlist * playlist; \
00051     Entry * entry
00052 
00053 #define LOOKUP_PLAYLIST do { \
00054     playlist = lookup_playlist (playlist_num); \
00055     g_return_if_fail (playlist != NULL); \
00056 } while (0)
00057 
00058 #define LOOKUP_PLAYLIST_RET(ret) do { \
00059     playlist = lookup_playlist (playlist_num); \
00060     g_return_val_if_fail (playlist != NULL, ret); \
00061 } while (0)
00062 
00063 #define LOOKUP_PLAYLIST_ENTRY do { \
00064     playlist = lookup_playlist (playlist_num); \
00065     g_return_if_fail (playlist != NULL); \
00066     entry = lookup_entry (playlist, entry_num); \
00067     g_return_if_fail (entry != NULL); \
00068 } while (0)
00069 
00070 #define LOOKUP_PLAYLIST_ENTRY_RET(ret) do { \
00071     playlist = lookup_playlist (playlist_num); \
00072     g_return_val_if_fail (playlist != NULL, ret); \
00073     entry = lookup_entry (playlist, entry_num); \
00074     g_return_val_if_fail (entry != NULL, ret); \
00075 } while (0)
00076 
00077 #define SELECTION_HAS_CHANGED(p, a, c) \
00078  queue_update (PLAYLIST_UPDATE_SELECTION, p, a, c)
00079 
00080 #define METADATA_WILL_CHANGE scan_stop ()
00081 
00082 #define METADATA_HAS_CHANGED(p, a, c) do { \
00083     scan_reset (); \
00084     queue_update (PLAYLIST_UPDATE_METADATA, p, a, c); \
00085 } while (0)
00086 
00087 #define PLAYLIST_WILL_CHANGE scan_stop ()
00088 
00089 #define PLAYLIST_HAS_CHANGED(p, a, c) do { \
00090     scan_reset (); \
00091     queue_update (PLAYLIST_UPDATE_STRUCTURE, p, a, c); \
00092 } while (0)
00093 
00094 typedef struct {
00095     gint number;
00096     gchar *filename;
00097     PluginHandle * decoder;
00098     Tuple *tuple;
00099     gchar * formatted, * title, * artist, * album;
00100     gint length;
00101     gboolean failed;
00102     gboolean selected;
00103     gint shuffle_num;
00104     gboolean queued;
00105     gboolean segmented;
00106     gint start;
00107     gint end;
00108 } Entry;
00109 
00110 typedef struct {
00111     gint number;
00112     gint unique_id;
00113     gchar *filename;
00114     gchar *title;
00115     struct index *entries;
00116     Entry * position;
00117     gint selected_count;
00118     gint last_shuffle_num;
00119     GList *queued;
00120     gint64 total_length;
00121     gint64 selected_length;
00122 } Playlist;
00123 
00124 static gint next_unique_id = 1000;
00125 
00126 static struct index * playlists = NULL;
00127 static Playlist * active_playlist = NULL;
00128 static Playlist * playing_playlist = NULL;
00129 
00130 static gint update_source, update_level;
00131 static Playlist * update_playlist;
00132 static gint unchanged_before, unchanged_after;
00133 
00134 static gint scan_source;
00135 static GMutex * scan_mutex;
00136 static GCond * scan_conds[SCAN_THREADS];
00137 static const gchar * scan_filenames[SCAN_THREADS];
00138 static PluginHandle * scan_decoders[SCAN_THREADS];
00139 static Tuple * scan_tuples[SCAN_THREADS];
00140 static gboolean scan_quit;
00141 static GThread * scan_threads[SCAN_THREADS];
00142 static gint scan_positions[SCAN_THREADS];
00143 
00144 static void * scanner (void * unused);
00145 
00146 static gchar *title_from_tuple(Tuple * tuple)
00147 {
00148     const gchar *format = tuple_get_string(tuple, FIELD_FORMATTER, NULL);
00149 
00150     if (format == NULL)
00151         format = get_gentitle_format();
00152 
00153     return tuple_formatter_make_title_string(tuple, format);
00154 }
00155 
00156 static void entry_set_tuple_real (Entry * entry, Tuple * tuple)
00157 {
00158     /* Hack: We cannot refresh segmented entries (since their info is read from
00159      * the cue sheet when it is first loaded), so leave them alone. -jlindgren */
00160     if (entry->segmented && ! tuple)
00161         return;
00162 
00163     if (entry->tuple != NULL)
00164         tuple_free (entry->tuple);
00165     entry->tuple = tuple;
00166 
00167     g_free (entry->formatted);
00168     stringpool_unref (entry->title);
00169     stringpool_unref (entry->artist);
00170     stringpool_unref (entry->album);
00171 
00172     if (tuple == NULL)
00173     {
00174         entry->formatted = NULL;
00175         entry->title = NULL;
00176         entry->artist = NULL;
00177         entry->album = NULL;
00178         entry->length = 0;
00179         entry->segmented = FALSE;
00180         entry->start = entry->end = -1;
00181     }
00182     else
00183     {
00184         entry->formatted = title_from_tuple (tuple);
00185         describe_song (entry->filename, tuple, & entry->title, & entry->artist,
00186          & entry->album);
00187         entry->length = tuple_get_int (tuple, FIELD_LENGTH, NULL);
00188         entry->length = MAX (entry->length, 0);
00189 
00190         if (tuple_get_value_type (tuple, FIELD_SEGMENT_START, NULL) == TUPLE_INT)
00191         {
00192             entry->segmented = TRUE;
00193             entry->start = tuple_get_int (tuple, FIELD_SEGMENT_START, NULL);
00194 
00195             if (tuple_get_value_type (tuple, FIELD_SEGMENT_END, NULL) ==
00196              TUPLE_INT)
00197                 entry->end = tuple_get_int (tuple, FIELD_SEGMENT_END, NULL);
00198             else
00199                 entry->end = -1;
00200         }
00201         else
00202             entry->segmented = FALSE;
00203     }
00204 }
00205 
00206 static void entry_set_tuple (Playlist * playlist, Entry * entry, Tuple * tuple)
00207 {
00208     if (entry->tuple != NULL)
00209     {
00210         playlist->total_length -= entry->length;
00211 
00212         if (entry->selected)
00213             playlist->selected_length -= entry->length;
00214     }
00215 
00216     entry_set_tuple_real (entry, tuple);
00217 
00218     if (tuple != NULL)
00219     {
00220         playlist->total_length += entry->length;
00221 
00222         if (entry->selected)
00223             playlist->selected_length += entry->length;
00224     }
00225 }
00226 
00227 static void entry_set_failed (Playlist * playlist, Entry * entry)
00228 {
00229     entry_set_tuple (playlist, entry, tuple_new_from_filename (entry->filename));
00230     entry->failed = TRUE;
00231 }
00232 
00233 static Entry * entry_new (gchar * filename, PluginHandle * decoder, Tuple *
00234  tuple)
00235 {
00236     Entry * entry = g_malloc (sizeof (Entry));
00237 
00238     entry->filename = filename;
00239     entry->decoder = decoder;
00240     entry->tuple = NULL;
00241     entry->formatted = NULL;
00242     entry->title = NULL;
00243     entry->artist = NULL;
00244     entry->album = NULL;
00245     entry->failed = FALSE;
00246     entry->number = -1;
00247     entry->selected = FALSE;
00248     entry->shuffle_num = 0;
00249     entry->queued = FALSE;
00250     entry->segmented = FALSE;
00251     entry->start = entry->end = -1;
00252 
00253     entry_set_tuple_real (entry, tuple);
00254     return entry;
00255 }
00256 
00257 static void entry_free (Entry * entry)
00258 {
00259     g_free(entry->filename);
00260 
00261     if (entry->tuple != NULL)
00262         tuple_free(entry->tuple);
00263 
00264     g_free (entry->formatted);
00265     stringpool_unref (entry->title);
00266     stringpool_unref (entry->artist);
00267     stringpool_unref (entry->album);
00268     g_free (entry);
00269 }
00270 
00271 static void entry_check_has_decoder (Playlist * playlist, Entry * entry)
00272 {
00273     if (entry->decoder != NULL || entry->failed)
00274         return;
00275 
00276     entry->decoder = file_find_decoder (entry->filename, FALSE);
00277     if (! entry->decoder)
00278         entry_set_failed (playlist, entry);
00279 }
00280 
00281 static Playlist * playlist_new (void)
00282 {
00283     Playlist * playlist = g_malloc (sizeof (Playlist));
00284 
00285     playlist->number = -1;
00286     playlist->unique_id = next_unique_id ++;
00287     playlist->filename = NULL;
00288     playlist->title = g_strdup(_("Untitled Playlist"));
00289     playlist->entries = index_new();
00290     playlist->position = NULL;
00291     playlist->selected_count = 0;
00292     playlist->last_shuffle_num = 0;
00293     playlist->queued = NULL;
00294     playlist->total_length = 0;
00295     playlist->selected_length = 0;
00296 
00297     return playlist;
00298 }
00299 
00300 static void playlist_free (Playlist * playlist)
00301 {
00302     gint count;
00303 
00304     g_free(playlist->filename);
00305     g_free(playlist->title);
00306 
00307     for (count = 0; count < index_count(playlist->entries); count++)
00308         entry_free(index_get(playlist->entries, count));
00309 
00310     index_free(playlist->entries);
00311     g_list_free(playlist->queued);
00312     g_free(playlist);
00313 }
00314 
00315 static void number_playlists(gint at, gint length)
00316 {
00317     gint count;
00318 
00319     for (count = 0; count < length; count++)
00320     {
00321         Playlist * playlist = index_get (playlists, at + count);
00322         playlist->number = at + count;
00323     }
00324 }
00325 
00326 static Playlist * lookup_playlist (gint playlist_num)
00327 {
00328     /* Not initted or already shut down */
00329     if (! playlists)
00330         return NULL;
00331 
00332     if (playlist_num < 0 || playlist_num >= index_count(playlists))
00333         return NULL;
00334 
00335     return index_get(playlists, playlist_num);
00336 }
00337 
00338 static void number_entries (Playlist * playlist, gint at, gint length)
00339 {
00340     gint count;
00341 
00342     for (count = 0; count < length; count++)
00343     {
00344         Entry * entry = index_get (playlist->entries, at + count);
00345         entry->number = at + count;
00346     }
00347 }
00348 
00349 static Entry * lookup_entry (Playlist * playlist, gint entry_num)
00350 {
00351     if (entry_num < 0 || entry_num >= index_count(playlist->entries))
00352         return NULL;
00353 
00354     return index_get(playlist->entries, entry_num);
00355 }
00356 
00357 static gboolean update (void * unused)
00358 {
00359     hook_call ("playlist update", GINT_TO_POINTER (update_level));
00360     update_source = 0;
00361     return FALSE;
00362 }
00363 
00364 static void queue_update (gint level, Playlist * list, gint at, gint count)
00365 {
00366     if (! update_source)
00367     {
00368         update_source = g_idle_add_full (G_PRIORITY_HIGH_IDLE, update, NULL,
00369          NULL);
00370         update_level = 0;
00371         update_playlist = list;
00372         unchanged_before = list ? index_count (list->entries) : 0;
00373         unchanged_after = list ? index_count (list->entries) : 0;
00374     }
00375 
00376     update_level = MAX (update_level, level);
00377 
00378     if (list && list == update_playlist)
00379     {
00380         unchanged_before = MIN (unchanged_before, at);
00381         unchanged_after = MIN (unchanged_after, index_count (list->entries) - at
00382          - count);
00383     }
00384     else
00385     {
00386         update_playlist = NULL;
00387         unchanged_before = 0;
00388         unchanged_after = 0;
00389     }
00390 }
00391 
00392 gboolean playlist_update_pending (void)
00393 {
00394     return update_source ? TRUE : FALSE;
00395 }
00396 
00397 gboolean playlist_update_range (gint * playlist, gint * at, gint * count)
00398 {
00399     g_return_val_if_fail (update_source, FALSE);
00400 
00401     if (! update_playlist)
00402         return FALSE;
00403 
00404     * playlist = update_playlist->number;
00405     * at = unchanged_before;
00406     * count = index_count (update_playlist->entries) - unchanged_before -
00407      unchanged_after;
00408     return TRUE;
00409 }
00410 
00411 /* scan_mutex must be locked! */
00412 void scan_receive (void)
00413 {
00414     for (gint i = 0; i < SCAN_THREADS; i ++)
00415     {
00416         if (! scan_filenames[i] || scan_decoders[i])
00417             continue; /* thread not in use or still working */
00418 
00419         Entry * entry = index_get (active_playlist->entries, scan_positions[i]);
00420 
00421         if (scan_tuples[i])
00422             entry_set_tuple (active_playlist, entry, scan_tuples[i]);
00423         else
00424             entry_set_failed (active_playlist, entry);
00425 
00426         scan_filenames[i] = NULL;
00427         scan_tuples[i] = NULL;
00428 
00429         queue_update (PLAYLIST_UPDATE_METADATA, active_playlist,
00430          scan_positions[i], 1);
00431     }
00432 }
00433 
00434 static gboolean scan_next (void * unused)
00435 {
00436     gint entries = index_count (active_playlist->entries);
00437     gint search = 0;
00438 
00439     g_mutex_lock (scan_mutex);
00440 
00441     if (scan_source)
00442     {
00443         g_source_remove (scan_source);
00444         scan_source = 0;
00445     }
00446 
00447     scan_receive ();
00448 
00449     for (gint i = 0; i < SCAN_THREADS; i ++)
00450         search = MAX (search, scan_positions[i] + 1);
00451 
00452     for (gint i = 0; i < SCAN_THREADS; i ++)
00453     {
00454         if (scan_filenames[i])
00455             continue; /* thread already in use */
00456 
00457         for (; search < entries; search ++)
00458         {
00459             Entry * entry = index_get (active_playlist->entries, search);
00460 
00461             if (entry->tuple)
00462                 continue;
00463 
00464             entry_check_has_decoder (active_playlist, entry);
00465             if (entry->failed)
00466                 continue;
00467 
00468             vfs_prepare_filename (entry->filename);
00469 
00470             scan_positions[i] = search;
00471             scan_filenames[i] = entry->filename;
00472             scan_decoders[i] = entry->decoder;
00473             g_cond_signal (scan_conds[i]);
00474 
00475             search ++;
00476             break;
00477         }
00478     }
00479 
00480     g_mutex_unlock (scan_mutex);
00481     return FALSE;
00482 }
00483 
00484 static void scan_continue (void)
00485 {
00486     if (! scan_source)
00487         scan_source = g_idle_add_full (G_PRIORITY_LOW, scan_next, NULL, NULL);
00488 }
00489 
00490 static void scan_reset (void)
00491 {
00492     for (gint i = 0; i < SCAN_THREADS; i ++)
00493     {
00494         assert (! scan_filenames[i]); /* scan in progress == very, very bad */
00495         scan_positions[i] = -1;
00496     }
00497 
00498     scan_continue ();
00499 }
00500 
00501 static void scan_stop (void)
00502 {
00503     g_mutex_lock (scan_mutex);
00504 
00505     if (scan_source != 0)
00506     {
00507         g_source_remove (scan_source);
00508         scan_source = 0;
00509     }
00510 
00511     for (gint i = 0; i < SCAN_THREADS; i ++)
00512     {
00513         if (! scan_filenames[i])
00514             continue;
00515 
00516         while (scan_decoders[i])
00517             g_cond_wait (scan_conds[i], scan_mutex);
00518     }
00519 
00520     scan_receive ();
00521     g_mutex_unlock (scan_mutex);
00522 }
00523 
00524 static void * scanner (void * data)
00525 {
00526     gint i = GPOINTER_TO_INT (data);
00527 
00528     g_mutex_lock (scan_mutex);
00529     g_cond_signal (scan_conds[i]);
00530 
00531     while (1)
00532     {
00533         g_cond_wait (scan_conds[i], scan_mutex);
00534 
00535         if (scan_quit)
00536             break;
00537 
00538         if (! scan_filenames[i])
00539             continue;
00540 
00541         scan_tuples[i] = file_read_tuple (scan_filenames[i], scan_decoders[i]);
00542         scan_decoders[i] = NULL;
00543         g_cond_signal (scan_conds[i]);
00544         scan_continue ();
00545     }
00546 
00547     g_mutex_unlock (scan_mutex);
00548     return NULL;
00549 }
00550 
00551 /* As soon as we know the caller is looking for metadata, we start the threaded
00552  * scanner.  Though it may be faster in the short run simply to scan the entry
00553  * we are concerned with in the main thread, this is better in the long run
00554  * because the scanner can work on the following entries while the caller is
00555  * processing this one. */
00556 static gboolean scan_threaded (Playlist * playlist, Entry * entry)
00557 {
00558     gint i;
00559 
00560     if (playlist != active_playlist)
00561         return FALSE;
00562 
00563     scan_next (NULL);
00564 
00565     if (entry->tuple)
00566         return TRUE;
00567 
00568     for (i = 0; i < SCAN_THREADS; i ++)
00569     {
00570         if (entry->number == scan_positions[i])
00571             goto FOUND;
00572     }
00573 
00574     return FALSE;
00575 
00576 FOUND:
00577     g_mutex_lock (scan_mutex);
00578     scan_receive ();
00579 
00580     while (scan_filenames[i])
00581     {
00582         g_cond_wait (scan_conds[i], scan_mutex);
00583         scan_receive ();
00584     }
00585 
00586     g_mutex_unlock (scan_mutex);
00587     return TRUE;
00588 }
00589 
00590 static void check_scanned (Playlist * playlist, Entry * entry)
00591 {
00592     if (entry->tuple)
00593         return;
00594     if (scan_threaded (playlist, entry))
00595         return;
00596 
00597     entry_check_has_decoder (playlist, entry);
00598     if (entry->failed)
00599         return;
00600 
00601     entry_set_tuple (playlist, entry, file_read_tuple (entry->filename,
00602      entry->decoder));
00603     if (! entry->tuple)
00604         entry_set_failed (playlist, entry);
00605 
00606     queue_update (PLAYLIST_UPDATE_METADATA, playlist, entry->number, 1);
00607 }
00608 
00609 static void check_selected_scanned (Playlist * playlist)
00610 {
00611     gint entries = index_count (playlist->entries);
00612     for (gint count = 0; count < entries; count++)
00613     {
00614         Entry * entry = index_get (playlist->entries, count);
00615         if (entry->selected)
00616             check_scanned (playlist, entry);
00617     }
00618 }
00619 
00620 static void check_all_scanned (Playlist * playlist)
00621 {
00622     gint entries = index_count (playlist->entries);
00623     for (gint count = 0; count < entries; count++)
00624         check_scanned (playlist, index_get (playlist->entries, count));
00625 }
00626 
00627 void playlist_init (void)
00628 {
00629     srand (time (NULL));
00630 
00631     playlists = index_new ();
00632     Playlist * playlist = playlist_new ();
00633     index_append (playlists, playlist);
00634     playlist->number = 0;
00635     active_playlist = playlist;
00636     playing_playlist = NULL;
00637 
00638     update_source = 0;
00639 
00640     scan_mutex = g_mutex_new ();
00641     memset (scan_filenames, 0, sizeof scan_filenames);
00642     memset (scan_decoders, 0, sizeof scan_decoders);
00643     memset (scan_tuples, 0, sizeof scan_tuples);
00644     scan_source = 0;
00645     scan_quit = FALSE;
00646 
00647     g_mutex_lock (scan_mutex);
00648 
00649     for (gint i = 0; i < SCAN_THREADS; i ++)
00650     {
00651         scan_conds[i] = g_cond_new ();
00652         scan_threads[i] = g_thread_create (scanner, GINT_TO_POINTER (i), TRUE,
00653          NULL);
00654         g_cond_wait (scan_conds[i], scan_mutex);
00655     }
00656 
00657     g_mutex_unlock (scan_mutex);
00658 
00659     scan_reset ();
00660 }
00661 
00662 void playlist_end(void)
00663 {
00664     gint count;
00665 
00666     scan_stop ();
00667     scan_quit = TRUE;
00668 
00669     for (gint i = 0; i < SCAN_THREADS; i ++)
00670     {
00671         g_mutex_lock (scan_mutex);
00672         g_cond_signal (scan_conds[i]);
00673         g_mutex_unlock (scan_mutex);
00674         g_thread_join (scan_threads[i]);
00675         g_cond_free (scan_conds[i]);
00676     }
00677 
00678     g_mutex_free (scan_mutex);
00679 
00680     if (update_source != 0)
00681         g_source_remove(update_source);
00682 
00683     for (count = 0; count < index_count(playlists); count++)
00684         playlist_free(index_get(playlists, count));
00685 
00686     index_free(playlists);
00687     playlists = NULL;
00688     active_playlist = NULL;
00689     playing_playlist = NULL;
00690 }
00691 
00692 gint playlist_count(void)
00693 {
00694     return index_count(playlists);
00695 }
00696 
00697 void playlist_insert(gint at)
00698 {
00699     PLAYLIST_WILL_CHANGE;
00700 
00701     if (at < 0 || at > index_count(playlists))
00702         at = index_count(playlists);
00703 
00704     if (at == index_count(playlists))
00705         index_append(playlists, playlist_new());
00706     else
00707         index_insert(playlists, at, playlist_new());
00708 
00709     number_playlists(at, index_count(playlists) - at);
00710 
00711     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00712 }
00713 
00714 void playlist_reorder (gint from, gint to, gint count)
00715 {
00716     struct index * displaced;
00717 
00718     g_return_if_fail (from >= 0 && from + count <= index_count (playlists));
00719     g_return_if_fail (to >= 0 && to + count <= index_count (playlists));
00720     g_return_if_fail (count >= 0);
00721 
00722     PLAYLIST_WILL_CHANGE;
00723 
00724     displaced = index_new ();
00725 
00726     if (to < from)
00727         index_copy_append (playlists, to, displaced, from - to);
00728     else
00729         index_copy_append (playlists, from + count, displaced, to - from);
00730 
00731     index_move (playlists, from, to, count);
00732 
00733     if (to < from)
00734     {
00735         index_copy_set (displaced, 0, playlists, to + count, from - to);
00736         number_playlists (to, from + count - to);
00737     }
00738     else
00739     {
00740         index_copy_set (displaced, 0, playlists, from, to - from);
00741         number_playlists (from, to + count - from);
00742     }
00743 
00744     index_free (displaced);
00745 
00746     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00747 }
00748 
00749 void playlist_delete (gint playlist_num)
00750 {
00751     DECLARE_PLAYLIST;
00752 
00753     LOOKUP_PLAYLIST;
00754 
00755     if (playlist == playing_playlist)
00756     {
00757         if (playback_get_playing ())
00758             playback_stop ();
00759 
00760         playing_playlist = NULL;
00761     }
00762 
00763     PLAYLIST_WILL_CHANGE;
00764 
00765     playlist_free(playlist);
00766     index_delete(playlists, playlist_num, 1);
00767     number_playlists(playlist_num, index_count(playlists) - playlist_num);
00768 
00769     if (index_count(playlists) == 0)
00770         playlist_insert(0);
00771 
00772     if (playlist == active_playlist)
00773         active_playlist = index_get (playlists, MIN (playlist_num, index_count
00774          (playlists) - 1));
00775 
00776     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00777 }
00778 
00779 gint playlist_get_unique_id (gint playlist_num)
00780 {
00781     DECLARE_PLAYLIST;
00782     LOOKUP_PLAYLIST_RET (-1);
00783     return playlist->unique_id;
00784 }
00785 
00786 gint playlist_by_unique_id (gint id)
00787 {
00788     g_return_val_if_fail (playlists, -1);
00789     gint n = index_count (playlists);
00790 
00791     for (gint i = 0; i < n; i ++)
00792     {
00793         Playlist * p = index_get (playlists, i);
00794         if (p->unique_id == id)
00795             return p->number;
00796     }
00797 
00798     return -1;
00799 }
00800 
00801 void playlist_set_filename(gint playlist_num, const gchar * filename)
00802 {
00803     DECLARE_PLAYLIST;
00804 
00805     LOOKUP_PLAYLIST;
00806     PLAYLIST_WILL_CHANGE;
00807 
00808     g_free(playlist->filename);
00809     playlist->filename = g_strdup(filename);
00810 
00811     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00812 }
00813 
00814 const gchar *playlist_get_filename(gint playlist_num)
00815 {
00816     DECLARE_PLAYLIST;
00817 
00818     LOOKUP_PLAYLIST_RET (NULL);
00819 
00820     return playlist->filename;
00821 }
00822 
00823 void playlist_set_title(gint playlist_num, const gchar * title)
00824 {
00825     DECLARE_PLAYLIST;
00826 
00827     LOOKUP_PLAYLIST;
00828     PLAYLIST_WILL_CHANGE;
00829 
00830     g_free(playlist->title);
00831     playlist->title = g_strdup(title);
00832 
00833     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00834 }
00835 
00836 const gchar *playlist_get_title(gint playlist_num)
00837 {
00838     DECLARE_PLAYLIST;
00839 
00840     LOOKUP_PLAYLIST_RET (NULL);
00841 
00842     return playlist->title;
00843 }
00844 
00845 void playlist_set_active(gint playlist_num)
00846 {
00847     DECLARE_PLAYLIST;
00848 
00849     LOOKUP_PLAYLIST;
00850     PLAYLIST_WILL_CHANGE;
00851 
00852     active_playlist = playlist;
00853 
00854     PLAYLIST_HAS_CHANGED (NULL, 0, 0);
00855 }
00856 
00857 gint playlist_get_active(void)
00858 {
00859     return (active_playlist == NULL) ? -1 : active_playlist->number;
00860 }
00861 
00862 void playlist_set_playing(gint playlist_num)
00863 {
00864     DECLARE_PLAYLIST;
00865 
00866     if (playlist_num == -1)
00867         playlist = NULL;
00868     else
00869         LOOKUP_PLAYLIST;
00870 
00871     if (playing_playlist != NULL && playback_get_playing ())
00872         playback_stop();
00873 
00874     playing_playlist = playlist;
00875 }
00876 
00877 gint playlist_get_playing(void)
00878 {
00879     return (playing_playlist == NULL) ? -1 : playing_playlist->number;
00880 }
00881 
00882 /* If we are already at the song or it is already at the top of the shuffle
00883  * list, we let it be.  Otherwise, we move it to the top. */
00884 static void set_position (Playlist * playlist, Entry * entry)
00885 {
00886     if (entry == playlist->position)
00887         return;
00888 
00889     playlist->position = entry;
00890 
00891     if (entry == NULL)
00892         return;
00893 
00894     if (! entry->shuffle_num || entry->shuffle_num != playlist->last_shuffle_num)
00895     {
00896         playlist->last_shuffle_num ++;
00897         entry->shuffle_num = playlist->last_shuffle_num;
00898     }
00899 }
00900 
00901 gint playlist_entry_count(gint playlist_num)
00902 {
00903     DECLARE_PLAYLIST;
00904 
00905     LOOKUP_PLAYLIST_RET (0);
00906 
00907     return index_count(playlist->entries);
00908 }
00909 
00910 static void make_entries (gchar * filename, PluginHandle * decoder, Tuple *
00911  tuple, struct index * list)
00912 {
00913     uri_check_utf8 (& filename, TRUE);
00914 
00915     if (! tuple && ! decoder)
00916         decoder = file_find_decoder (filename, TRUE);
00917 
00918     if (! tuple && decoder && input_plugin_has_subtunes (decoder) && ! strchr
00919      (filename, '?'))
00920         tuple = file_read_tuple (filename, decoder);
00921 
00922     if (tuple != NULL && tuple->nsubtunes > 0)
00923     {
00924         gint subtune;
00925 
00926         for (subtune = 0; subtune < tuple->nsubtunes; subtune++)
00927         {
00928             gchar *name = g_strdup_printf("%s?%d", filename, (tuple->subtunes == NULL) ? 1 + subtune : tuple->subtunes[subtune]);
00929             make_entries(name, decoder, NULL, list);
00930         }
00931 
00932         g_free(filename);
00933         tuple_free(tuple);
00934     }
00935     else
00936         index_append(list, entry_new(filename, decoder, tuple));
00937 }
00938 
00939 void playlist_entry_insert(gint playlist_num, gint at, gchar * filename, Tuple * tuple)
00940 {
00941     struct index *filenames = index_new();
00942     struct index *tuples = index_new();
00943 
00944     index_append(filenames, filename);
00945     index_append(tuples, tuple);
00946 
00947     playlist_entry_insert_batch_with_decoders (playlist_num, at, filenames,
00948      NULL, tuples);
00949 }
00950 
00951 void playlist_entry_insert_batch (gint playlist_num, gint at,
00952  struct index * filenames, struct index * tuples)
00953 {
00954     playlist_entry_insert_batch_with_decoders (playlist_num, at, filenames,
00955      NULL, tuples);
00956 }
00957 
00958 void playlist_entry_insert_batch_with_decoders (gint playlist_num, gint at,
00959  struct index * filenames, struct index * decoders, struct index * tuples)
00960 {
00961     DECLARE_PLAYLIST;
00962     LOOKUP_PLAYLIST;
00963     PLAYLIST_WILL_CHANGE;
00964 
00965     gint entries = index_count (playlist->entries);
00966 
00967     if (at < 0 || at > entries)
00968         at = entries;
00969 
00970     gint number = index_count (filenames);
00971     struct index * add = index_new ();
00972 
00973     /* Preallocate space to avoid reallocs.  (The actual number of entries may
00974      * turn out to be greater due to subtunes.) */
00975     index_allocate (add, number);
00976 
00977     for (gint count = 0; count < number; count ++)
00978         make_entries (index_get (filenames, count), decoders ? index_get
00979          (decoders, count) : NULL, tuples ? index_get (tuples, count) : NULL,
00980          add);
00981 
00982     index_free (filenames);
00983     if (decoders)
00984         index_free (decoders);
00985     if (tuples)
00986         index_free (tuples);
00987 
00988     number = index_count (add);
00989     index_merge_insert (playlist->entries, at, add);
00990     index_free (add);
00991 
00992     number_entries(playlist, at, entries + number - at);
00993 
00994     for (gint count = 0; count < number; count ++)
00995     {
00996         Entry * entry = index_get (playlist->entries, at + count);
00997         playlist->total_length += entry->length;
00998     }
00999 
01000     PLAYLIST_HAS_CHANGED (playlist, at, number);
01001 }
01002 
01003 void playlist_entry_delete(gint playlist_num, gint at, gint number)
01004 {
01005     DECLARE_PLAYLIST;
01006     gboolean stop = FALSE;
01007     gint entries, count;
01008 
01009     LOOKUP_PLAYLIST;
01010     PLAYLIST_WILL_CHANGE;
01011 
01012     entries = index_count (playlist->entries);
01013 
01014     if (at < 0 || at > entries)
01015         at = entries;
01016     if (number < 0 || number > entries - at)
01017         number = entries - at;
01018 
01019     for (count = 0; count < number; count++)
01020     {
01021         Entry * entry = index_get (playlist->entries, at + count);
01022 
01023         if (entry == playlist->position)
01024         {
01025             stop = (playlist == playing_playlist);
01026             set_position (playlist, NULL);
01027         }
01028 
01029         if (entry->selected)
01030         {
01031             playlist->selected_count--;
01032             playlist->selected_length -= entry->length;
01033         }
01034 
01035         if (entry->queued)
01036             playlist->queued = g_list_remove(playlist->queued, entry);
01037 
01038         playlist->total_length -= entry->length;
01039 
01040         entry_free(entry);
01041     }
01042 
01043     index_delete (playlist->entries, at, number);
01044     number_entries (playlist, at, entries - at - number);
01045 
01046     if (stop && playback_get_playing ())
01047         playback_stop ();
01048 
01049     PLAYLIST_HAS_CHANGED (playlist, at, 0);
01050 }
01051 
01052 const gchar *playlist_entry_get_filename(gint playlist_num, gint entry_num)
01053 {
01054     DECLARE_PLAYLIST_ENTRY;
01055 
01056     LOOKUP_PLAYLIST_ENTRY_RET (NULL);
01057 
01058     return entry->filename;
01059 }
01060 
01061 PluginHandle * playlist_entry_get_decoder (gint playlist_num, gint entry_num,
01062  gboolean fast)
01063 {
01064     DECLARE_PLAYLIST_ENTRY;
01065     LOOKUP_PLAYLIST_ENTRY_RET (NULL);
01066 
01067     if (! fast)
01068         entry_check_has_decoder (playlist, entry);
01069 
01070     return entry->decoder;
01071 }
01072 
01073 void playlist_entry_set_tuple (gint playlist_num, gint entry_num, Tuple * tuple)
01074 {
01075     DECLARE_PLAYLIST_ENTRY;
01076 
01077     LOOKUP_PLAYLIST_ENTRY;
01078     METADATA_WILL_CHANGE;
01079 
01080     entry_set_tuple (playlist, entry, tuple);
01081 
01082     METADATA_HAS_CHANGED (playlist, entry_num, 1);
01083 }
01084 
01085 const Tuple * playlist_entry_get_tuple (gint playlist_num, gint entry_num,
01086  gboolean fast)
01087 {
01088     DECLARE_PLAYLIST_ENTRY;
01089     LOOKUP_PLAYLIST_ENTRY_RET (NULL);
01090 
01091     if (! fast)
01092         check_scanned (playlist, entry);
01093 
01094     return entry->tuple;
01095 }
01096 
01097 const gchar * playlist_entry_get_title (gint playlist_num, gint entry_num,
01098  gboolean fast)
01099 {
01100     DECLARE_PLAYLIST_ENTRY;
01101     LOOKUP_PLAYLIST_ENTRY_RET (NULL);
01102 
01103     if (! fast)
01104         check_scanned (playlist, entry);
01105 
01106     return (entry->formatted == NULL) ? entry->filename : entry->formatted;
01107 }
01108 
01109 void playlist_entry_describe (gint playlist_num, gint entry_num,
01110  const gchar * * title, const gchar * * artist, const gchar * * album,
01111  gboolean fast)
01112 {
01113     * title = * artist = * album = NULL;
01114     DECLARE_PLAYLIST_ENTRY;
01115     LOOKUP_PLAYLIST_ENTRY;
01116 
01117     if (! fast)
01118         check_scanned (playlist, entry);
01119 
01120     if (entry->title)
01121     {
01122         * title = entry->title;
01123         * artist = entry->artist;
01124         * album = entry->album;
01125     }
01126     else
01127         * title = entry->filename;
01128 }
01129 
01130 gint playlist_entry_get_length (gint playlist_num, gint entry_num, gboolean fast)
01131 {
01132     DECLARE_PLAYLIST_ENTRY;
01133     LOOKUP_PLAYLIST_ENTRY_RET (0);
01134 
01135     if (! fast)
01136         check_scanned (playlist, entry);
01137 
01138     return entry->length;
01139 }
01140 
01141 gboolean playlist_entry_is_segmented(gint playlist_num, gint entry_num)
01142 {
01143     DECLARE_PLAYLIST_ENTRY;
01144 
01145     LOOKUP_PLAYLIST_ENTRY_RET (FALSE);
01146 
01147     return entry->segmented;
01148 }
01149 
01150 gint playlist_entry_get_start_time (gint playlist_num, gint entry_num)
01151 {
01152     DECLARE_PLAYLIST_ENTRY;
01153 
01154     LOOKUP_PLAYLIST_ENTRY_RET (-1);
01155 
01156     return entry->start;
01157 }
01158 
01159 gint playlist_entry_get_end_time (gint playlist_num, gint entry_num)
01160 {
01161     DECLARE_PLAYLIST_ENTRY;
01162 
01163     LOOKUP_PLAYLIST_ENTRY_RET (-1);
01164 
01165     return entry->end;
01166 }
01167 
01168 void playlist_set_position (gint playlist_num, gint entry_num)
01169 {
01170     DECLARE_PLAYLIST_ENTRY;
01171 
01172     if (entry_num == -1)
01173     {
01174         LOOKUP_PLAYLIST;
01175         entry = NULL;
01176     }
01177     else
01178         LOOKUP_PLAYLIST_ENTRY;
01179 
01180     if (playlist == playing_playlist && playback_get_playing ())
01181         playback_stop ();
01182 
01183     set_position (playlist, entry);
01184 
01185     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
01186 }
01187 
01188 gint playlist_get_position(gint playlist_num)
01189 {
01190     DECLARE_PLAYLIST;
01191 
01192     LOOKUP_PLAYLIST_RET (-1);
01193 
01194     return (playlist->position == NULL) ? -1 : playlist->position->number;
01195 }
01196 
01197 void playlist_entry_set_selected(gint playlist_num, gint entry_num, gboolean selected)
01198 {
01199     DECLARE_PLAYLIST_ENTRY;
01200 
01201     LOOKUP_PLAYLIST_ENTRY;
01202 
01203     if (entry->selected == selected)
01204         return;
01205 
01206     entry->selected = selected;
01207 
01208     if (selected)
01209     {
01210         playlist->selected_count++;
01211         playlist->selected_length += entry->length;
01212     }
01213     else
01214     {
01215         playlist->selected_count--;
01216         playlist->selected_length -= entry->length;
01217     }
01218 
01219     SELECTION_HAS_CHANGED (playlist, entry_num, 1);
01220 }
01221 
01222 gboolean playlist_entry_get_selected(gint playlist_num, gint entry_num)
01223 {
01224     DECLARE_PLAYLIST_ENTRY;
01225 
01226     LOOKUP_PLAYLIST_ENTRY_RET (FALSE);
01227 
01228     return entry->selected;
01229 }
01230 
01231 gint playlist_selected_count(gint playlist_num)
01232 {
01233     DECLARE_PLAYLIST;
01234 
01235     LOOKUP_PLAYLIST_RET (0);
01236 
01237     return playlist->selected_count;
01238 }
01239 
01240 void playlist_select_all (gint playlist_num, gboolean selected)
01241 {
01242     DECLARE_PLAYLIST;
01243     LOOKUP_PLAYLIST;
01244 
01245     gint entries = index_count (playlist->entries);
01246     gint first = entries, last = 0;
01247 
01248     for (gint count = 0; count < entries; count ++)
01249     {
01250         Entry * entry = index_get (playlist->entries, count);
01251 
01252         if ((selected && ! entry->selected) || (entry->selected && ! selected))
01253         {
01254             entry->selected = selected;
01255             first = MIN (first, entry->number);
01256             last = entry->number;
01257         }
01258     }
01259 
01260     if (selected)
01261     {
01262         playlist->selected_count = entries;
01263         playlist->selected_length = playlist->total_length;
01264     }
01265     else
01266     {
01267         playlist->selected_count = 0;
01268         playlist->selected_length = 0;
01269     }
01270 
01271     if (first < entries)
01272         SELECTION_HAS_CHANGED (playlist, first, last + 1 - first);
01273 }
01274 
01275 gint playlist_shift (gint playlist_num, gint entry_num, gint distance)
01276 {
01277     DECLARE_PLAYLIST_ENTRY;
01278     LOOKUP_PLAYLIST_ENTRY_RET (0);
01279 
01280     if (! entry->selected || ! distance)
01281         return 0;
01282 
01283     PLAYLIST_WILL_CHANGE;
01284 
01285     gint entries = index_count (playlist->entries);
01286     gint shift = 0, center, top, bottom;
01287 
01288     if (distance < 0)
01289     {
01290         for (center = entry_num; center > 0 && shift > distance; )
01291         {
01292             entry = index_get (playlist->entries, -- center);
01293             if (! entry->selected)
01294                 shift --;
01295         }
01296     }
01297     else
01298     {
01299         for (center = entry_num + 1; center < entries && shift < distance; )
01300         {
01301             entry = index_get (playlist->entries, center ++);
01302             if (! entry->selected)
01303                 shift ++;
01304         }
01305     }
01306 
01307     top = bottom = center;
01308 
01309     for (gint i = 0; i < top; i ++)
01310     {
01311         entry = index_get (playlist->entries, i);
01312         if (entry->selected)
01313             top = i;
01314     }
01315 
01316     for (gint i = entries; i > bottom; i --)
01317     {
01318         entry = index_get (playlist->entries, i - 1);
01319         if (entry->selected)
01320             bottom = i;
01321     }
01322 
01323     struct index * temp = index_new ();
01324 
01325     for (gint i = top; i < center; i ++)
01326     {
01327         entry = index_get (playlist->entries, i);
01328         if (! entry->selected)
01329             index_append (temp, entry);
01330     }
01331 
01332     for (gint i = top; i < bottom; i ++)
01333     {
01334         entry = index_get (playlist->entries, i);
01335         if (entry->selected)
01336             index_append (temp, entry);
01337     }
01338 
01339     for (gint i = center; i < bottom; i ++)
01340     {
01341         entry = index_get (playlist->entries, i);
01342         if (! entry->selected)
01343             index_append (temp, entry);
01344     }
01345 
01346     index_copy_set (temp, 0, playlist->entries, top, bottom - top);
01347 
01348     number_entries (playlist, top, bottom - top);
01349     PLAYLIST_HAS_CHANGED (playlist, top, bottom - top);
01350 
01351     return shift;
01352 }
01353 
01354 void playlist_delete_selected(gint playlist_num)
01355 {
01356     DECLARE_PLAYLIST;
01357     gboolean stop = FALSE;
01358     gint entries, count;
01359     struct index *others;
01360 
01361     LOOKUP_PLAYLIST;
01362     PLAYLIST_WILL_CHANGE;
01363 
01364     entries = index_count (playlist->entries);
01365     others = index_new();
01366 
01367     for (count = 0; count < entries; count++)
01368     {
01369         Entry * entry = index_get (playlist->entries, count);
01370 
01371         if (entry->selected)
01372         {
01373             if (entry == playlist->position)
01374             {
01375                 stop = (playlist == playing_playlist);
01376                 set_position (playlist, NULL);
01377             }
01378 
01379             if (entry->queued)
01380                 playlist->queued = g_list_remove(playlist->queued, entry);
01381 
01382             playlist->total_length -= entry->length;
01383 
01384             entry_free(entry);
01385         }
01386         else
01387             index_append(others, entry);
01388     }
01389 
01390     index_free(playlist->entries);
01391     playlist->entries = others;
01392 
01393     number_entries(playlist, 0, index_count(playlist->entries));
01394     playlist->selected_count = 0;
01395     playlist->selected_length = 0;
01396 
01397     if (stop && playback_get_playing ())
01398         playback_stop ();
01399 
01400     PLAYLIST_HAS_CHANGED (playlist, 0, index_count (playlist->entries));
01401 }
01402 
01403 void playlist_reverse(gint playlist_num)
01404 {
01405     DECLARE_PLAYLIST;
01406     gint entries, count;
01407     struct index *reversed;
01408 
01409     LOOKUP_PLAYLIST;
01410     PLAYLIST_WILL_CHANGE;
01411 
01412     entries = index_count(playlist->entries);
01413     reversed = index_new();
01414     count = entries;
01415 
01416     while (count--)
01417         index_append(reversed, index_get(playlist->entries, count));
01418 
01419     index_free(playlist->entries);
01420     playlist->entries = reversed;
01421 
01422     number_entries(playlist, 0, entries);
01423 
01424     PLAYLIST_HAS_CHANGED (playlist, 0, entries);
01425 }
01426 
01427 void playlist_randomize (gint playlist_num)
01428 {
01429     DECLARE_PLAYLIST;
01430     LOOKUP_PLAYLIST;
01431     PLAYLIST_WILL_CHANGE;
01432 
01433     gint entries = index_count (playlist->entries);
01434 
01435     for (gint i = 0; i < entries; i ++)
01436     {
01437         gint j = i + rand () % (entries - i);
01438 
01439         struct entry * entry = index_get (playlist->entries, j);
01440         index_set (playlist->entries, j, index_get (playlist->entries, i));
01441         index_set (playlist->entries, i, entry);
01442     }
01443 
01444     number_entries (playlist, 0, entries);
01445     PLAYLIST_HAS_CHANGED (playlist, 0, entries);
01446 }
01447 
01448 static gint filename_compare (const void * _a, const void * _b, void * _compare)
01449 {
01450     const Entry * a = _a, * b = _b;
01451     gint (* compare) (const gchar * a, const gchar * b) = _compare;
01452 
01453     gint diff = compare (a->filename, b->filename);
01454     if (diff)
01455         return diff;
01456 
01457     /* preserve order of "equal" entries */
01458     return a->number - b->number;
01459 }
01460 
01461 static gint tuple_compare (const void * _a, const void * _b, void * _compare)
01462 {
01463     const Entry * a = _a, * b = _b;
01464     gint (* compare) (const Tuple * a, const Tuple * b) = _compare;
01465 
01466     if (a->tuple == NULL)
01467         return (b->tuple == NULL) ? 0 : -1;
01468     if (b->tuple == NULL)
01469         return 1;
01470 
01471     gint diff = compare (a->tuple, b->tuple);
01472     if (diff)
01473         return diff;
01474 
01475     /* preserve order of "equal" entries */
01476     return a->number - b->number;
01477 }
01478 
01479 static gint title_compare (const void * _a, const void * _b, void * _compare)
01480 {
01481     const Entry * a = _a, * b = _b;
01482     gint (* compare) (const gchar * a, const gchar * b) = _compare;
01483 
01484     gint diff = compare (a->formatted ? a->formatted : a->filename, b->formatted
01485      ? b->formatted : b->filename);
01486     if (diff)
01487         return diff;
01488 
01489     /* preserve order of "equal" entries */
01490     return a->number - b->number;
01491 }
01492 
01493 static void sort (Playlist * playlist, gint (* compare) (const void * a,
01494  const void * b, void * inner), void * inner)
01495 {
01496     PLAYLIST_WILL_CHANGE;
01497 
01498     index_sort_with_data (playlist->entries, compare, inner);
01499     number_entries (playlist, 0, index_count (playlist->entries));
01500 
01501     PLAYLIST_HAS_CHANGED (playlist, 0, index_count (playlist->entries));
01502 }
01503 
01504 static void sort_selected (Playlist * playlist, gint (* compare) (const void *
01505  a, const void * b, void * inner), void * inner)
01506 {
01507     gint entries, count, count2;
01508     struct index *selected;
01509 
01510     PLAYLIST_WILL_CHANGE;
01511 
01512     entries = index_count(playlist->entries);
01513     selected = index_new();
01514 
01515     for (count = 0; count < entries; count++)
01516     {
01517         Entry * entry = index_get (playlist->entries, count);
01518         if (entry->selected)
01519             index_append(selected, entry);
01520     }
01521 
01522     index_sort_with_data (selected, compare, inner);
01523 
01524     count2 = 0;
01525 
01526     for (count = 0; count < entries; count++)
01527     {
01528         Entry * entry = index_get (playlist->entries, count);
01529         if (entry->selected)
01530             index_set(playlist->entries, count, index_get(selected, count2++));
01531     }
01532 
01533     index_free(selected);
01534     number_entries(playlist, 0, entries);
01535 
01536     PLAYLIST_HAS_CHANGED (playlist, 0, entries);
01537 }
01538 
01539 void playlist_sort_by_filename (gint playlist_num, gint (* compare)
01540  (const gchar * a, const gchar * b))
01541 {
01542     DECLARE_PLAYLIST;
01543 
01544     LOOKUP_PLAYLIST;
01545 
01546     sort (playlist, filename_compare, compare);
01547 }
01548 
01549 void playlist_sort_by_tuple (gint playlist_num, gint (* compare)
01550  (const Tuple * a, const Tuple * b))
01551 {
01552     DECLARE_PLAYLIST;
01553     LOOKUP_PLAYLIST;
01554     check_all_scanned (playlist);
01555     sort (playlist, tuple_compare, compare);
01556 }
01557 
01558 void playlist_sort_by_title (gint playlist_num, gint (* compare) (const gchar *
01559  a, const gchar * b))
01560 {
01561     DECLARE_PLAYLIST;
01562     LOOKUP_PLAYLIST;
01563     check_all_scanned (playlist);
01564     sort (playlist, title_compare, compare);
01565 }
01566 
01567 void playlist_sort_selected_by_filename (gint playlist_num, gint (* compare)
01568  (const gchar * a, const gchar * b))
01569 {
01570     DECLARE_PLAYLIST;
01571 
01572     LOOKUP_PLAYLIST;
01573 
01574     sort_selected (playlist, filename_compare, compare);
01575 }
01576 
01577 void playlist_sort_selected_by_tuple (gint playlist_num, gint (* compare)
01578  (const Tuple * a, const Tuple * b))
01579 {
01580     DECLARE_PLAYLIST;
01581     LOOKUP_PLAYLIST;
01582     check_selected_scanned (playlist);
01583     sort_selected (playlist, tuple_compare, compare);
01584 }
01585 
01586 void playlist_sort_selected_by_title (gint playlist_num, gint (* compare)
01587  (const gchar * a, const gchar * b))
01588 {
01589     DECLARE_PLAYLIST;
01590     LOOKUP_PLAYLIST;
01591     check_selected_scanned (playlist);
01592     sort (playlist, title_compare, compare);
01593 }
01594 
01595 void playlist_reformat_titles (void)
01596 {
01597     gint playlist_num;
01598 
01599     METADATA_WILL_CHANGE;
01600 
01601     for (playlist_num = 0; playlist_num < index_count(playlists); playlist_num++)
01602     {
01603         Playlist * playlist = index_get (playlists, playlist_num);
01604         gint entries = index_count(playlist->entries);
01605         gint count;
01606 
01607         for (count = 0; count < entries; count++)
01608         {
01609             Entry * entry = index_get (playlist->entries, count);
01610             g_free(entry->formatted);
01611             entry->formatted = (entry->tuple == NULL) ? NULL : title_from_tuple(entry->tuple);
01612         }
01613     }
01614 
01615     METADATA_HAS_CHANGED (NULL, 0, 0);
01616 }
01617 
01618 void playlist_rescan_real (gint playlist_num, gboolean onlyselected)
01619 {
01620     DECLARE_PLAYLIST;
01621     gint entries, count;
01622 
01623     LOOKUP_PLAYLIST;
01624     METADATA_WILL_CHANGE;
01625 
01626     entries = index_count(playlist->entries);
01627 
01628     for (count = 0; count < entries; count++)
01629     {
01630         Entry * entry = index_get (playlist->entries, count);
01631         if (! onlyselected || entry->selected)
01632         {
01633             entry_set_tuple (playlist, entry, NULL);
01634             entry->failed = FALSE;
01635         }
01636     }
01637 
01638     METADATA_HAS_CHANGED (playlist, 0, entries);
01639 }
01640 
01641 void playlist_rescan (gint playlist_num)
01642 {
01643     playlist_rescan_real (playlist_num, FALSE);
01644 }
01645 
01646 void playlist_rescan_selected (gint playlist_num)
01647 {
01648     playlist_rescan_real (playlist_num, TRUE);
01649 }
01650 
01651 void playlist_rescan_file (const gchar * filename)
01652 {
01653     gint num_playlists = index_count (playlists);
01654     gint playlist_num;
01655 
01656     METADATA_WILL_CHANGE;
01657 
01658     gchar * copy = NULL;
01659     if (! uri_is_utf8 (filename, TRUE))
01660         filename = copy = uri_to_utf8 (filename);
01661 
01662     for (playlist_num = 0; playlist_num < num_playlists; playlist_num ++)
01663     {
01664         Playlist * playlist = index_get (playlists, playlist_num);
01665         gint num_entries = index_count (playlist->entries);
01666         gint entry_num;
01667 
01668         for (entry_num = 0; entry_num < num_entries; entry_num ++)
01669         {
01670             Entry * entry = index_get (playlist->entries, entry_num);
01671 
01672             if (! strcmp (entry->filename, filename))
01673             {
01674                 entry_set_tuple (playlist, entry, NULL);
01675                 entry->failed = FALSE;
01676             }
01677         }
01678     }
01679 
01680     g_free (copy);
01681 
01682     METADATA_HAS_CHANGED (NULL, 0, 0);
01683 }
01684 
01685 gint64 playlist_get_total_length (gint playlist_num, gboolean fast)
01686 {
01687     DECLARE_PLAYLIST;
01688     LOOKUP_PLAYLIST_RET (0);
01689 
01690     if (! fast)
01691         check_all_scanned (playlist);
01692 
01693     return playlist->total_length;
01694 }
01695 
01696 gint64 playlist_get_selected_length (gint playlist_num, gboolean fast)
01697 {
01698     DECLARE_PLAYLIST;
01699     LOOKUP_PLAYLIST_RET (0);
01700 
01701     if (! fast)
01702         check_selected_scanned (playlist);
01703 
01704     return playlist->selected_length;
01705 }
01706 
01707 gint playlist_queue_count(gint playlist_num)
01708 {
01709     DECLARE_PLAYLIST;
01710 
01711     LOOKUP_PLAYLIST_RET (0);
01712 
01713     return g_list_length (playlist->queued);
01714 }
01715 
01716 void playlist_queue_insert(gint playlist_num, gint at, gint entry_num)
01717 {
01718     DECLARE_PLAYLIST_ENTRY;
01719 
01720     LOOKUP_PLAYLIST_ENTRY;
01721 
01722     if (entry->queued)
01723         return;
01724 
01725     if (at < 0)
01726         playlist->queued = g_list_append(playlist->queued, entry);
01727     else
01728         playlist->queued = g_list_insert(playlist->queued, entry, at);
01729 
01730     entry->queued = TRUE;
01731 
01732     SELECTION_HAS_CHANGED (playlist, entry_num, 1);
01733 }
01734 
01735 void playlist_queue_insert_selected (gint playlist_num, gint at)
01736 {
01737     DECLARE_PLAYLIST;
01738     LOOKUP_PLAYLIST;
01739 
01740     gint entries = index_count(playlist->entries);
01741     gint first = entries, last = 0;
01742 
01743     for (gint count = 0; count < entries; count++)
01744     {
01745         Entry * entry = index_get (playlist->entries, count);
01746 
01747         if (!entry->selected || entry->queued)
01748             continue;
01749 
01750         if (at < 0)
01751             playlist->queued = g_list_append(playlist->queued, entry);
01752         else
01753             playlist->queued = g_list_insert(playlist->queued, entry, at++);
01754 
01755         entry->queued = TRUE;
01756         first = MIN (first, entry->number);
01757         last = entry->number;
01758     }
01759 
01760     if (first < entries)
01761         SELECTION_HAS_CHANGED (playlist, first, last + 1 - first);
01762 }
01763 
01764 gint playlist_queue_get_entry(gint playlist_num, gint at)
01765 {
01766     DECLARE_PLAYLIST;
01767     GList *node;
01768 
01769     LOOKUP_PLAYLIST_RET (-1);
01770     node = g_list_nth(playlist->queued, at);
01771 
01772     if (node == NULL)
01773         return -1;
01774 
01775     return ((Entry *) node->data)->number;
01776 }
01777 
01778 gint playlist_queue_find_entry(gint playlist_num, gint entry_num)
01779 {
01780     DECLARE_PLAYLIST_ENTRY;
01781 
01782     LOOKUP_PLAYLIST_ENTRY_RET (-1);
01783 
01784     if (! entry->queued)
01785         return -1;
01786 
01787     return g_list_index(playlist->queued, entry);
01788 }
01789 
01790 void playlist_queue_delete(gint playlist_num, gint at, gint number)
01791 {
01792     DECLARE_PLAYLIST;
01793     LOOKUP_PLAYLIST;
01794 
01795     gint entries = index_count (playlist->entries);
01796     gint first = entries, last = 0;
01797 
01798     if (at == 0)
01799     {
01800         while (playlist->queued != NULL && number--)
01801         {
01802             Entry * entry = playlist->queued->data;
01803 
01804             playlist->queued = g_list_delete_link(playlist->queued, playlist->queued);
01805 
01806             entry->queued = FALSE;
01807             first = MIN (first, entry->number);
01808             last = entry->number;
01809         }
01810     }
01811     else
01812     {
01813         GList *anchor = g_list_nth(playlist->queued, at - 1);
01814 
01815         if (anchor == NULL)
01816             goto DONE;
01817 
01818         while (anchor->next != NULL && number--)
01819         {
01820             Entry * entry = anchor->next->data;
01821 
01822             playlist->queued = g_list_delete_link(playlist->queued, anchor->next);
01823 
01824             entry->queued = FALSE;
01825             first = MIN (first, entry->number);
01826             last = entry->number;
01827         }
01828     }
01829 
01830 DONE:
01831     if (first < entries)
01832         SELECTION_HAS_CHANGED (playlist, first, last + 1 - first);
01833 }
01834 
01835 void playlist_queue_delete_selected (gint playlist_num)
01836 {
01837     DECLARE_PLAYLIST;
01838     LOOKUP_PLAYLIST;
01839 
01840     gint entries = index_count (playlist->entries);
01841     gint first = entries, last = 0;
01842 
01843     for (GList * node = playlist->queued; node != NULL; )
01844     {
01845         GList * next = node->next;
01846         Entry * entry = node->data;
01847 
01848         if (entry->selected)
01849         {
01850             playlist->queued = g_list_delete_link (playlist->queued, node);
01851             first = MIN (first, entry->number);
01852             last = entry->number;
01853         }
01854 
01855         node = next;
01856     }
01857 
01858     if (first < entries)
01859         SELECTION_HAS_CHANGED (playlist, first, last + 1 - first);
01860 }
01861 
01862 static gboolean shuffle_prev (Playlist * playlist)
01863 {
01864     gint entries = index_count (playlist->entries), count;
01865     Entry * found = NULL;
01866 
01867     for (count = 0; count < entries; count ++)
01868     {
01869         Entry * entry = index_get (playlist->entries, count);
01870 
01871         if (entry->shuffle_num && (playlist->position == NULL ||
01872          entry->shuffle_num < playlist->position->shuffle_num) && (found == NULL
01873          || entry->shuffle_num > found->shuffle_num))
01874             found = entry;
01875     }
01876 
01877     if (found == NULL)
01878         return FALSE;
01879 
01880     playlist->position = found;
01881     return TRUE;
01882 }
01883 
01884 gboolean playlist_prev_song(gint playlist_num)
01885 {
01886     DECLARE_PLAYLIST;
01887 
01888     LOOKUP_PLAYLIST_RET (FALSE);
01889 
01890     if (cfg.shuffle)
01891     {
01892         if (! shuffle_prev (playlist))
01893             return FALSE;
01894     }
01895     else
01896     {
01897         if (playlist->position == NULL || playlist->position->number == 0)
01898             return FALSE;
01899 
01900         set_position (playlist, index_get (playlist->entries,
01901          playlist->position->number - 1));
01902     }
01903 
01904     if (playlist == playing_playlist && playback_get_playing ())
01905         playback_stop();
01906 
01907     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
01908     return TRUE;
01909 }
01910 
01911 static gboolean shuffle_next (Playlist * playlist)
01912 {
01913     gint entries = index_count (playlist->entries), choice = 0, count;
01914     Entry * found = NULL;
01915 
01916     for (count = 0; count < entries; count ++)
01917     {
01918         Entry * entry = index_get (playlist->entries, count);
01919 
01920         if (! entry->shuffle_num)
01921             choice ++;
01922         else if (playlist->position != NULL && entry->shuffle_num >
01923          playlist->position->shuffle_num && (found == NULL || entry->shuffle_num
01924          < found->shuffle_num))
01925             found = entry;
01926     }
01927 
01928     if (found != NULL)
01929     {
01930         playlist->position = found;
01931         return TRUE;
01932     }
01933 
01934     if (! choice)
01935         return FALSE;
01936 
01937     choice = rand () % choice;
01938 
01939     for (count = 0; ; count ++)
01940     {
01941         Entry * entry = index_get (playlist->entries, count);
01942 
01943         if (! entry->shuffle_num)
01944         {
01945             if (! choice)
01946             {
01947                 set_position (playlist, entry);
01948                 return TRUE;
01949             }
01950 
01951             choice --;
01952         }
01953     }
01954 }
01955 
01956 static void shuffle_reset (Playlist * playlist)
01957 {
01958     gint entries = index_count (playlist->entries), count;
01959 
01960     playlist->last_shuffle_num = 0;
01961 
01962     for (count = 0; count < entries; count ++)
01963     {
01964         Entry * entry = index_get (playlist->entries, count);
01965         entry->shuffle_num = 0;
01966     }
01967 }
01968 
01969 gboolean playlist_next_song(gint playlist_num, gboolean repeat)
01970 {
01971     DECLARE_PLAYLIST;
01972     gint entries;
01973 
01974     LOOKUP_PLAYLIST_RET (FALSE);
01975     entries = index_count(playlist->entries);
01976 
01977     if (entries == 0)
01978         return FALSE;
01979 
01980     /* If we have a song in queue, jump to it, _then_ remove it from queue */
01981     if (playlist->queued != NULL)
01982     {
01983         set_position (playlist, playlist->queued->data);
01984 
01985         playlist->queued = g_list_remove(playlist->queued, playlist->position);
01986         playlist->position->queued = FALSE;
01987     }
01988     else if (cfg.shuffle)
01989     {
01990         if (! shuffle_next (playlist))
01991         {
01992             if (! repeat)
01993                 return FALSE;
01994 
01995             shuffle_reset (playlist);
01996 
01997             if (! shuffle_next (playlist))
01998                 return FALSE;
01999         }
02000     }
02001     else
02002     {
02003         if (playlist->position == NULL)
02004             set_position (playlist, index_get (playlist->entries, 0));
02005         else if (playlist->position->number == entries - 1)
02006         {
02007             if (!repeat)
02008                 return FALSE;
02009 
02010             set_position (playlist, index_get (playlist->entries, 0));
02011         }
02012         else
02013             set_position (playlist, index_get (playlist->entries,
02014              playlist->position->number + 1));
02015     }
02016 
02017     if (playlist == playing_playlist && playback_get_playing ())
02018         playback_stop();
02019 
02020     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
02021     return TRUE;
02022 }
02023 
02024 void playlist_save_state (void)
02025 {
02026     gchar scratch[512];
02027     FILE * handle;
02028     gint playlist_num;
02029 
02030     snprintf (scratch, sizeof scratch, "%s/" STATE_FILE,
02031      get_path (AUD_PATH_USER_DIR));
02032     handle = fopen (scratch, "w");
02033 
02034     if (handle == NULL)
02035         return;
02036 
02037     fprintf (handle, "active %d\n", playlist_get_active ());
02038     fprintf (handle, "playing %d\n", playlist_get_playing ());
02039 
02040     for (playlist_num = 0; playlist_num < index_count (playlists);
02041      playlist_num ++)
02042     {
02043         Playlist * playlist = index_get (playlists, playlist_num);
02044         gint entries = index_count (playlist->entries), count;
02045 
02046         fprintf (handle, "playlist %d\n", playlist_num);
02047         fprintf (handle, "position %d\n", playlist_get_position (playlist_num));
02048         fprintf (handle, "last-shuffled %d\n", playlist->last_shuffle_num);
02049 
02050         for (count = 0; count < entries; count ++)
02051         {
02052             Entry * entry = index_get (playlist->entries, count);
02053             fprintf (handle, "S %d\n", entry->shuffle_num);
02054         }
02055     }
02056 
02057     fclose (handle);
02058 }
02059 
02060 static gchar parse_key[32];
02061 static gchar * parse_value;
02062 
02063 static void parse_next (FILE * handle)
02064 {
02065     gchar * found;
02066 
02067     parse_value = NULL;
02068 
02069     if (fgets (parse_key, sizeof parse_key, handle) == NULL)
02070         return;
02071 
02072     found = strchr (parse_key, ' ');
02073 
02074     if (found == NULL)
02075         return;
02076 
02077     * found = 0;
02078     parse_value = found + 1;
02079 
02080     found = strchr (parse_value, '\n');
02081 
02082     if (found != NULL)
02083         * found = 0;
02084 }
02085 
02086 static gboolean parse_integer (const gchar * key, gint * value)
02087 {
02088     return (parse_value != NULL && ! strcmp (parse_key, key) && sscanf
02089      (parse_value, "%d", value) == 1);
02090 }
02091 
02092 void playlist_load_state (void)
02093 {
02094     gchar scratch[512];
02095     FILE * handle;
02096     gint playlist_num;
02097 
02098     snprintf (scratch, sizeof scratch, "%s/" STATE_FILE,
02099      get_path (AUD_PATH_USER_DIR));
02100     handle = fopen (scratch, "r");
02101 
02102     if (handle == NULL)
02103         return;
02104 
02105     parse_next (handle);
02106 
02107     if (parse_integer ("active", & playlist_num))
02108     {
02109         playlist_set_active (playlist_num);
02110         parse_next (handle);
02111     }
02112 
02113     if (parse_integer ("playing", & playlist_num))
02114     {
02115         playlist_set_playing (playlist_num);
02116         parse_next (handle);
02117     }
02118 
02119     while (parse_integer ("playlist", & playlist_num) && playlist_num >= 0 &&
02120      playlist_num < index_count (playlists))
02121     {
02122         Playlist * playlist = index_get (playlists, playlist_num);
02123         gint entries = index_count (playlist->entries), position, count;
02124 
02125         parse_next (handle);
02126 
02127         if (parse_integer ("position", & position))
02128             parse_next (handle);
02129 
02130         if (position >= 0 && position < entries)
02131             playlist->position = index_get (playlist->entries, position);
02132 
02133         gint obsolete = 0;
02134         if (parse_integer ("shuffled", & obsolete)) /* compatibility with 2.3 */
02135             parse_next (handle);
02136 
02137         if (parse_integer ("last-shuffled", & playlist->last_shuffle_num))
02138             parse_next (handle);
02139         else /* compatibility with 2.3 beta */
02140             playlist->last_shuffle_num = obsolete;
02141 
02142         for (count = 0; count < entries; count ++)
02143         {
02144             Entry * entry = index_get (playlist->entries, count);
02145             if (parse_integer ("S", & entry->shuffle_num))
02146                 parse_next (handle);
02147         }
02148     }
02149 
02150     fclose (handle);
02151 }