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