00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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)
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 }