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