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