Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
adder.c
Go to the documentation of this file.
1 /*
2  * adder.c
3  * Copyright 2011 John Lindgren
4  *
5  * This file is part of Audacious.
6  *
7  * Audacious is free software: you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation, version 2 or version 3 of the License.
10  *
11  * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * Audacious. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * The Audacious team does not consider modular code linking to Audacious or
19  * using our public API to be a derived work.
20  */
21 
22 #include <dirent.h>
23 #include <pthread.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 
27 #include <gtk/gtk.h>
28 
29 #include <libaudcore/audstrings.h>
30 #include <libaudcore/hook.h>
31 
32 #include "config.h"
33 #include "i18n.h"
34 #include "playback.h"
35 #include "playlist.h"
36 #include "plugins.h"
37 #include "main.h"
38 #include "misc.h"
39 
40 typedef struct {
43  Index * filenames, * tuples;
45  void * user;
46 } AddTask;
47 
48 typedef struct {
51  Index * filenames, * tuples, * decoders;
52 } AddResult;
53 
54 static GList * add_tasks = NULL;
55 static GList * add_results = NULL;
56 static int current_playlist_id = -1;
57 
58 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
59 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
61 static pthread_t add_thread;
62 static int add_source = 0;
63 
64 static int status_source = 0;
65 static char status_path[512];
66 static int status_count;
67 static GtkWidget * status_window = NULL, * status_path_label,
69 
70 static bool_t status_cb (void * unused)
71 {
72  if (! headless && ! status_window)
73  {
74  status_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
75  gtk_window_set_type_hint ((GtkWindow *) status_window,
76  GDK_WINDOW_TYPE_HINT_DIALOG);
77  gtk_window_set_title ((GtkWindow *) status_window, _("Searching ..."));
78  gtk_window_set_resizable ((GtkWindow *) status_window, FALSE);
79  gtk_container_set_border_width ((GtkContainer *) status_window, 6);
80 
81  GtkWidget * vbox = gtk_vbox_new (FALSE, 6);
82  gtk_container_add ((GtkContainer *) status_window, vbox);
83 
84  status_path_label = gtk_label_new (NULL);
85 #if GTK_CHECK_VERSION (3, 0, 0)
86  gtk_label_set_width_chars ((GtkLabel *) status_path_label, 40);
87  gtk_label_set_max_width_chars ((GtkLabel *) status_path_label, 40);
88 #else
89  gtk_widget_set_size_request (status_path_label, 320, -1);
90 #endif
91  gtk_label_set_ellipsize ((GtkLabel *) status_path_label,
92  PANGO_ELLIPSIZE_MIDDLE);
93  gtk_box_pack_start ((GtkBox *) vbox, status_path_label, FALSE, FALSE, 0);
94 
95  status_count_label = gtk_label_new (NULL);
96 #if GTK_CHECK_VERSION (3, 0, 0)
97  gtk_label_set_width_chars ((GtkLabel *) status_count_label, 40);
98  gtk_label_set_max_width_chars ((GtkLabel *) status_count_label, 40);
99 #else
100  gtk_widget_set_size_request (status_count_label, 320, -1);
101 #endif
102  gtk_box_pack_start ((GtkBox *) vbox, status_count_label, FALSE, FALSE, 0);
103 
104  gtk_widget_show_all (status_window);
105 
106  g_signal_connect (status_window, "destroy", (GCallback)
107  gtk_widget_destroyed, & status_window);
108  }
109 
110  pthread_mutex_lock (& mutex);
111 
112  char scratch[128];
113  snprintf (scratch, sizeof scratch, dngettext (PACKAGE, "%d file found",
114  "%d files found", status_count), status_count);
115 
116  if (headless)
117  {
118  printf ("Searching, %s ...\r", scratch);
119  fflush (stdout);
120  }
121  else
122  {
123  gtk_label_set_text ((GtkLabel *) status_path_label, status_path);
124  gtk_label_set_text ((GtkLabel *) status_count_label, scratch);
125  }
126 
127  pthread_mutex_unlock (& mutex);
128  return TRUE;
129 }
130 
131 static void status_update (const char * filename, int found)
132 {
133  pthread_mutex_lock (& mutex);
134 
135  snprintf (status_path, sizeof status_path, "%s", filename);
136  status_count = found;
137 
138  if (! status_source)
139  status_source = g_timeout_add (250, status_cb, NULL);
140 
141  pthread_mutex_unlock (& mutex);
142 }
143 
144 static void status_done_locked (void)
145 {
146  if (status_source)
147  {
148  g_source_remove (status_source);
149  status_source = 0;
150  }
151 
152  if (headless)
153  printf ("\n");
154  else if (status_window)
155  gtk_widget_destroy (status_window);
156 }
157 
158 static void index_free_filenames (Index * filenames)
159 {
160  int count = index_count (filenames);
161  for (int i = 0; i < count; i ++)
162  str_unref (index_get (filenames, i));
163 
164  index_free (filenames);
165 }
166 
167 static void index_free_tuples (Index * tuples)
168 {
169  int count = index_count (tuples);
170  for (int i = 0; i < count; i ++)
171  {
172  Tuple * tuple = index_get (tuples, i);
173  if (tuple)
174  tuple_unref (tuple);
175  }
176 
177  index_free (tuples);
178 }
179 
180 static AddTask * add_task_new (int playlist_id, int at, bool_t play,
181  Index * filenames, Index * tuples, PlaylistFilterFunc filter,
182  void * user)
183 {
184  AddTask * task = g_slice_new (AddTask);
185  task->playlist_id = playlist_id;
186  task->at = at;
187  task->play = play;
188  task->filenames = filenames;
189  task->tuples = tuples;
190  task->filter = filter;
191  task->user = user;
192  return task;
193 }
194 
195 static void add_task_free (AddTask * task)
196 {
197  if (task->filenames)
199  if (task->tuples)
200  index_free_tuples (task->tuples);
201 
202  g_slice_free (AddTask, task);
203 }
204 
205 static AddResult * add_result_new (int playlist_id, int at, bool_t play)
206 {
207  AddResult * result = g_slice_new (AddResult);
208  result->playlist_id = playlist_id;
209  result->at = at;
210  result->play = play;
211  result->filenames = index_new ();
212  result->tuples = index_new ();
213  result->decoders = index_new ();
214  return result;
215 }
216 
217 static void add_result_free (AddResult * result)
218 {
219  if (result->filenames)
221  if (result->tuples)
222  index_free_tuples (result->tuples);
223  if (result->decoders)
224  index_free (result->decoders);
225 
226  g_slice_free (AddResult, result);
227 }
228 
229 static void add_file (char * filename, Tuple * tuple, PluginHandle * decoder,
230  PlaylistFilterFunc filter, void * user, AddResult * result, bool_t validate)
231 {
232  g_return_if_fail (filename);
233  if (filter && ! filter (filename, user))
234  {
235  str_unref (filename);
236  return;
237  }
238 
239  status_update (filename, index_count (result->filenames));
240 
241  if (! tuple && ! decoder)
242  {
243  decoder = file_find_decoder (filename, TRUE);
244  if (validate && ! decoder)
245  {
246  str_unref (filename);
247  return;
248  }
249  }
250 
251  if (! tuple && decoder && input_plugin_has_subtunes (decoder) && ! strchr
252  (filename, '?'))
253  tuple = file_read_tuple (filename, decoder);
254 
255  int n_subtunes = tuple ? tuple_get_n_subtunes (tuple) : 0;
256 
257  if (n_subtunes)
258  {
259  for (int sub = 0; sub < n_subtunes; sub ++)
260  {
261  char * subname = str_printf ("%s?%d", filename,
262  tuple_get_nth_subtune (tuple, sub));
263  add_file (subname, NULL, decoder, filter, user, result, FALSE);
264  }
265 
266  str_unref (filename);
267  tuple_unref (tuple);
268  return;
269  }
270 
271  index_append (result->filenames, filename);
272  index_append (result->tuples, tuple);
273  index_append (result->decoders, decoder);
274 }
275 
276 static void add_folder (char * filename, PlaylistFilterFunc filter,
277  void * user, AddResult * result)
278 {
279  g_return_if_fail (filename);
280  if (filter && ! filter (filename, user))
281  {
282  str_unref (filename);
283  return;
284  }
285 
286  status_update (filename, index_count (result->filenames));
287 
288  char * unix_name = uri_to_filename (filename);
289  if (! unix_name)
290  {
291  str_unref (filename);
292  return;
293  }
294 
295  if (unix_name[strlen (unix_name) - 1] == '/')
296  unix_name[strlen (unix_name) - 1] = 0;
297 
298  GList * files = NULL;
299  DIR * folder = opendir (unix_name);
300  if (! folder)
301  goto FREE;
302 
303  struct dirent * entry;
304  while ((entry = readdir (folder)))
305  {
306  if (entry->d_name[0] != '.')
307  files = g_list_prepend (files, g_strdup_printf ("%s"
308  G_DIR_SEPARATOR_S "%s", unix_name, entry->d_name));
309  }
310 
311  closedir (folder);
312  files = g_list_sort (files, (GCompareFunc) string_compare);
313 
314  while (files)
315  {
316  struct stat info;
317  if (stat (files->data, & info) < 0)
318  goto NEXT;
319 
320  if (S_ISREG (info.st_mode))
321  {
322  char * item_name = filename_to_uri (files->data);
323  if (item_name)
324  {
325  add_file (str_get (item_name), NULL, NULL, filter, user, result, TRUE);
326  g_free (item_name);
327  }
328  }
329  else if (S_ISDIR (info.st_mode))
330  {
331  char * item_name = filename_to_uri (files->data);
332  if (item_name)
333  {
334  add_folder (str_get (item_name), filter, user, result);
335  g_free (item_name);
336  }
337  }
338 
339  NEXT:
340  g_free (files->data);
341  files = g_list_delete_link (files, files);
342  }
343 
344 FREE:
345  str_unref (filename);
346  g_free (unix_name);
347 }
348 
349 static void add_playlist (char * filename, PlaylistFilterFunc filter,
350  void * user, AddResult * result)
351 {
352  g_return_if_fail (filename);
353  if (filter && ! filter (filename, user))
354  {
355  str_unref (filename);
356  return;
357  }
358 
359  status_update (filename, index_count (result->filenames));
360 
361  char * title = NULL;
362  Index * filenames, * tuples;
363  if (! playlist_load (filename, & title, & filenames, & tuples))
364  {
365  str_unref (filename);
366  return;
367  }
368 
369  int count = index_count (filenames);
370  for (int i = 0; i < count; i ++)
371  add_file (index_get (filenames, i), tuples ? index_get (tuples, i) :
372  NULL, NULL, filter, user, result, FALSE);
373 
374  str_unref (filename);
375  str_unref (title);
376  index_free (filenames);
377  if (tuples)
378  index_free (tuples);
379 }
380 
381 static void add_generic (char * filename, Tuple * tuple,
382  PlaylistFilterFunc filter, void * user, AddResult * result)
383 {
384  g_return_if_fail (filename);
385 
386  if (tuple)
387  add_file (filename, tuple, NULL, filter, user, result, FALSE);
388  else if (vfs_file_test (filename, G_FILE_TEST_IS_DIR))
389  add_folder (filename, filter, user, result);
390  else if (filename_is_playlist (filename))
391  add_playlist (filename, filter, user, result);
392  else
393  add_file (filename, NULL, NULL, filter, user, result, FALSE);
394 }
395 
396 static bool_t add_finish (void * unused)
397 {
398  pthread_mutex_lock (& mutex);
399 
400  while (add_results)
401  {
402  AddResult * result = add_results->data;
403  add_results = g_list_delete_link (add_results, add_results);
404 
406  if (playlist < 0) /* playlist deleted */
407  goto FREE;
408 
409  int count = playlist_entry_count (playlist);
410  if (result->at < 0 || result->at > count)
411  result->at = count;
412 
413  playlist_entry_insert_batch_raw (playlist, result->at,
414  result->filenames, result->tuples, result->decoders);
415  result->filenames = NULL;
416  result->tuples = NULL;
417  result->decoders = NULL;
418 
419  if (result->play && playlist_entry_count (playlist) > count)
420  {
421  playlist_set_playing (playlist);
422  if (! get_bool (NULL, "shuffle"))
423  playlist_set_position (playlist, result->at);
424 
425  playback_play (0, FALSE);
426  }
427 
428  FREE:
429  add_result_free (result);
430  }
431 
432  if (add_source)
433  {
434  g_source_remove (add_source);
435  add_source = 0;
436  }
437 
438  if (! add_tasks)
440 
441  pthread_mutex_unlock (& mutex);
442 
443  hook_call ("playlist add complete", NULL);
444  return FALSE;
445 }
446 
447 static void * add_worker (void * unused)
448 {
449  pthread_mutex_lock (& mutex);
450 
451  while (! add_quit)
452  {
453  if (! add_tasks)
454  {
455  pthread_cond_wait (& cond, & mutex);
456  continue;
457  }
458 
459  AddTask * task = add_tasks->data;
460  add_tasks = g_list_delete_link (add_tasks, add_tasks);
461 
463  pthread_mutex_unlock (& mutex);
464 
465  AddResult * result = add_result_new (task->playlist_id, task->at,
466  task->play);
467 
468  int count = index_count (task->filenames);
469  if (task->tuples)
470  count = MIN (count, index_count (task->tuples));
471 
472  for (int i = 0; i < count; i ++)
473  {
474  add_generic (index_get (task->filenames, i), task->tuples ?
475  index_get (task->tuples, i) : NULL, task->filter, task->user,
476  result);
477 
478  index_set (task->filenames, i, NULL);
479  if (task->tuples)
480  index_set (task->tuples, i, NULL);
481  }
482 
483  add_task_free (task);
484 
485  pthread_mutex_lock (& mutex);
486  current_playlist_id = -1;
487 
488  add_results = g_list_append (add_results, result);
489 
490  if (! add_source)
491  add_source = g_timeout_add (0, add_finish, NULL);
492  }
493 
494  pthread_mutex_unlock (& mutex);
495  return NULL;
496 }
497 
498 void adder_init (void)
499 {
500  pthread_mutex_lock (& mutex);
501  add_quit = FALSE;
502  pthread_create (& add_thread, NULL, add_worker, NULL);
503  pthread_mutex_unlock (& mutex);
504 }
505 
506 void adder_cleanup (void)
507 {
508  pthread_mutex_lock (& mutex);
509  add_quit = TRUE;
510  pthread_cond_broadcast (& cond);
511  pthread_mutex_unlock (& mutex);
512  pthread_join (add_thread, NULL);
513 
514  if (add_source)
515  {
516  g_source_remove (add_source);
517  add_source = 0;
518  }
519 
521 }
522 
523 void playlist_entry_insert (int playlist, int at, const char * filename,
524  Tuple * tuple, bool_t play)
525 {
526  Index * filenames = index_new ();
527  Index * tuples = index_new ();
528  index_append (filenames, str_get (filename));
529  index_append (tuples, tuple);
530 
531  playlist_entry_insert_batch (playlist, at, filenames, tuples, play);
532 }
533 
535  Index * filenames, Index * tuples, bool_t play)
536 {
537  playlist_entry_insert_filtered (playlist, at, filenames, tuples, NULL, NULL, play);
538 }
539 
541  Index * filenames, Index * tuples, PlaylistFilterFunc filter,
542  void * user, bool_t play)
543 {
544  int playlist_id = playlist_get_unique_id (playlist);
545  g_return_if_fail (playlist_id >= 0);
546 
547  AddTask * task = add_task_new (playlist_id, at, play, filenames, tuples, filter, user);
548 
549  pthread_mutex_lock (& mutex);
550  add_tasks = g_list_append (add_tasks, task);
551  pthread_cond_broadcast (& cond);
552  pthread_mutex_unlock (& mutex);
553 }
554 
556 {
557  int playlist_id = playlist_get_unique_id (playlist);
558  g_return_val_if_fail (playlist_id >= 0, FALSE);
559 
560  pthread_mutex_lock (& mutex);
561 
562  for (GList * node = add_tasks; node; node = node->next)
563  {
564  if (((AddTask *) node->data)->playlist_id == playlist_id)
565  goto YES;
566  }
567 
568  if (current_playlist_id == playlist_id)
569  goto YES;
570 
571  for (GList * node = add_results; node; node = node->next)
572  {
573  if (((AddResult *) node->data)->playlist_id == playlist_id)
574  goto YES;
575  }
576 
577  pthread_mutex_unlock (& mutex);
578  return FALSE;
579 
580 YES:
581  pthread_mutex_unlock (& mutex);
582  return TRUE;
583 }