Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
adder.c
Go to the documentation of this file.
00001 /*
00002  * adder.c
00003  * Copyright 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 <dirent.h>
00023 #include <pthread.h>
00024 #include <string.h>
00025 #include <sys/stat.h>
00026 
00027 #include <gtk/gtk.h>
00028 
00029 #include <libaudcore/audstrings.h>
00030 #include <libaudcore/hook.h>
00031 
00032 #include "config.h"
00033 #include "i18n.h"
00034 #include "playback.h"
00035 #include "playlist.h"
00036 #include "plugins.h"
00037 #include "main.h"
00038 #include "misc.h"
00039 
00040 typedef struct {
00041     int playlist_id, at;
00042     bool_t play;
00043     Index * filenames, * tuples;
00044     PlaylistFilterFunc filter;
00045     void * user;
00046 } AddTask;
00047 
00048 typedef struct {
00049     int playlist_id, at;
00050     bool_t play;
00051     Index * filenames, * tuples, * decoders;
00052 } AddResult;
00053 
00054 static GList * add_tasks = NULL;
00055 static GList * add_results = NULL;
00056 static int current_playlist_id = -1;
00057 
00058 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
00059 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
00060 static bool_t add_quit;
00061 static pthread_t add_thread;
00062 static int add_source = 0;
00063 
00064 static int status_source = 0;
00065 static char status_path[512];
00066 static int status_count;
00067 static GtkWidget * status_window = NULL, * status_path_label,
00068  * status_count_label;
00069 
00070 static bool_t status_cb (void * unused)
00071 {
00072     if (! headless && ! status_window)
00073     {
00074         status_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
00075         gtk_window_set_type_hint ((GtkWindow *) status_window,
00076          GDK_WINDOW_TYPE_HINT_DIALOG);
00077         gtk_window_set_title ((GtkWindow *) status_window, _("Searching ..."));
00078         gtk_window_set_resizable ((GtkWindow *) status_window, FALSE);
00079         gtk_container_set_border_width ((GtkContainer *) status_window, 6);
00080 
00081         GtkWidget * vbox = gtk_vbox_new (FALSE, 6);
00082         gtk_container_add ((GtkContainer *) status_window, vbox);
00083 
00084         status_path_label = gtk_label_new (NULL);
00085 #if GTK_CHECK_VERSION (3, 0, 0)
00086         gtk_label_set_width_chars ((GtkLabel *) status_path_label, 40);
00087         gtk_label_set_max_width_chars ((GtkLabel *) status_path_label, 40);
00088 #else
00089         gtk_widget_set_size_request (status_path_label, 320, -1);
00090 #endif
00091         gtk_label_set_ellipsize ((GtkLabel *) status_path_label,
00092          PANGO_ELLIPSIZE_MIDDLE);
00093         gtk_box_pack_start ((GtkBox *) vbox, status_path_label, FALSE, FALSE, 0);
00094 
00095         status_count_label = gtk_label_new (NULL);
00096 #if GTK_CHECK_VERSION (3, 0, 0)
00097         gtk_label_set_width_chars ((GtkLabel *) status_count_label, 40);
00098         gtk_label_set_max_width_chars ((GtkLabel *) status_count_label, 40);
00099 #else
00100         gtk_widget_set_size_request (status_count_label, 320, -1);
00101 #endif
00102         gtk_box_pack_start ((GtkBox *) vbox, status_count_label, FALSE, FALSE, 0);
00103 
00104         gtk_widget_show_all (status_window);
00105 
00106         g_signal_connect (status_window, "destroy", (GCallback)
00107          gtk_widget_destroyed, & status_window);
00108     }
00109 
00110     pthread_mutex_lock (& mutex);
00111 
00112     char scratch[128];
00113     snprintf (scratch, sizeof scratch, dngettext (PACKAGE, "%d file found",
00114      "%d files found", status_count), status_count);
00115 
00116     if (headless)
00117     {
00118         printf ("Searching, %s ...\r", scratch);
00119         fflush (stdout);
00120     }
00121     else
00122     {
00123         gtk_label_set_text ((GtkLabel *) status_path_label, status_path);
00124         gtk_label_set_text ((GtkLabel *) status_count_label, scratch);
00125     }
00126 
00127     pthread_mutex_unlock (& mutex);
00128     return TRUE;
00129 }
00130 
00131 static void status_update (const char * filename, int found)
00132 {
00133     pthread_mutex_lock (& mutex);
00134 
00135     snprintf (status_path, sizeof status_path, "%s", filename);
00136     status_count = found;
00137 
00138     if (! status_source)
00139         status_source = g_timeout_add (250, status_cb, NULL);
00140 
00141     pthread_mutex_unlock (& mutex);
00142 }
00143 
00144 static void status_done_locked (void)
00145 {
00146     if (status_source)
00147     {
00148         g_source_remove (status_source);
00149         status_source = 0;
00150     }
00151 
00152     if (headless)
00153         printf ("\n");
00154     else if (status_window)
00155         gtk_widget_destroy (status_window);
00156 }
00157 
00158 static void index_free_filenames (Index * filenames)
00159 {
00160     int count = index_count (filenames);
00161     for (int i = 0; i < count; i ++)
00162         str_unref (index_get (filenames, i));
00163 
00164     index_free (filenames);
00165 }
00166 
00167 static void index_free_tuples (Index * tuples)
00168 {
00169     int count = index_count (tuples);
00170     for (int i = 0; i < count; i ++)
00171     {
00172         Tuple * tuple = index_get (tuples, i);
00173         if (tuple)
00174             tuple_unref (tuple);
00175     }
00176 
00177     index_free (tuples);
00178 }
00179 
00180 static AddTask * add_task_new (int playlist_id, int at, bool_t play,
00181  Index * filenames, Index * tuples, PlaylistFilterFunc filter,
00182  void * user)
00183 {
00184     AddTask * task = g_slice_new (AddTask);
00185     task->playlist_id = playlist_id;
00186     task->at = at;
00187     task->play = play;
00188     task->filenames = filenames;
00189     task->tuples = tuples;
00190     task->filter = filter;
00191     task->user = user;
00192     return task;
00193 }
00194 
00195 static void add_task_free (AddTask * task)
00196 {
00197     if (task->filenames)
00198         index_free_filenames (task->filenames);
00199     if (task->tuples)
00200         index_free_tuples (task->tuples);
00201 
00202     g_slice_free (AddTask, task);
00203 }
00204 
00205 static AddResult * add_result_new (int playlist_id, int at, bool_t play)
00206 {
00207     AddResult * result = g_slice_new (AddResult);
00208     result->playlist_id = playlist_id;
00209     result->at = at;
00210     result->play = play;
00211     result->filenames = index_new ();
00212     result->tuples = index_new ();
00213     result->decoders = index_new ();
00214     return result;
00215 }
00216 
00217 static void add_result_free (AddResult * result)
00218 {
00219     if (result->filenames)
00220         index_free_filenames (result->filenames);
00221     if (result->tuples)
00222         index_free_tuples (result->tuples);
00223     if (result->decoders)
00224         index_free (result->decoders);
00225 
00226     g_slice_free (AddResult, result);
00227 }
00228 
00229 static void add_file (char * filename, Tuple * tuple, PluginHandle * decoder,
00230  PlaylistFilterFunc filter, void * user, AddResult * result, bool_t validate)
00231 {
00232     g_return_if_fail (filename);
00233     if (filter && ! filter (filename, user))
00234     {
00235         str_unref (filename);
00236         return;
00237     }
00238 
00239     status_update (filename, index_count (result->filenames));
00240 
00241     if (! tuple && ! decoder)
00242     {
00243         decoder = file_find_decoder (filename, TRUE);
00244         if (validate && ! decoder)
00245         {
00246             str_unref (filename);
00247             return;
00248         }
00249     }
00250 
00251     if (! tuple && decoder && input_plugin_has_subtunes (decoder) && ! strchr
00252      (filename, '?'))
00253         tuple = file_read_tuple (filename, decoder);
00254 
00255     int n_subtunes = tuple ? tuple_get_n_subtunes (tuple) : 0;
00256 
00257     if (n_subtunes)
00258     {
00259         for (int sub = 0; sub < n_subtunes; sub ++)
00260         {
00261             char * subname = str_printf ("%s?%d", filename,
00262              tuple_get_nth_subtune (tuple, sub));
00263             add_file (subname, NULL, decoder, filter, user, result, FALSE);
00264         }
00265 
00266         str_unref (filename);
00267         tuple_unref (tuple);
00268         return;
00269     }
00270 
00271     index_append (result->filenames, filename);
00272     index_append (result->tuples, tuple);
00273     index_append (result->decoders, decoder);
00274 }
00275 
00276 static void add_folder (char * filename, PlaylistFilterFunc filter,
00277  void * user, AddResult * result)
00278 {
00279     g_return_if_fail (filename);
00280     if (filter && ! filter (filename, user))
00281     {
00282         str_unref (filename);
00283         return;
00284     }
00285 
00286     status_update (filename, index_count (result->filenames));
00287 
00288     char * unix_name = uri_to_filename (filename);
00289     if (! unix_name)
00290     {
00291         str_unref (filename);
00292         return;
00293     }
00294 
00295     if (unix_name[strlen (unix_name) - 1] == '/')
00296         unix_name[strlen (unix_name) - 1] = 0;
00297 
00298     GList * files = NULL;
00299     DIR * folder = opendir (unix_name);
00300     if (! folder)
00301         goto FREE;
00302 
00303     struct dirent * entry;
00304     while ((entry = readdir (folder)))
00305     {
00306         if (entry->d_name[0] != '.')
00307             files = g_list_prepend (files, g_strdup_printf ("%s"
00308              G_DIR_SEPARATOR_S "%s", unix_name, entry->d_name));
00309     }
00310 
00311     closedir (folder);
00312     files = g_list_sort (files, (GCompareFunc) string_compare);
00313 
00314     while (files)
00315     {
00316         struct stat info;
00317         if (stat (files->data, & info) < 0)
00318             goto NEXT;
00319 
00320         if (S_ISREG (info.st_mode))
00321         {
00322             char * item_name = filename_to_uri (files->data);
00323             if (item_name)
00324             {
00325                 add_file (str_get (item_name), NULL, NULL, filter, user, result, TRUE);
00326                 g_free (item_name);
00327             }
00328         }
00329         else if (S_ISDIR (info.st_mode))
00330         {
00331             char * item_name = filename_to_uri (files->data);
00332             if (item_name)
00333             {
00334                 add_folder (str_get (item_name), filter, user, result);
00335                 g_free (item_name);
00336             }
00337         }
00338 
00339     NEXT:
00340         g_free (files->data);
00341         files = g_list_delete_link (files, files);
00342     }
00343 
00344 FREE:
00345     str_unref (filename);
00346     g_free (unix_name);
00347 }
00348 
00349 static void add_playlist (char * filename, PlaylistFilterFunc filter,
00350  void * user, AddResult * result)
00351 {
00352     g_return_if_fail (filename);
00353     if (filter && ! filter (filename, user))
00354     {
00355         str_unref (filename);
00356         return;
00357     }
00358 
00359     status_update (filename, index_count (result->filenames));
00360 
00361     char * title = NULL;
00362     Index * filenames, * tuples;
00363     if (! playlist_load (filename, & title, & filenames, & tuples))
00364     {
00365         str_unref (filename);
00366         return;
00367     }
00368 
00369     int count = index_count (filenames);
00370     for (int i = 0; i < count; i ++)
00371         add_file (index_get (filenames, i), tuples ? index_get (tuples, i) :
00372          NULL, NULL, filter, user, result, FALSE);
00373 
00374     str_unref (filename);
00375     str_unref (title);
00376     index_free (filenames);
00377     if (tuples)
00378         index_free (tuples);
00379 }
00380 
00381 static void add_generic (char * filename, Tuple * tuple,
00382  PlaylistFilterFunc filter, void * user, AddResult * result)
00383 {
00384     g_return_if_fail (filename);
00385 
00386     if (tuple)
00387         add_file (filename, tuple, NULL, filter, user, result, FALSE);
00388     else if (vfs_file_test (filename, G_FILE_TEST_IS_DIR))
00389         add_folder (filename, filter, user, result);
00390     else if (filename_is_playlist (filename))
00391         add_playlist (filename, filter, user, result);
00392     else
00393         add_file (filename, NULL, NULL, filter, user, result, FALSE);
00394 }
00395 
00396 static bool_t add_finish (void * unused)
00397 {
00398     pthread_mutex_lock (& mutex);
00399 
00400     while (add_results)
00401     {
00402         AddResult * result = add_results->data;
00403         add_results = g_list_delete_link (add_results, add_results);
00404 
00405         int playlist = playlist_by_unique_id (result->playlist_id);
00406         if (playlist < 0) /* playlist deleted */
00407             goto FREE;
00408 
00409         int count = playlist_entry_count (playlist);
00410         if (result->at < 0 || result->at > count)
00411             result->at = count;
00412 
00413         playlist_entry_insert_batch_raw (playlist, result->at,
00414          result->filenames, result->tuples, result->decoders);
00415         result->filenames = NULL;
00416         result->tuples = NULL;
00417         result->decoders = NULL;
00418 
00419         if (result->play && playlist_entry_count (playlist) > count)
00420         {
00421             playlist_set_playing (playlist);
00422             if (! get_bool (NULL, "shuffle"))
00423                 playlist_set_position (playlist, result->at);
00424 
00425             playback_play (0, FALSE);
00426         }
00427 
00428     FREE:
00429         add_result_free (result);
00430     }
00431 
00432     if (add_source)
00433     {
00434         g_source_remove (add_source);
00435         add_source = 0;
00436     }
00437 
00438     if (! add_tasks)
00439         status_done_locked ();
00440 
00441     pthread_mutex_unlock (& mutex);
00442 
00443     hook_call ("playlist add complete", NULL);
00444     return FALSE;
00445 }
00446 
00447 static void * add_worker (void * unused)
00448 {
00449     pthread_mutex_lock (& mutex);
00450 
00451     while (! add_quit)
00452     {
00453         if (! add_tasks)
00454         {
00455             pthread_cond_wait (& cond, & mutex);
00456             continue;
00457         }
00458 
00459         AddTask * task = add_tasks->data;
00460         add_tasks = g_list_delete_link (add_tasks, add_tasks);
00461 
00462         current_playlist_id = task->playlist_id;
00463         pthread_mutex_unlock (& mutex);
00464 
00465         AddResult * result = add_result_new (task->playlist_id, task->at,
00466          task->play);
00467 
00468         int count = index_count (task->filenames);
00469         if (task->tuples)
00470             count = MIN (count, index_count (task->tuples));
00471 
00472         for (int i = 0; i < count; i ++)
00473         {
00474             add_generic (index_get (task->filenames, i), task->tuples ?
00475              index_get (task->tuples, i) : NULL, task->filter, task->user,
00476              result);
00477 
00478             index_set (task->filenames, i, NULL);
00479             if (task->tuples)
00480                 index_set (task->tuples, i, NULL);
00481         }
00482 
00483         add_task_free (task);
00484 
00485         pthread_mutex_lock (& mutex);
00486         current_playlist_id = -1;
00487 
00488         add_results = g_list_append (add_results, result);
00489 
00490         if (! add_source)
00491             add_source = g_timeout_add (0, add_finish, NULL);
00492     }
00493 
00494     pthread_mutex_unlock (& mutex);
00495     return NULL;
00496 }
00497 
00498 void adder_init (void)
00499 {
00500     pthread_mutex_lock (& mutex);
00501     add_quit = FALSE;
00502     pthread_create (& add_thread, NULL, add_worker, NULL);
00503     pthread_mutex_unlock (& mutex);
00504 }
00505 
00506 void adder_cleanup (void)
00507 {
00508     pthread_mutex_lock (& mutex);
00509     add_quit = TRUE;
00510     pthread_cond_broadcast (& cond);
00511     pthread_mutex_unlock (& mutex);
00512     pthread_join (add_thread, NULL);
00513 
00514     if (add_source)
00515     {
00516         g_source_remove (add_source);
00517         add_source = 0;
00518     }
00519 
00520     status_done_locked ();
00521 }
00522 
00523 void playlist_entry_insert (int playlist, int at, const char * filename,
00524  Tuple * tuple, bool_t play)
00525 {
00526     Index * filenames = index_new ();
00527     Index * tuples = index_new ();
00528     index_append (filenames, str_get (filename));
00529     index_append (tuples, tuple);
00530 
00531     playlist_entry_insert_batch (playlist, at, filenames, tuples, play);
00532 }
00533 
00534 void playlist_entry_insert_batch (int playlist, int at,
00535  Index * filenames, Index * tuples, bool_t play)
00536 {
00537     playlist_entry_insert_filtered (playlist, at, filenames, tuples, NULL, NULL, play);
00538 }
00539 
00540 void playlist_entry_insert_filtered (int playlist, int at,
00541  Index * filenames, Index * tuples, PlaylistFilterFunc filter,
00542  void * user, bool_t play)
00543 {
00544     int playlist_id = playlist_get_unique_id (playlist);
00545     g_return_if_fail (playlist_id >= 0);
00546 
00547     AddTask * task = add_task_new (playlist_id, at, play, filenames, tuples, filter, user);
00548 
00549     pthread_mutex_lock (& mutex);
00550     add_tasks = g_list_append (add_tasks, task);
00551     pthread_cond_broadcast (& cond);
00552     pthread_mutex_unlock (& mutex);
00553 }
00554 
00555 bool_t playlist_add_in_progress (int playlist)
00556 {
00557     int playlist_id = playlist_get_unique_id (playlist);
00558     g_return_val_if_fail (playlist_id >= 0, FALSE);
00559 
00560     pthread_mutex_lock (& mutex);
00561 
00562     for (GList * node = add_tasks; node; node = node->next)
00563     {
00564         if (((AddTask *) node->data)->playlist_id == playlist_id)
00565             goto YES;
00566     }
00567 
00568     if (current_playlist_id == playlist_id)
00569         goto YES;
00570 
00571     for (GList * node = add_results; node; node = node->next)
00572     {
00573         if (((AddResult *) node->data)->playlist_id == playlist_id)
00574             goto YES;
00575     }
00576 
00577     pthread_mutex_unlock (& mutex);
00578     return FALSE;
00579 
00580 YES:
00581     pthread_mutex_unlock (& mutex);
00582     return TRUE;
00583 }