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