Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
playlist-utils.c
Go to the documentation of this file.
1 /*
2  * playlist-utils.c
3  * Copyright 2009-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 <glib.h>
24 #include <regex.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include <libaudcore/audstrings.h>
30 #include <libaudcore/hook.h>
31 
32 #include "misc.h"
33 #include "playlist.h"
34 
35 static const char * get_basename (const char * filename)
36 {
37  const char * slash = strrchr (filename, '/');
38 
39  return (slash == NULL) ? filename : slash + 1;
40 }
41 
42 static int filename_compare_basename (const char * a, const char * b)
43 {
45 }
46 
47 static int tuple_compare_string (const Tuple * a, const Tuple * b, int field)
48 {
49  char * string_a = tuple_get_str (a, field, NULL);
50  char * string_b = tuple_get_str (b, field, NULL);
51  int ret;
52 
53  if (string_a == NULL)
54  ret = (string_b == NULL) ? 0 : -1;
55  else if (string_b == NULL)
56  ret = 1;
57  else
58  ret = string_compare (string_a, string_b);
59 
60  str_unref (string_a);
61  str_unref (string_b);
62  return ret;
63 }
64 
65 static int tuple_compare_int (const Tuple * a, const Tuple * b, int field)
66 {
67  if (tuple_get_value_type (a, field, NULL) != TUPLE_INT)
68  return (tuple_get_value_type (b, field, NULL) != TUPLE_INT) ? 0 : -1;
69  if (tuple_get_value_type (b, field, NULL) != TUPLE_INT)
70  return 1;
71 
72  int int_a = tuple_get_int (a, field, NULL);
73  int int_b = tuple_get_int (b, field, NULL);
74 
75  return (int_a < int_b) ? -1 : (int_a > int_b);
76 }
77 
78 static int tuple_compare_title (const Tuple * a, const Tuple * b)
79 {
80  return tuple_compare_string (a, b, FIELD_TITLE);
81 }
82 
83 static int tuple_compare_album (const Tuple * a, const Tuple * b)
84 {
85  return tuple_compare_string (a, b, FIELD_ALBUM);
86 }
87 
88 static int tuple_compare_artist (const Tuple * a, const Tuple * b)
89 {
90  return tuple_compare_string (a, b, FIELD_ARTIST);
91 }
92 
93 static int tuple_compare_date (const Tuple * a, const Tuple * b)
94 {
95  return tuple_compare_int (a, b, FIELD_YEAR);
96 }
97 
98 static int tuple_compare_track (const Tuple * a, const Tuple * b)
99 {
100  return tuple_compare_int (a, b, FIELD_TRACK_NUMBER);
101 }
102 
112 
122 
132 
133 void playlist_sort_by_scheme (int playlist, int scheme)
134 {
135  if (filename_comparisons[scheme] != NULL)
137  else if (tuple_comparisons[scheme] != NULL)
138  playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
139  else if (title_comparisons[scheme] != NULL)
140  playlist_sort_by_title (playlist, title_comparisons[scheme]);
141 }
142 
144 {
145  if (filename_comparisons[scheme] != NULL)
147  filename_comparisons[scheme]);
148  else if (tuple_comparisons[scheme] != NULL)
150  else if (title_comparisons[scheme] != NULL)
152 }
153 
154 /* Fix me: This considers empty fields as duplicates. */
156 {
157  int entries = playlist_entry_count (playlist);
158  int count;
159 
160  if (entries < 1)
161  return;
162 
163  playlist_select_all (playlist, FALSE);
164 
165  if (filename_comparisons[scheme] != NULL)
166  {
167  int (* compare) (const char * a, const char * b) =
168  filename_comparisons[scheme];
169 
170  playlist_sort_by_filename (playlist, compare);
171  char * last = playlist_entry_get_filename (playlist, 0);
172 
173  for (count = 1; count < entries; count ++)
174  {
175  char * current = playlist_entry_get_filename (playlist, count);
176 
177  if (compare (last, current) == 0)
178  playlist_entry_set_selected (playlist, count, TRUE);
179 
180  str_unref (last);
181  last = current;
182  }
183 
184  str_unref (last);
185  }
186  else if (tuple_comparisons[scheme] != NULL)
187  {
188  int (* compare) (const Tuple * a, const Tuple * b) =
189  tuple_comparisons[scheme];
190 
191  playlist_sort_by_tuple (playlist, compare);
192  Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE);
193 
194  for (count = 1; count < entries; count ++)
195  {
196  Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE);
197 
198  if (last != NULL && current != NULL && compare (last, current) == 0)
199  playlist_entry_set_selected (playlist, count, TRUE);
200 
201  if (last)
202  tuple_unref (last);
203  last = current;
204  }
205 
206  if (last)
207  tuple_unref (last);
208  }
209 
210  playlist_delete_selected (playlist);
211 }
212 
214 {
215  int entries = playlist_entry_count (playlist);
216  int count;
217 
218  playlist_select_all (playlist, FALSE);
219 
220  for (count = 0; count < entries; count ++)
221  {
222  char * filename = playlist_entry_get_filename (playlist, count);
223 
224  /* vfs_file_test() only works for file:// URIs currently */
225  if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename,
226  G_FILE_TEST_EXISTS))
227  playlist_entry_set_selected (playlist, count, TRUE);
228 
229  str_unref (filename);
230  }
231 
232  playlist_delete_selected (playlist);
233 }
234 
235 void playlist_select_by_patterns (int playlist, const Tuple * patterns)
236 {
237  const int fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST,
239 
240  int entries = playlist_entry_count (playlist);
241  int field, entry;
242 
243  playlist_select_all (playlist, TRUE);
244 
245  for (field = 0; field < G_N_ELEMENTS (fields); field ++)
246  {
247  char * pattern = tuple_get_str (patterns, fields[field], NULL);
248  regex_t regex;
249 
250  if (! pattern || ! pattern[0] || regcomp (& regex, pattern, REG_ICASE))
251  {
252  str_unref (pattern);
253  continue;
254  }
255 
256  for (entry = 0; entry < entries; entry ++)
257  {
258  if (! playlist_entry_get_selected (playlist, entry))
259  continue;
260 
261  Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE);
262  char * string = tuple ? tuple_get_str (tuple, fields[field], NULL) : NULL;
263 
264  if (! string || regexec (& regex, string, 0, NULL, 0))
265  playlist_entry_set_selected (playlist, entry, FALSE);
266 
267  str_unref (string);
268  if (tuple)
269  tuple_unref (tuple);
270  }
271 
272  regfree (& regex);
273  str_unref (pattern);
274  }
275 }
276 
277 static char * make_playlist_path (int playlist)
278 {
279  if (! playlist)
280  return g_strdup_printf ("%s/playlist.xspf", get_path (AUD_PATH_USER_DIR));
281 
282  return g_strdup_printf ("%s/playlist_%02d.xspf",
283  get_path (AUD_PATH_PLAYLISTS_DIR), 1 + playlist);
284 }
285 
286 static void load_playlists_real (void)
287 {
288  /* old (v3.1 and earlier) naming scheme */
289 
290  int count;
291  for (count = 0; ; count ++)
292  {
293  char * path = make_playlist_path (count);
294 
295  if (! g_file_test (path, G_FILE_TEST_EXISTS))
296  {
297  g_free (path);
298  break;
299  }
300 
301  char * uri = filename_to_uri (path);
302 
303  playlist_insert (count);
304  playlist_insert_playlist_raw (count, 0, uri);
305  playlist_set_modified (count, TRUE);
306 
307  g_free (path);
308  g_free (uri);
309  }
310 
311  /* unique ID-based naming scheme */
312 
313  char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
314  char * order_string;
315  g_file_get_contents (order_path, & order_string, NULL, NULL);
316  g_free (order_path);
317 
318  if (! order_string)
319  goto DONE;
320 
321  char * * order = g_strsplit (order_string, " ", -1);
322  g_free (order_string);
323 
324  for (int i = 0; order[i]; i ++)
325  {
326  char * path = g_strdup_printf ("%s/%s.audpl", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
327 
328  if (! g_file_test (path, G_FILE_TEST_EXISTS))
329  {
330  g_free (path);
331  path = g_strdup_printf ("%s/%s.xspf", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
332  }
333 
334  char * uri = filename_to_uri (path);
335 
336  playlist_insert_with_id (count + i, atoi (order[i]));
337  playlist_insert_playlist_raw (count + i, 0, uri);
338  playlist_set_modified (count + i, FALSE);
339 
340  if (g_str_has_suffix (path, ".xspf"))
341  playlist_set_modified (count + i, TRUE);
342 
343  g_free (path);
344  g_free (uri);
345  }
346 
347  g_strfreev (order);
348 
349 DONE:
350  if (! playlist_count ())
351  playlist_insert (0);
352 
354 }
355 
356 static void save_playlists_real (void)
357 {
358  int lists = playlist_count ();
359  const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR);
360 
361  /* save playlists */
362 
363  char * * order = g_malloc (sizeof (char *) * (lists + 1));
364  GHashTable * saved = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
365 
366  for (int i = 0; i < lists; i ++)
367  {
368  int id = playlist_get_unique_id (i);
369  order[i] = g_strdup_printf ("%d", id);
370 
371  if (playlist_get_modified (i))
372  {
373  char * path = g_strdup_printf ("%s/%d.audpl", folder, id);
374  char * uri = filename_to_uri (path);
375 
376  playlist_save (i, uri);
378 
379  g_free (path);
380  g_free (uri);
381  }
382 
383  g_hash_table_insert (saved, g_strdup_printf ("%d.audpl", id), NULL);
384  }
385 
386  order[lists] = NULL;
387  char * order_string = g_strjoinv (" ", order);
388  g_strfreev (order);
389 
390  GError * error = NULL;
391  char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
392 
393  char * old_order_string;
394  g_file_get_contents (order_path, & old_order_string, NULL, NULL);
395 
396  if (! old_order_string || strcmp (old_order_string, order_string))
397  {
398  if (! g_file_set_contents (order_path, order_string, -1, & error))
399  {
400  fprintf (stderr, "Cannot write to %s: %s\n", order_path, error->message);
401  g_error_free (error);
402  }
403  }
404 
405  g_free (order_string);
406  g_free (order_path);
407  g_free (old_order_string);
408 
409  /* clean up deleted playlists and files from old naming scheme */
410 
411  char * path = make_playlist_path (0);
412  remove (path);
413  g_free (path);
414 
415  DIR * dir = opendir (folder);
416  if (! dir)
417  goto DONE;
418 
419  struct dirent * entry;
420  while ((entry = readdir (dir)))
421  {
422  if (! g_str_has_suffix (entry->d_name, ".audpl")
423  && ! g_str_has_suffix (entry->d_name, ".xspf"))
424  continue;
425 
426  if (! g_hash_table_lookup_extended (saved, entry->d_name, NULL, NULL))
427  {
428  char * path = g_strdup_printf ("%s/%s", folder, entry->d_name);
429  remove (path);
430  g_free (path);
431  }
432  }
433 
434  closedir (dir);
435 
436 DONE:
437  g_hash_table_destroy (saved);
438 }
439 
441 
442 static void update_cb (void * data, void * user)
443 {
444  if (GPOINTER_TO_INT (data) < PLAYLIST_UPDATE_METADATA)
445  return;
446 
448 }
449 
450 static void state_cb (void * data, void * user)
451 {
453 }
454 
455 void load_playlists (void)
456 {
459 
461 
462  if (! hooks_added)
463  {
464  hook_associate ("playlist update", update_cb, NULL);
465  hook_associate ("playlist activate", state_cb, NULL);
466  hook_associate ("playlist position", state_cb, NULL);
467 
468  hooks_added = TRUE;
469  }
470 }
471 
472 void save_playlists (bool_t exiting)
473 {
475 
476  /* on exit, save resume time if resume feature is enabled */
477  if (state_changed || (exiting && get_bool (NULL, "resume_playback_on_startup")))
478  {
481  }
482 
483  if (exiting && hooks_added)
484  {
485  hook_dissociate ("playlist update", update_cb);
486  hook_dissociate ("playlist activate", state_cb);
487  hook_dissociate ("playlist position", state_cb);
488 
489  hooks_added = FALSE;
490  }
491 }