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 
110 
120 
130 
131 void playlist_sort_by_scheme (int playlist, int scheme)
132 {
133  if (filename_comparisons[scheme] != NULL)
135  else if (tuple_comparisons[scheme] != NULL)
136  playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
137  else if (title_comparisons[scheme] != NULL)
138  playlist_sort_by_title (playlist, title_comparisons[scheme]);
139 }
140 
142 {
143  if (filename_comparisons[scheme] != NULL)
145  filename_comparisons[scheme]);
146  else if (tuple_comparisons[scheme] != NULL)
148  else if (title_comparisons[scheme] != NULL)
150 }
151 
152 /* Fix me: This considers empty fields as duplicates. */
154 {
155  int entries = playlist_entry_count (playlist);
156  int count;
157 
158  if (entries < 1)
159  return;
160 
161  playlist_select_all (playlist, FALSE);
162 
163  if (filename_comparisons[scheme] != NULL)
164  {
165  int (* compare) (const char * a, const char * b) =
166  filename_comparisons[scheme];
167 
168  playlist_sort_by_filename (playlist, compare);
169  char * last = playlist_entry_get_filename (playlist, 0);
170 
171  for (count = 1; count < entries; count ++)
172  {
173  char * current = playlist_entry_get_filename (playlist, count);
174 
175  if (compare (last, current) == 0)
176  playlist_entry_set_selected (playlist, count, TRUE);
177 
178  str_unref (last);
179  last = current;
180  }
181 
182  str_unref (last);
183  }
184  else if (tuple_comparisons[scheme] != NULL)
185  {
186  int (* compare) (const Tuple * a, const Tuple * b) =
187  tuple_comparisons[scheme];
188 
189  playlist_sort_by_tuple (playlist, compare);
190  Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE);
191 
192  for (count = 1; count < entries; count ++)
193  {
194  Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE);
195 
196  if (last != NULL && current != NULL && compare (last, current) == 0)
197  playlist_entry_set_selected (playlist, count, TRUE);
198 
199  if (last)
200  tuple_unref (last);
201  last = current;
202  }
203 
204  if (last)
205  tuple_unref (last);
206  }
207 
208  playlist_delete_selected (playlist);
209 }
210 
212 {
213  int entries = playlist_entry_count (playlist);
214  int count;
215 
216  playlist_select_all (playlist, FALSE);
217 
218  for (count = 0; count < entries; count ++)
219  {
220  char * filename = playlist_entry_get_filename (playlist, count);
221 
222  /* vfs_file_test() only works for file:// URIs currently */
223  if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename,
224  G_FILE_TEST_EXISTS))
225  playlist_entry_set_selected (playlist, count, TRUE);
226 
227  str_unref (filename);
228  }
229 
230  playlist_delete_selected (playlist);
231 }
232 
233 void playlist_select_by_patterns (int playlist, const Tuple * patterns)
234 {
235  const int fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST,
237 
238  int entries = playlist_entry_count (playlist);
239  int field, entry;
240 
241  playlist_select_all (playlist, TRUE);
242 
243  for (field = 0; field < G_N_ELEMENTS (fields); field ++)
244  {
245  char * pattern = tuple_get_str (patterns, fields[field], NULL);
246  regex_t regex;
247 
248  if (! pattern || ! pattern[0] || regcomp (& regex, pattern, REG_ICASE))
249  {
250  str_unref (pattern);
251  continue;
252  }
253 
254  for (entry = 0; entry < entries; entry ++)
255  {
256  if (! playlist_entry_get_selected (playlist, entry))
257  continue;
258 
259  Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE);
260  char * string = tuple ? tuple_get_str (tuple, fields[field], NULL) : NULL;
261 
262  if (! string || regexec (& regex, string, 0, NULL, 0))
263  playlist_entry_set_selected (playlist, entry, FALSE);
264 
265  str_unref (string);
266  if (tuple)
267  tuple_unref (tuple);
268  }
269 
270  regfree (& regex);
271  str_unref (pattern);
272  }
273 }
274 
275 static char * make_playlist_path (int playlist)
276 {
277  if (! playlist)
278  return g_strdup_printf ("%s/playlist.xspf", get_path (AUD_PATH_USER_DIR));
279 
280  return g_strdup_printf ("%s/playlist_%02d.xspf",
281  get_path (AUD_PATH_PLAYLISTS_DIR), 1 + playlist);
282 }
283 
284 static void load_playlists_real (void)
285 {
286  /* old (v3.1 and earlier) naming scheme */
287 
288  int count;
289  for (count = 0; ; count ++)
290  {
291  char * path = make_playlist_path (count);
292 
293  if (! g_file_test (path, G_FILE_TEST_EXISTS))
294  {
295  g_free (path);
296  break;
297  }
298 
299  char * uri = filename_to_uri (path);
300 
301  playlist_insert (count);
302  playlist_insert_playlist_raw (count, 0, uri);
303  playlist_set_modified (count, TRUE);
304 
305  g_free (path);
306  g_free (uri);
307  }
308 
309  /* unique ID-based naming scheme */
310 
311  char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
312  char * order_string;
313  g_file_get_contents (order_path, & order_string, NULL, NULL);
314  g_free (order_path);
315 
316  if (! order_string)
317  goto DONE;
318 
319  char * * order = g_strsplit (order_string, " ", -1);
320  g_free (order_string);
321 
322  for (int i = 0; order[i]; i ++)
323  {
324  char * path = g_strdup_printf ("%s/%s.audpl", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
325 
326  if (! g_file_test (path, G_FILE_TEST_EXISTS))
327  {
328  g_free (path);
329  path = g_strdup_printf ("%s/%s.xspf", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
330  }
331 
332  char * uri = filename_to_uri (path);
333 
334  playlist_insert_with_id (count + i, atoi (order[i]));
335  playlist_insert_playlist_raw (count + i, 0, uri);
336  playlist_set_modified (count + i, FALSE);
337 
338  if (g_str_has_suffix (path, ".xspf"))
339  playlist_set_modified (count + i, TRUE);
340 
341  g_free (path);
342  g_free (uri);
343  }
344 
345  g_strfreev (order);
346 
347 DONE:
348  if (! playlist_count ())
349  playlist_insert (0);
350 
352 }
353 
354 static void save_playlists_real (void)
355 {
356  int lists = playlist_count ();
357  const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR);
358 
359  /* save playlists */
360 
361  char * * order = g_malloc (sizeof (char *) * (lists + 1));
362  GHashTable * saved = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
363 
364  for (int i = 0; i < lists; i ++)
365  {
366  int id = playlist_get_unique_id (i);
367  order[i] = g_strdup_printf ("%d", id);
368 
369  if (playlist_get_modified (i))
370  {
371  char * path = g_strdup_printf ("%s/%d.audpl", folder, id);
372  char * uri = filename_to_uri (path);
373 
374  playlist_save (i, uri);
376 
377  g_free (path);
378  g_free (uri);
379  }
380 
381  g_hash_table_insert (saved, g_strdup_printf ("%d.audpl", id), NULL);
382  }
383 
384  order[lists] = NULL;
385  char * order_string = g_strjoinv (" ", order);
386  g_strfreev (order);
387 
388  GError * error = NULL;
389  char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
390 
391  char * old_order_string;
392  g_file_get_contents (order_path, & old_order_string, NULL, NULL);
393 
394  if (! old_order_string || strcmp (old_order_string, order_string))
395  {
396  if (! g_file_set_contents (order_path, order_string, -1, & error))
397  {
398  fprintf (stderr, "Cannot write to %s: %s\n", order_path, error->message);
399  g_error_free (error);
400  }
401  }
402 
403  g_free (order_string);
404  g_free (order_path);
405  g_free (old_order_string);
406 
407  /* clean up deleted playlists and files from old naming scheme */
408 
409  char * path = make_playlist_path (0);
410  remove (path);
411  g_free (path);
412 
413  DIR * dir = opendir (folder);
414  if (! dir)
415  goto DONE;
416 
417  struct dirent * entry;
418  while ((entry = readdir (dir)))
419  {
420  if (! g_str_has_suffix (entry->d_name, ".audpl")
421  && ! g_str_has_suffix (entry->d_name, ".xspf"))
422  continue;
423 
424  if (! g_hash_table_lookup_extended (saved, entry->d_name, NULL, NULL))
425  {
426  char * path = g_strdup_printf ("%s/%s", folder, entry->d_name);
427  remove (path);
428  g_free (path);
429  }
430  }
431 
432  closedir (dir);
433 
434 DONE:
435  g_hash_table_destroy (saved);
436 }
437 
439 
440 static void update_cb (void * data, void * user)
441 {
442  if (GPOINTER_TO_INT (data) < PLAYLIST_UPDATE_METADATA)
443  return;
444 
446 }
447 
448 static void state_cb (void * data, void * user)
449 {
451 }
452 
453 void load_playlists (void)
454 {
457 
459 
460  if (! hooks_added)
461  {
462  hook_associate ("playlist update", update_cb, NULL);
463  hook_associate ("playlist activate", state_cb, NULL);
464  hook_associate ("playlist position", state_cb, NULL);
465 
466  hooks_added = TRUE;
467  }
468 }
469 
470 void save_playlists (bool_t exiting)
471 {
473 
474  /* on exit, save resume states */
475  if (state_changed || exiting)
476  {
479  }
480 
481  if (exiting && hooks_added)
482  {
483  hook_dissociate ("playlist update", update_cb);
484  hook_dissociate ("playlist activate", state_cb);
485  hook_dissociate ("playlist position", state_cb);
486 
487  hooks_added = FALSE;
488  }
489 }