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     if (playback_get_playing () && playlist_num == playlist_get_playing ())
00826         playback_stop ();
00827 
00828     ENTER;
00829     DECLARE_PLAYLIST;
00830     LOOKUP_PLAYLIST;
00831 
00832     index_delete (playlists, playlist_num, 1);
00833     playlist_free (playlist);
00834 
00835     if (! index_count (playlists))
00836         index_insert (playlists, 0, playlist_new (-1));
00837 
00838     number_playlists (playlist_num, index_count (playlists) - playlist_num);
00839 
00840     if (playlist == active_playlist)
00841         active_playlist = index_get (playlists, MIN (playlist_num, index_count
00842          (playlists) - 1));
00843     if (playlist == playing_playlist)
00844         playing_playlist = NULL;
00845 
00846     PLAYLIST_HAS_CHANGED (-1, 0, 0);
00847     LEAVE;
00848 }
00849 
00850 int playlist_get_unique_id (int playlist_num)
00851 {
00852     ENTER;
00853     DECLARE_PLAYLIST;
00854     LOOKUP_PLAYLIST_RET (-1);
00855 
00856     int unique_id = playlist->unique_id;
00857 
00858     LEAVE_RET (unique_id);
00859 }
00860 
00861 int playlist_by_unique_id (int id)
00862 {
00863     ENTER;
00864 
00865     Playlist * p = g_hash_table_lookup (unique_id_table, GINT_TO_POINTER (id));
00866     int num = p ? p->number : -1;
00867 
00868     LEAVE_RET (num);
00869 }
00870 
00871 void playlist_set_filename (int playlist_num, const char * filename)
00872 {
00873     ENTER;
00874     DECLARE_PLAYLIST;
00875     LOOKUP_PLAYLIST;
00876 
00877     str_unref (playlist->filename);
00878     playlist->filename = str_get (filename);
00879     playlist->modified = TRUE;
00880 
00881     METADATA_HAS_CHANGED (-1, 0, 0);
00882     LEAVE;
00883 }
00884 
00885 char * playlist_get_filename (int playlist_num)
00886 {
00887     ENTER;
00888     DECLARE_PLAYLIST;
00889     LOOKUP_PLAYLIST_RET (NULL);
00890 
00891     char * filename = str_ref (playlist->filename);
00892 
00893     LEAVE_RET (filename);
00894 }
00895 
00896 void playlist_set_title (int playlist_num, const char * title)
00897 {
00898     ENTER;
00899     DECLARE_PLAYLIST;
00900     LOOKUP_PLAYLIST;
00901 
00902     str_unref (playlist->title);
00903     playlist->title = str_get (title);
00904     playlist->modified = TRUE;
00905 
00906     METADATA_HAS_CHANGED (-1, 0, 0);
00907     LEAVE;
00908 }
00909 
00910 char * playlist_get_title (int playlist_num)
00911 {
00912     ENTER;
00913     DECLARE_PLAYLIST;
00914     LOOKUP_PLAYLIST_RET (NULL);
00915 
00916     char * title = str_ref (playlist->title);
00917 
00918     LEAVE_RET (title);
00919 }
00920 
00921 void playlist_set_modified (int playlist_num, bool_t modified)
00922 {
00923     ENTER;
00924     DECLARE_PLAYLIST;
00925     LOOKUP_PLAYLIST;
00926 
00927     playlist->modified = modified;
00928 
00929     LEAVE;
00930 }
00931 
00932 bool_t playlist_get_modified (int playlist_num)
00933 {
00934     ENTER;
00935     DECLARE_PLAYLIST;
00936     LOOKUP_PLAYLIST_RET (FALSE);
00937 
00938     bool_t modified = playlist->modified;
00939 
00940     LEAVE_RET (modified);
00941 }
00942 
00943 void playlist_set_active (int playlist_num)
00944 {
00945     ENTER;
00946     DECLARE_PLAYLIST;
00947     LOOKUP_PLAYLIST;
00948 
00949     bool_t changed = FALSE;
00950 
00951     if (playlist != active_playlist)
00952     {
00953         changed = TRUE;
00954         active_playlist = playlist;
00955     }
00956 
00957     LEAVE;
00958 
00959     if (changed)
00960         hook_call ("playlist activate", NULL);
00961 }
00962 
00963 int playlist_get_active (void)
00964 {
00965     ENTER;
00966     int list = active_playlist ? active_playlist->number : -1;
00967     LEAVE_RET (list);
00968 }
00969 
00970 void playlist_set_playing (int playlist_num)
00971 {
00972     if (playback_get_playing ())
00973         playback_stop ();
00974 
00975     ENTER;
00976     DECLARE_PLAYLIST;
00977 
00978     if (playlist_num < 0)
00979         playlist = NULL;
00980     else
00981         LOOKUP_PLAYLIST;
00982 
00983     playing_playlist = playlist;
00984 
00985     LEAVE;
00986 
00987     hook_call ("playlist set playing", NULL);
00988 }
00989 
00990 int playlist_get_playing (void)
00991 {
00992     ENTER;
00993     int list = playing_playlist ? playing_playlist->number: -1;
00994     LEAVE_RET (list);
00995 }
00996 
00997 int playlist_get_blank (void)
00998 {
00999     int list = playlist_get_active ();
01000     char * title = playlist_get_title (list);
01001 
01002     if (strcmp (title, _(default_title)) || playlist_entry_count (list) > 0)
01003     {
01004         list = playlist_count ();
01005         playlist_insert (list);
01006     }
01007 
01008     str_unref (title);
01009     return list;
01010 }
01011 
01012 int playlist_get_temporary (void)
01013 {
01014     int list, count = playlist_count ();
01015     bool_t found = FALSE;
01016 
01017     for (list = 0; list < count; list ++)
01018     {
01019         char * title = playlist_get_title (list);
01020         found = ! strcmp (title, _(temp_title));
01021         str_unref (title);
01022 
01023         if (found)
01024             break;
01025     }
01026 
01027     if (! found)
01028     {
01029         list = playlist_get_blank ();
01030         playlist_set_title (list, _(temp_title));
01031     }
01032 
01033     return list;
01034 }
01035 
01036 /* If we are already at the song or it is already at the top of the shuffle
01037  * list, we let it be.  Otherwise, we move it to the top. */
01038 static void set_position (Playlist * playlist, Entry * entry)
01039 {
01040     if (entry == playlist->position)
01041         return;
01042 
01043     playlist->position = entry;
01044 
01045     if (! entry)
01046         return;
01047 
01048     if (! entry->shuffle_num || entry->shuffle_num != playlist->last_shuffle_num)
01049     {
01050         playlist->last_shuffle_num ++;
01051         entry->shuffle_num = playlist->last_shuffle_num;
01052     }
01053 }
01054 
01055 int playlist_entry_count (int playlist_num)
01056 {
01057     ENTER;
01058     DECLARE_PLAYLIST;
01059     LOOKUP_PLAYLIST_RET (0);
01060 
01061     int count = index_count (playlist->entries);
01062 
01063     LEAVE_RET (count);
01064 }
01065 
01066 void playlist_entry_insert_batch_raw (int playlist_num, int at,
01067  Index * filenames, Index * tuples, Index * decoders)
01068 {
01069     ENTER;
01070     DECLARE_PLAYLIST;
01071     LOOKUP_PLAYLIST;
01072 
01073     int entries = index_count (playlist->entries);
01074 
01075     if (at < 0 || at > entries)
01076         at = entries;
01077 
01078     int number = index_count (filenames);
01079 
01080     Index * add = index_new ();
01081     index_allocate (add, number);
01082 
01083     for (int i = 0; i < number; i ++)
01084     {
01085         char * filename = index_get (filenames, i);
01086         Tuple * tuple = tuples ? index_get (tuples, i) : NULL;
01087         PluginHandle * decoder = decoders ? index_get (decoders, i) : NULL;
01088         index_append (add, entry_new (filename, tuple, decoder));
01089     }
01090 
01091     index_free (filenames);
01092     if (decoders)
01093         index_free (decoders);
01094     if (tuples)
01095         index_free (tuples);
01096 
01097     number = index_count (add);
01098     index_merge_insert (playlist->entries, at, add);
01099     index_free (add);
01100 
01101     number_entries (playlist, at, entries + number - at);
01102 
01103     for (int count = 0; count < number; count ++)
01104     {
01105         Entry * entry = index_get (playlist->entries, at + count);
01106         playlist->total_length += entry->length;
01107     }
01108 
01109     PLAYLIST_HAS_CHANGED (playlist->number, at, number);
01110     LEAVE;
01111 }
01112 
01113 void playlist_entry_delete (int playlist_num, int at, int number)
01114 {
01115     if (playback_get_playing () && playlist_num == playlist_get_playing () &&
01116      playlist_get_position (playlist_num) >= at && playlist_get_position
01117      (playlist_num) < at + number)
01118         playback_stop ();
01119 
01120     ENTER;
01121     DECLARE_PLAYLIST;
01122     LOOKUP_PLAYLIST;
01123 
01124     int entries = index_count (playlist->entries);
01125 
01126     if (at < 0 || at > entries)
01127         at = entries;
01128     if (number < 0 || number > entries - at)
01129         number = entries - at;
01130 
01131     if (playlist->position && playlist->position->number >= at &&
01132      playlist->position->number < at + number)
01133         set_position (playlist, NULL);
01134 
01135     for (int count = 0; count < number; count ++)
01136     {
01137         Entry * entry = index_get (playlist->entries, at + count);
01138 
01139         if (entry->queued)
01140             playlist->queued = g_list_remove (playlist->queued, entry);
01141 
01142         if (entry->selected)
01143         {
01144             playlist->selected_count --;
01145             playlist->selected_length -= entry->length;
01146         }
01147 
01148         playlist->total_length -= entry->length;
01149         entry_free (entry);
01150     }
01151 
01152     index_delete (playlist->entries, at, number);
01153     number_entries (playlist, at, entries - at - number);
01154 
01155     PLAYLIST_HAS_CHANGED (playlist->number, at, 0);
01156     LEAVE;
01157 }
01158 
01159 char * playlist_entry_get_filename (int playlist_num, int entry_num)
01160 {
01161     ENTER;
01162     DECLARE_PLAYLIST_ENTRY;
01163     LOOKUP_PLAYLIST_ENTRY_RET (NULL);
01164 
01165     char * filename = str_ref (entry->filename);
01166 
01167     LEAVE_RET (filename);
01168 }
01169 
01170 PluginHandle * playlist_entry_get_decoder (int playlist_num, int entry_num, bool_t fast)
01171 {
01172     ENTER;
01173 
01174     Entry * entry = get_entry (playlist_num, entry_num, ! fast, FALSE);
01175     PluginHandle * decoder = entry ? entry->decoder : NULL;
01176 
01177     LEAVE_RET (decoder);
01178 }
01179 
01180 Tuple * playlist_entry_get_tuple (int playlist_num, int entry_num, bool_t fast)
01181 {
01182     ENTER;
01183 
01184     Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast);
01185     Tuple * tuple = entry ? entry->tuple : NULL;
01186 
01187     if (tuple)
01188         tuple_ref (tuple);
01189 
01190     LEAVE_RET (tuple);
01191 }
01192 
01193 char * playlist_entry_get_title (int playlist_num, int entry_num, bool_t fast)
01194 {
01195     ENTER;
01196 
01197     Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast);
01198     char * title = entry ? str_ref (entry->formatted ? entry->formatted : entry->title) : NULL;
01199 
01200     LEAVE_RET (title);
01201 }
01202 
01203 void playlist_entry_describe (int playlist_num, int entry_num,
01204  char * * title, char * * artist, char * * album, bool_t fast)
01205 {
01206     ENTER;
01207 
01208     Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast);
01209     * title = (entry && entry->title) ? str_ref (entry->title) : NULL;
01210     * artist = (entry && entry->artist) ? str_ref (entry->artist) : NULL;
01211     * album = (entry && entry->album) ? str_ref (entry->album) : NULL;
01212 
01213     LEAVE;
01214 }
01215 
01216 int playlist_entry_get_length (int playlist_num, int entry_num, bool_t fast)
01217 {
01218     ENTER;
01219 
01220     Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast);
01221     int length = entry ? entry->length : 0;
01222 
01223     LEAVE_RET (length);
01224 }
01225 
01226 void playlist_set_position (int playlist_num, int entry_num)
01227 {
01228     if (playback_get_playing () && playlist_num == playlist_get_playing ())
01229         playback_stop ();
01230 
01231     ENTER;
01232     DECLARE_PLAYLIST_ENTRY;
01233 
01234     if (entry_num == -1)
01235     {
01236         LOOKUP_PLAYLIST;
01237         entry = NULL;
01238     }
01239     else
01240         LOOKUP_PLAYLIST_ENTRY;
01241 
01242     set_position (playlist, entry);
01243     LEAVE;
01244 
01245     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
01246 }
01247 
01248 int playlist_get_position (int playlist_num)
01249 {
01250     ENTER;
01251     DECLARE_PLAYLIST;
01252     LOOKUP_PLAYLIST_RET (-1);
01253 
01254     int position = playlist->position ? playlist->position->number : -1;
01255 
01256     LEAVE_RET (position);
01257 }
01258 
01259 void playlist_entry_set_selected (int playlist_num, int entry_num,
01260  bool_t selected)
01261 {
01262     ENTER;
01263     DECLARE_PLAYLIST_ENTRY;
01264     LOOKUP_PLAYLIST_ENTRY;
01265 
01266     if (entry->selected == selected)
01267         LEAVE_RET_VOID;
01268 
01269     entry->selected = selected;
01270 
01271     if (selected)
01272     {
01273         playlist->selected_count++;
01274         playlist->selected_length += entry->length;
01275     }
01276     else
01277     {
01278         playlist->selected_count--;
01279         playlist->selected_length -= entry->length;
01280     }
01281 
01282     SELECTION_HAS_CHANGED (playlist->number, entry_num, 1);
01283     LEAVE;
01284 }
01285 
01286 bool_t playlist_entry_get_selected (int playlist_num, int entry_num)
01287 {
01288     ENTER;
01289     DECLARE_PLAYLIST_ENTRY;
01290     LOOKUP_PLAYLIST_ENTRY_RET (FALSE);
01291 
01292     bool_t selected = entry->selected;
01293 
01294     LEAVE_RET (selected);
01295 }
01296 
01297 int playlist_selected_count (int playlist_num)
01298 {
01299     ENTER;
01300     DECLARE_PLAYLIST;
01301     LOOKUP_PLAYLIST_RET (0);
01302 
01303     int selected_count = playlist->selected_count;
01304 
01305     LEAVE_RET (selected_count);
01306 }
01307 
01308 void playlist_select_all (int playlist_num, bool_t selected)
01309 {
01310     ENTER;
01311     DECLARE_PLAYLIST;
01312     LOOKUP_PLAYLIST;
01313 
01314     int entries = index_count (playlist->entries);
01315     int first = entries, last = 0;
01316 
01317     for (int count = 0; count < entries; count ++)
01318     {
01319         Entry * entry = index_get (playlist->entries, count);
01320 
01321         if ((selected && ! entry->selected) || (entry->selected && ! selected))
01322         {
01323             entry->selected = selected;
01324             first = MIN (first, entry->number);
01325             last = entry->number;
01326         }
01327     }
01328 
01329     if (selected)
01330     {
01331         playlist->selected_count = entries;
01332         playlist->selected_length = playlist->total_length;
01333     }
01334     else
01335     {
01336         playlist->selected_count = 0;
01337         playlist->selected_length = 0;
01338     }
01339 
01340     if (first < entries)
01341         SELECTION_HAS_CHANGED (playlist->number, first, last + 1 - first);
01342 
01343     LEAVE;
01344 }
01345 
01346 int playlist_shift (int playlist_num, int entry_num, int distance)
01347 {
01348     ENTER;
01349     DECLARE_PLAYLIST_ENTRY;
01350     LOOKUP_PLAYLIST_ENTRY_RET (0);
01351 
01352     if (! entry->selected || ! distance)
01353         LEAVE_RET (0);
01354 
01355     int entries = index_count (playlist->entries);
01356     int shift = 0, center, top, bottom;
01357 
01358     if (distance < 0)
01359     {
01360         for (center = entry_num; center > 0 && shift > distance; )
01361         {
01362             entry = index_get (playlist->entries, -- center);
01363             if (! entry->selected)
01364                 shift --;
01365         }
01366     }
01367     else
01368     {
01369         for (center = entry_num + 1; center < entries && shift < distance; )
01370         {
01371             entry = index_get (playlist->entries, center ++);
01372             if (! entry->selected)
01373                 shift ++;
01374         }
01375     }
01376 
01377     top = bottom = center;
01378 
01379     for (int i = 0; i < top; i ++)
01380     {
01381         entry = index_get (playlist->entries, i);
01382         if (entry->selected)
01383             top = i;
01384     }
01385 
01386     for (int i = entries; i > bottom; i --)
01387     {
01388         entry = index_get (playlist->entries, i - 1);
01389         if (entry->selected)
01390             bottom = i;
01391     }
01392 
01393     Index * temp = index_new ();
01394 
01395     for (int i = top; i < center; i ++)
01396     {
01397         entry = index_get (playlist->entries, i);
01398         if (! entry->selected)
01399             index_append (temp, entry);
01400     }
01401 
01402     for (int i = top; i < bottom; i ++)
01403     {
01404         entry = index_get (playlist->entries, i);
01405         if (entry->selected)
01406             index_append (temp, entry);
01407     }
01408 
01409     for (int i = center; i < bottom; i ++)
01410     {
01411         entry = index_get (playlist->entries, i);
01412         if (! entry->selected)
01413             index_append (temp, entry);
01414     }
01415 
01416     index_copy_set (temp, 0, playlist->entries, top, bottom - top);
01417 
01418     number_entries (playlist, top, bottom - top);
01419     PLAYLIST_HAS_CHANGED (playlist->number, top, bottom - top);
01420 
01421     LEAVE_RET (shift);
01422 }
01423 
01424 void playlist_delete_selected (int playlist_num)
01425 {
01426     if (playback_get_playing () && playlist_num == playlist_get_playing () &&
01427      playlist_get_position (playlist_num) >= 0 && playlist_entry_get_selected
01428      (playlist_num, playlist_get_position (playlist_num)))
01429         playback_stop ();
01430 
01431     ENTER;
01432     DECLARE_PLAYLIST;
01433     LOOKUP_PLAYLIST;
01434 
01435     if (! playlist->selected_count)
01436         LEAVE_RET_VOID;
01437 
01438     int entries = index_count (playlist->entries);
01439 
01440     Index * others = index_new ();
01441     index_allocate (others, entries - playlist->selected_count);
01442 
01443     if (playlist->position && playlist->position->selected)
01444         set_position (playlist, NULL);
01445 
01446     int before = 0, after = 0;
01447     bool_t found = FALSE;
01448 
01449     for (int count = 0; count < entries; count++)
01450     {
01451         Entry * entry = index_get (playlist->entries, count);
01452 
01453         if (entry->selected)
01454         {
01455             if (entry->queued)
01456                 playlist->queued = g_list_remove (playlist->queued, entry);
01457 
01458             playlist->total_length -= entry->length;
01459             entry_free (entry);
01460 
01461             found = TRUE;
01462             after = 0;
01463         }
01464         else
01465         {
01466             index_append (others, entry);
01467 
01468             if (found)
01469                 after ++;
01470             else
01471                 before ++;
01472         }
01473     }
01474 
01475     index_free (playlist->entries);
01476     playlist->entries = others;
01477 
01478     playlist->selected_count = 0;
01479     playlist->selected_length = 0;
01480 
01481     number_entries (playlist, before, index_count (playlist->entries) - before);
01482     PLAYLIST_HAS_CHANGED (playlist->number, before, index_count
01483      (playlist->entries) - after - before);
01484     LEAVE;
01485 }
01486 
01487 void playlist_reverse (int playlist_num)
01488 {
01489     ENTER;
01490     DECLARE_PLAYLIST;
01491     LOOKUP_PLAYLIST;
01492 
01493     int entries = index_count (playlist->entries);
01494 
01495     Index * reversed = index_new ();
01496     index_allocate (reversed, entries);
01497 
01498     for (int count = entries; count --; )
01499         index_append (reversed, index_get (playlist->entries, count));
01500 
01501     index_free (playlist->entries);
01502     playlist->entries = reversed;
01503 
01504     number_entries (playlist, 0, entries);
01505     PLAYLIST_HAS_CHANGED (playlist->number, 0, entries);
01506     LEAVE;
01507 }
01508 
01509 void playlist_randomize (int playlist_num)
01510 {
01511     ENTER;
01512     DECLARE_PLAYLIST;
01513     LOOKUP_PLAYLIST;
01514 
01515     int entries = index_count (playlist->entries);
01516 
01517     for (int i = 0; i < entries; i ++)
01518     {
01519         int j = i + rand () % (entries - i);
01520 
01521         struct entry * entry = index_get (playlist->entries, j);
01522         index_set (playlist->entries, j, index_get (playlist->entries, i));
01523         index_set (playlist->entries, i, entry);
01524     }
01525 
01526     number_entries (playlist, 0, entries);
01527     PLAYLIST_HAS_CHANGED (playlist->number, 0, entries);
01528     LEAVE;
01529 }
01530 
01531 static int filename_compare (const void * _a, const void * _b, void * compare)
01532 {
01533     const Entry * a = _a, * b = _b;
01534 
01535     int diff = ((int (*) (const char * a, const char * b)) compare)
01536      (a->filename, b->filename);
01537 
01538     if (diff)
01539         return diff;
01540 
01541     /* preserve order of "equal" entries */
01542     return a->number - b->number;
01543 }
01544 
01545 static int tuple_compare (const void * _a, const void * _b, void * compare)
01546 {
01547     const Entry * a = _a, * b = _b;
01548 
01549     if (! a->tuple)
01550         return b->tuple ? -1 : 0;
01551     if (! b->tuple)
01552         return 1;
01553 
01554     int diff = ((int (*) (const Tuple * a, const Tuple * b)) compare)
01555      (a->tuple, b->tuple);
01556 
01557     if (diff)
01558         return diff;
01559 
01560     /* preserve order of "equal" entries */
01561     return a->number - b->number;
01562 }
01563 
01564 static int title_compare (const void * _a, const void * _b, void * compare)
01565 {
01566     const Entry * a = _a, * b = _b;
01567 
01568     int diff = ((int (*) (const char * a, const char * b)) compare)
01569      (a->formatted ? a->formatted : a->filename,
01570       b->formatted ? b->formatted : b->filename);
01571 
01572     if (diff)
01573         return diff;
01574 
01575     /* preserve order of "equal" entries */
01576     return a->number - b->number;
01577 }
01578 
01579 static void sort (Playlist * playlist, int (* compare) (const void * a,
01580  const void * b, void * inner), void * inner)
01581 {
01582     index_sort_with_data (playlist->entries, compare, inner);
01583     number_entries (playlist, 0, index_count (playlist->entries));
01584 
01585     PLAYLIST_HAS_CHANGED (playlist->number, 0, index_count (playlist->entries));
01586 }
01587 
01588 static void sort_selected (Playlist * playlist, int (* compare) (const void *
01589  a, const void * b, void * inner), void * inner)
01590 {
01591     int entries = index_count (playlist->entries);
01592 
01593     Index * selected = index_new ();
01594     index_allocate (selected, playlist->selected_count);
01595 
01596     for (int count = 0; count < entries; count++)
01597     {
01598         Entry * entry = index_get (playlist->entries, count);
01599         if (entry->selected)
01600             index_append (selected, entry);
01601     }
01602 
01603     index_sort_with_data (selected, compare, inner);
01604 
01605     int count2 = 0;
01606     for (int count = 0; count < entries; count++)
01607     {
01608         Entry * entry = index_get (playlist->entries, count);
01609         if (entry->selected)
01610             index_set (playlist->entries, count, index_get (selected, count2 ++));
01611     }
01612 
01613     index_free (selected);
01614 
01615     number_entries (playlist, 0, entries);
01616     PLAYLIST_HAS_CHANGED (playlist->number, 0, entries);
01617 }
01618 
01619 static bool_t entries_are_scanned (Playlist * playlist, bool_t selected)
01620 {
01621     int entries = index_count (playlist->entries);
01622     for (int count = 0; count < entries; count ++)
01623     {
01624         Entry * entry = index_get (playlist->entries, count);
01625         if (selected && ! entry->selected)
01626             continue;
01627 
01628         if (! entry->tuple)
01629         {
01630             interface_show_error (_("The playlist cannot be sorted because "
01631              "metadata scanning is still in progress (or has been disabled)."));
01632             return FALSE;
01633         }
01634     }
01635 
01636     return TRUE;
01637 }
01638 
01639 void playlist_sort_by_filename (int playlist_num, int (* compare)
01640  (const char * a, const char * b))
01641 {
01642     ENTER;
01643     DECLARE_PLAYLIST;
01644     LOOKUP_PLAYLIST;
01645 
01646     sort (playlist, filename_compare, (void *) compare);
01647 
01648     LEAVE;
01649 }
01650 
01651 void playlist_sort_by_tuple (int playlist_num, int (* compare)
01652  (const Tuple * a, const Tuple * b))
01653 {
01654     ENTER;
01655     DECLARE_PLAYLIST;
01656     LOOKUP_PLAYLIST;
01657 
01658     if (entries_are_scanned (playlist, FALSE))
01659         sort (playlist, tuple_compare, (void *) compare);
01660 
01661     LEAVE;
01662 }
01663 
01664 void playlist_sort_by_title (int playlist_num, int (* compare) (const char *
01665  a, const char * b))
01666 {
01667     ENTER;
01668     DECLARE_PLAYLIST;
01669     LOOKUP_PLAYLIST;
01670 
01671     if (entries_are_scanned (playlist, FALSE))
01672         sort (playlist, title_compare, (void *) compare);
01673 
01674     LEAVE;
01675 }
01676 
01677 void playlist_sort_selected_by_filename (int playlist_num, int (* compare)
01678  (const char * a, const char * b))
01679 {
01680     ENTER;
01681     DECLARE_PLAYLIST;
01682     LOOKUP_PLAYLIST;
01683 
01684     sort_selected (playlist, filename_compare, (void *) compare);
01685 
01686     LEAVE;
01687 }
01688 
01689 void playlist_sort_selected_by_tuple (int playlist_num, int (* compare)
01690  (const Tuple * a, const Tuple * b))
01691 {
01692     ENTER;
01693     DECLARE_PLAYLIST;
01694     LOOKUP_PLAYLIST;
01695 
01696     if (entries_are_scanned (playlist, TRUE))
01697         sort_selected (playlist, tuple_compare, (void *) compare);
01698 
01699     LEAVE;
01700 }
01701 
01702 void playlist_sort_selected_by_title (int playlist_num, int (* compare)
01703  (const char * a, const char * b))
01704 {
01705     ENTER;
01706     DECLARE_PLAYLIST;
01707     LOOKUP_PLAYLIST;
01708 
01709     if (entries_are_scanned (playlist, TRUE))
01710         sort (playlist, title_compare, (void *) compare);
01711 
01712     LEAVE;
01713 }
01714 
01715 void playlist_reformat_titles (void)
01716 {
01717     ENTER;
01718 
01719     g_free (title_format);
01720     title_format = NULL;
01721 
01722     for (int playlist_num = 0; playlist_num < index_count (playlists);
01723      playlist_num ++)
01724     {
01725         Playlist * playlist = index_get (playlists, playlist_num);
01726         int entries = index_count (playlist->entries);
01727 
01728         for (int count = 0; count < entries; count++)
01729         {
01730             Entry * entry = index_get (playlist->entries, count);
01731             str_unref (entry->formatted);
01732             entry->formatted = entry->tuple ? title_from_tuple (entry->tuple) : NULL;
01733         }
01734 
01735         METADATA_HAS_CHANGED (playlist_num, 0, entries);
01736     }
01737 
01738     LEAVE;
01739 }
01740 
01741 void playlist_trigger_scan (void)
01742 {
01743     ENTER;
01744 
01745     for (int i = 0; i < index_count (playlists); i ++)
01746     {
01747         Playlist * p = index_get (playlists, i);
01748         p->scanning = TRUE;
01749     }
01750 
01751     scan_trigger ();
01752 
01753     LEAVE;
01754 }
01755 
01756 static void playlist_rescan_real (int playlist_num, bool_t selected)
01757 {
01758     ENTER;
01759     DECLARE_PLAYLIST;
01760     LOOKUP_PLAYLIST;
01761 
01762     int entries = index_count (playlist->entries);
01763 
01764     for (int count = 0; count < entries; count ++)
01765     {
01766         Entry * entry = index_get (playlist->entries, count);
01767         if (! selected || entry->selected)
01768         {
01769             entry_set_tuple (playlist, entry, NULL);
01770             entry->failed = FALSE;
01771         }
01772     }
01773 
01774     METADATA_HAS_CHANGED (playlist->number, 0, entries);
01775     LEAVE;
01776 }
01777 
01778 void playlist_rescan (int playlist_num)
01779 {
01780     playlist_rescan_real (playlist_num, FALSE);
01781 }
01782 
01783 void playlist_rescan_selected (int playlist_num)
01784 {
01785     playlist_rescan_real (playlist_num, TRUE);
01786 }
01787 
01788 void playlist_rescan_file (const char * filename)
01789 {
01790     ENTER;
01791 
01792     int num_playlists = index_count (playlists);
01793 
01794     for (int playlist_num = 0; playlist_num < num_playlists; playlist_num ++)
01795     {
01796         Playlist * playlist = index_get (playlists, playlist_num);
01797         int num_entries = index_count (playlist->entries);
01798 
01799         for (int entry_num = 0; entry_num < num_entries; entry_num ++)
01800         {
01801             Entry * entry = index_get (playlist->entries, entry_num);
01802 
01803             if (! strcmp (entry->filename, filename))
01804             {
01805                 entry_set_tuple (playlist, entry, NULL);
01806                 entry->failed = FALSE;
01807 
01808                 METADATA_HAS_CHANGED (playlist_num, entry_num, 1);
01809             }
01810         }
01811     }
01812 
01813     LEAVE;
01814 }
01815 
01816 int64_t playlist_get_total_length (int playlist_num)
01817 {
01818     ENTER;
01819     DECLARE_PLAYLIST;
01820     LOOKUP_PLAYLIST_RET (0);
01821 
01822     int64_t length = playlist->total_length;
01823 
01824     LEAVE_RET (length);
01825 }
01826 
01827 int64_t playlist_get_selected_length (int playlist_num)
01828 {
01829     ENTER;
01830     DECLARE_PLAYLIST;
01831     LOOKUP_PLAYLIST_RET (0);
01832 
01833     int64_t length = playlist->selected_length;
01834 
01835     LEAVE_RET (length);
01836 }
01837 
01838 int playlist_queue_count (int playlist_num)
01839 {
01840     ENTER;
01841     DECLARE_PLAYLIST;
01842     LOOKUP_PLAYLIST_RET (0);
01843 
01844     int count = g_list_length (playlist->queued);
01845 
01846     LEAVE_RET (count);
01847 }
01848 
01849 void playlist_queue_insert (int playlist_num, int at, int entry_num)
01850 {
01851     ENTER;
01852     DECLARE_PLAYLIST_ENTRY;
01853     LOOKUP_PLAYLIST_ENTRY;
01854 
01855     if (entry->queued)
01856         LEAVE_RET_VOID;
01857 
01858     if (at < 0)
01859         playlist->queued = g_list_append (playlist->queued, entry);
01860     else
01861         playlist->queued = g_list_insert (playlist->queued, entry, at);
01862 
01863     entry->queued = TRUE;
01864 
01865     SELECTION_HAS_CHANGED (playlist->number, entry_num, 1);
01866     LEAVE;
01867 }
01868 
01869 void playlist_queue_insert_selected (int playlist_num, int at)
01870 {
01871     ENTER;
01872     DECLARE_PLAYLIST;
01873     LOOKUP_PLAYLIST;
01874 
01875     int entries = index_count(playlist->entries);
01876     int first = entries, last = 0;
01877 
01878     for (int count = 0; count < entries; count++)
01879     {
01880         Entry * entry = index_get (playlist->entries, count);
01881 
01882         if (! entry->selected || entry->queued)
01883             continue;
01884 
01885         if (at < 0)
01886             playlist->queued = g_list_append (playlist->queued, entry);
01887         else
01888             playlist->queued = g_list_insert (playlist->queued, entry, at++);
01889 
01890         entry->queued = TRUE;
01891         first = MIN (first, entry->number);
01892         last = entry->number;
01893     }
01894 
01895     if (first < entries)
01896         SELECTION_HAS_CHANGED (playlist->number, first, last + 1 - first);
01897 
01898     LEAVE;
01899 }
01900 
01901 int playlist_queue_get_entry (int playlist_num, int at)
01902 {
01903     ENTER;
01904     DECLARE_PLAYLIST;
01905     LOOKUP_PLAYLIST_RET (-1);
01906 
01907     GList * node = g_list_nth (playlist->queued, at);
01908     int entry_num = node ? ((Entry *) node->data)->number : -1;
01909 
01910     LEAVE_RET (entry_num);
01911 }
01912 
01913 int playlist_queue_find_entry (int playlist_num, int entry_num)
01914 {
01915     ENTER;
01916     DECLARE_PLAYLIST_ENTRY;
01917     LOOKUP_PLAYLIST_ENTRY_RET (-1);
01918 
01919     int pos = entry->queued ? g_list_index (playlist->queued, entry) : -1;
01920 
01921     LEAVE_RET (pos);
01922 }
01923 
01924 void playlist_queue_delete (int playlist_num, int at, int number)
01925 {
01926     ENTER;
01927     DECLARE_PLAYLIST;
01928     LOOKUP_PLAYLIST;
01929 
01930     int entries = index_count (playlist->entries);
01931     int first = entries, last = 0;
01932 
01933     if (at == 0)
01934     {
01935         while (playlist->queued && number --)
01936         {
01937             Entry * entry = playlist->queued->data;
01938             entry->queued = FALSE;
01939             first = MIN (first, entry->number);
01940             last = entry->number;
01941 
01942             playlist->queued = g_list_delete_link (playlist->queued,
01943              playlist->queued);
01944         }
01945     }
01946     else
01947     {
01948         GList * anchor = g_list_nth (playlist->queued, at - 1);
01949         if (! anchor)
01950             goto DONE;
01951 
01952         while (anchor->next && number --)
01953         {
01954             Entry * entry = anchor->next->data;
01955             entry->queued = FALSE;
01956             first = MIN (first, entry->number);
01957             last = entry->number;
01958 
01959             playlist->queued = g_list_delete_link (playlist->queued,
01960              anchor->next);
01961         }
01962     }
01963 
01964 DONE:
01965     if (first < entries)
01966         SELECTION_HAS_CHANGED (playlist->number, first, last + 1 - first);
01967 
01968     LEAVE;
01969 }
01970 
01971 void playlist_queue_delete_selected (int playlist_num)
01972 {
01973     ENTER;
01974     DECLARE_PLAYLIST;
01975     LOOKUP_PLAYLIST;
01976 
01977     int entries = index_count (playlist->entries);
01978     int first = entries, last = 0;
01979 
01980     for (GList * node = playlist->queued; node; )
01981     {
01982         GList * next = node->next;
01983         Entry * entry = node->data;
01984 
01985         if (entry->selected)
01986         {
01987             entry->queued = FALSE;
01988             playlist->queued = g_list_delete_link (playlist->queued, node);
01989             first = MIN (first, entry->number);
01990             last = entry->number;
01991         }
01992 
01993         node = next;
01994     }
01995 
01996     if (first < entries)
01997         SELECTION_HAS_CHANGED (playlist->number, first, last + 1 - first);
01998 
01999     LEAVE;
02000 }
02001 
02002 static bool_t shuffle_prev (Playlist * playlist)
02003 {
02004     int entries = index_count (playlist->entries);
02005     Entry * found = NULL;
02006 
02007     for (int count = 0; count < entries; count ++)
02008     {
02009         Entry * entry = index_get (playlist->entries, count);
02010 
02011         if (entry->shuffle_num && (! playlist->position ||
02012          entry->shuffle_num < playlist->position->shuffle_num) && (! found
02013          || entry->shuffle_num > found->shuffle_num))
02014             found = entry;
02015     }
02016 
02017     if (! found)
02018         return FALSE;
02019 
02020     playlist->position = found;
02021     return TRUE;
02022 }
02023 
02024 bool_t playlist_prev_song (int playlist_num)
02025 {
02026     if (playback_get_playing () && playlist_num == playlist_get_playing ())
02027         playback_stop ();
02028 
02029     ENTER;
02030     DECLARE_PLAYLIST;
02031     LOOKUP_PLAYLIST_RET (FALSE);
02032 
02033     if (get_bool (NULL, "shuffle"))
02034     {
02035         if (! shuffle_prev (playlist))
02036             LEAVE_RET (FALSE);
02037     }
02038     else
02039     {
02040         if (! playlist->position || playlist->position->number == 0)
02041             LEAVE_RET (FALSE);
02042 
02043         set_position (playlist, index_get (playlist->entries,
02044          playlist->position->number - 1));
02045     }
02046 
02047     LEAVE;
02048 
02049     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
02050     return TRUE;
02051 }
02052 
02053 static bool_t shuffle_next (Playlist * playlist)
02054 {
02055     int entries = index_count (playlist->entries), choice = 0, count;
02056     Entry * found = NULL;
02057 
02058     for (count = 0; count < entries; count ++)
02059     {
02060         Entry * entry = index_get (playlist->entries, count);
02061 
02062         if (! entry->shuffle_num)
02063             choice ++;
02064         else if (playlist->position && entry->shuffle_num >
02065          playlist->position->shuffle_num && (! found || entry->shuffle_num
02066          < found->shuffle_num))
02067             found = entry;
02068     }
02069 
02070     if (found)
02071     {
02072         playlist->position = found;
02073         return TRUE;
02074     }
02075 
02076     if (! choice)
02077         return FALSE;
02078 
02079     choice = rand () % choice;
02080 
02081     for (count = 0; ; count ++)
02082     {
02083         Entry * entry = index_get (playlist->entries, count);
02084 
02085         if (! entry->shuffle_num)
02086         {
02087             if (! choice)
02088             {
02089                 set_position (playlist, entry);
02090                 return TRUE;
02091             }
02092 
02093             choice --;
02094         }
02095     }
02096 }
02097 
02098 static void shuffle_reset (Playlist * playlist)
02099 {
02100     int entries = index_count (playlist->entries);
02101 
02102     playlist->last_shuffle_num = 0;
02103 
02104     for (int count = 0; count < entries; count ++)
02105     {
02106         Entry * entry = index_get (playlist->entries, count);
02107         entry->shuffle_num = 0;
02108     }
02109 }
02110 
02111 bool_t playlist_next_song (int playlist_num, bool_t repeat)
02112 {
02113     if (playback_get_playing () && playlist_num == playlist_get_playing ())
02114         playback_stop ();
02115 
02116     ENTER;
02117     DECLARE_PLAYLIST;
02118     LOOKUP_PLAYLIST_RET (FALSE);
02119 
02120     int entries = index_count(playlist->entries);
02121 
02122     if (! entries)
02123         LEAVE_RET (FALSE);
02124 
02125     if (playlist->queued)
02126     {
02127         set_position (playlist, playlist->queued->data);
02128         playlist->queued = g_list_remove (playlist->queued, playlist->position);
02129         playlist->position->queued = FALSE;
02130     }
02131     else if (get_bool (NULL, "shuffle"))
02132     {
02133         if (! shuffle_next (playlist))
02134         {
02135             if (! repeat)
02136                 LEAVE_RET (FALSE);
02137 
02138             shuffle_reset (playlist);
02139 
02140             if (! shuffle_next (playlist))
02141                 LEAVE_RET (FALSE);
02142         }
02143     }
02144     else
02145     {
02146         if (! playlist->position)
02147             set_position (playlist, index_get (playlist->entries, 0));
02148         else if (playlist->position->number == entries - 1)
02149         {
02150             if (! repeat)
02151                 LEAVE_RET (FALSE);
02152 
02153             set_position (playlist, index_get (playlist->entries, 0));
02154         }
02155         else
02156             set_position (playlist, index_get (playlist->entries,
02157              playlist->position->number + 1));
02158     }
02159 
02160     LEAVE;
02161 
02162     hook_call ("playlist position", GINT_TO_POINTER (playlist_num));
02163     return TRUE;
02164 }
02165 
02166 int playback_entry_get_position (void)
02167 {
02168     ENTER;
02169 
02170     Entry * entry = get_playback_entry (FALSE, FALSE);
02171     int entry_num = entry ? entry->number : -1;
02172 
02173     LEAVE_RET (entry_num);
02174 }
02175 
02176 PluginHandle * playback_entry_get_decoder (void)
02177 {
02178     ENTER;
02179 
02180     Entry * entry = get_playback_entry (TRUE, FALSE);
02181     PluginHandle * decoder = entry ? entry->decoder : NULL;
02182 
02183     LEAVE_RET (decoder);
02184 }
02185 
02186 Tuple * playback_entry_get_tuple (void)
02187 {
02188     ENTER;
02189 
02190     Entry * entry = get_playback_entry (FALSE, TRUE);
02191     Tuple * tuple = entry ? entry->tuple : NULL;
02192 
02193     if (tuple)
02194         tuple_ref (tuple);
02195 
02196     LEAVE_RET (tuple);
02197 }
02198 
02199 char * playback_entry_get_title (void)
02200 {
02201     ENTER;
02202 
02203     Entry * entry = get_playback_entry (FALSE, TRUE);
02204     char * title = entry ? str_ref (entry->formatted ? entry->formatted :
02205      entry->title) : NULL;
02206 
02207     LEAVE_RET (title);
02208 }
02209 
02210 int playback_entry_get_length (void)
02211 {
02212     ENTER;
02213 
02214     Entry * entry = get_playback_entry (FALSE, TRUE);
02215     int length = entry->length;
02216 
02217     LEAVE_RET (length);
02218 }
02219 
02220 void playback_entry_set_tuple (Tuple * tuple)
02221 {
02222     ENTER;
02223     if (! playing_playlist || ! playing_playlist->position)
02224         LEAVE_RET_VOID;
02225 
02226     Entry * entry = playing_playlist->position;
02227     entry_cancel_scan (entry);
02228     entry_set_tuple (playing_playlist, entry, tuple);
02229 
02230     METADATA_HAS_CHANGED (playing_playlist->number, entry->number, 1);
02231     LEAVE;
02232 }
02233 
02234 int playback_entry_get_start_time (void)
02235 {
02236     ENTER;
02237     if (! playing_playlist || ! playing_playlist->position)
02238         LEAVE_RET (0);
02239 
02240     int start = playing_playlist->position->start;
02241     LEAVE_RET (start);
02242 }
02243 
02244 int playback_entry_get_end_time (void)
02245 {
02246     ENTER;
02247     if (! playing_playlist || ! playing_playlist->position)
02248         LEAVE_RET (-1);
02249 
02250     int end = playing_playlist->position->end;
02251     LEAVE_RET (end);
02252 }
02253 
02254 void playlist_save_state (void)
02255 {
02256     ENTER;
02257 
02258     char * path = g_strdup_printf ("%s/" STATE_FILE, get_path (AUD_PATH_USER_DIR));
02259     FILE * handle = fopen (path, "w");
02260     g_free (path);
02261     if (! handle)
02262         LEAVE_RET_VOID;
02263 
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     fprintf (handle, "resume-state %d\n", resume_state);
02269     fprintf (handle, "resume-time %d\n", resume_time);
02270 
02271     fprintf (handle, "active %d\n", active_playlist ? active_playlist->number : -1);
02272     fprintf (handle, "playing %d\n", playing_playlist ? playing_playlist->number : -1);
02273 
02274     for (int playlist_num = 0; playlist_num < index_count (playlists);
02275      playlist_num ++)
02276     {
02277         Playlist * playlist = index_get (playlists, playlist_num);
02278 
02279         fprintf (handle, "playlist %d\n", playlist_num);
02280 
02281         if (playlist->filename)
02282             fprintf (handle, "filename %s\n", playlist->filename);
02283 
02284         fprintf (handle, "position %d\n", playlist->position ?
02285          playlist->position->number : -1);
02286     }
02287 
02288     fclose (handle);
02289     LEAVE;
02290 }
02291 
02292 static char parse_key[512];
02293 static char * parse_value;
02294 
02295 static void parse_next (FILE * handle)
02296 {
02297     parse_value = NULL;
02298 
02299     if (! fgets (parse_key, sizeof parse_key, handle))
02300         return;
02301 
02302     char * space = strchr (parse_key, ' ');
02303     if (! space)
02304         return;
02305 
02306     * space = 0;
02307     parse_value = space + 1;
02308 
02309     char * newline = strchr (parse_value, '\n');
02310     if (newline)
02311         * newline = 0;
02312 }
02313 
02314 static bool_t parse_integer (const char * key, int * value)
02315 {
02316     return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value,
02317      "%d", value) == 1);
02318 }
02319 
02320 static char * parse_string (const char * key)
02321 {
02322     return (parse_value && ! strcmp (parse_key, key)) ? str_get (parse_value) : NULL;
02323 }
02324 
02325 void playlist_load_state (void)
02326 {
02327     ENTER;
02328     int playlist_num;
02329 
02330     char * path = g_strdup_printf ("%s/" STATE_FILE, get_path (AUD_PATH_USER_DIR));
02331     FILE * handle = fopen (path, "r");
02332     g_free (path);
02333     if (! handle)
02334         LEAVE_RET_VOID;
02335 
02336     parse_next (handle);
02337 
02338     if (parse_integer ("resume-state", & resume_state))
02339         parse_next (handle);
02340     if (parse_integer ("resume-time", & resume_time))
02341         parse_next (handle);
02342 
02343     if (parse_integer ("active", & playlist_num))
02344     {
02345         if (! (active_playlist = lookup_playlist (playlist_num)))
02346             active_playlist = index_get (playlists, 0);
02347         parse_next (handle);
02348     }
02349 
02350     if (parse_integer ("playing", & playlist_num))
02351     {
02352         playing_playlist = lookup_playlist (playlist_num);
02353         parse_next (handle);
02354     }
02355 
02356     while (parse_integer ("playlist", & playlist_num) && playlist_num >= 0 &&
02357      playlist_num < index_count (playlists))
02358     {
02359         Playlist * playlist = index_get (playlists, playlist_num);
02360         int entries = index_count (playlist->entries), position;
02361         char * s;
02362 
02363         parse_next (handle);
02364 
02365         if ((s = parse_string ("filename")))
02366         {
02367             str_unref (playlist->filename);
02368             playlist->filename = s;
02369             parse_next (handle);
02370         }
02371 
02372         if (parse_integer ("position", & position))
02373             parse_next (handle);
02374 
02375         if (position >= 0 && position < entries)
02376             set_position (playlist, index_get (playlist->entries, position));
02377     }
02378 
02379     fclose (handle);
02380 
02381     /* clear updates queued during init sequence */
02382 
02383     for (int i = 0; i < index_count (playlists); i ++)
02384     {
02385         Playlist * p = index_get (playlists, i);
02386         memset (& p->last_update, 0, sizeof (Update));
02387         memset (& p->next_update, 0, sizeof (Update));
02388     }
02389 
02390     update_level = 0;
02391 
02392     if (update_source)
02393     {
02394         g_source_remove (update_source);
02395         update_source = 0;
02396     }
02397 
02398     LEAVE;
02399 }
02400 
02401 void playlist_resume (void)
02402 {
02403     if (resume_state == RESUME_PLAY || resume_state == RESUME_PAUSE)
02404         playback_play (resume_time, resume_state == RESUME_PAUSE);
02405 }