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