PageRenderTime 37ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/audacious-3.3-beta2/src/audacious/playlist-utils.c

#
C | 489 lines | 358 code | 106 blank | 25 comment | 82 complexity | 6c6fdf454f18b686e888b876fe57b856 MD5 | raw 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. #include <dirent.h>
  20. #include <glib.h>
  21. #include <regex.h>
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <libaudcore/audstrings.h>
  26. #include <libaudcore/hook.h>
  27. #include "misc.h"
  28. #include "playlist.h"
  29. static const char * get_basename (const char * filename)
  30. {
  31. const char * slash = strrchr (filename, '/');
  32. return (slash == NULL) ? filename : slash + 1;
  33. }
  34. static int filename_compare_basename (const char * a, const char * b)
  35. {
  36. return string_compare_encoded (get_basename (a), get_basename (b));
  37. }
  38. static int tuple_compare_string (const Tuple * a, const Tuple * b, int field)
  39. {
  40. char * string_a = tuple_get_str (a, field, NULL);
  41. char * string_b = tuple_get_str (b, field, NULL);
  42. int ret;
  43. if (string_a == NULL)
  44. ret = (string_b == NULL) ? 0 : -1;
  45. else if (string_b == NULL)
  46. ret = 1;
  47. else
  48. ret = string_compare (string_a, string_b);
  49. str_unref (string_a);
  50. str_unref (string_b);
  51. return ret;
  52. }
  53. static int tuple_compare_int (const Tuple * a, const Tuple * b, int field)
  54. {
  55. if (tuple_get_value_type (a, field, NULL) != TUPLE_INT)
  56. return (tuple_get_value_type (b, field, NULL) != TUPLE_INT) ? 0 : -1;
  57. if (tuple_get_value_type (b, field, NULL) != TUPLE_INT)
  58. return 1;
  59. int int_a = tuple_get_int (a, field, NULL);
  60. int int_b = tuple_get_int (b, field, NULL);
  61. return (int_a < int_b) ? -1 : (int_a > int_b);
  62. }
  63. static int tuple_compare_title (const Tuple * a, const Tuple * b)
  64. {
  65. return tuple_compare_string (a, b, FIELD_TITLE);
  66. }
  67. static int tuple_compare_album (const Tuple * a, const Tuple * b)
  68. {
  69. return tuple_compare_string (a, b, FIELD_ALBUM);
  70. }
  71. static int tuple_compare_artist (const Tuple * a, const Tuple * b)
  72. {
  73. return tuple_compare_string (a, b, FIELD_ARTIST);
  74. }
  75. static int tuple_compare_date (const Tuple * a, const Tuple * b)
  76. {
  77. return tuple_compare_int (a, b, FIELD_YEAR);
  78. }
  79. static int tuple_compare_track (const Tuple * a, const Tuple * b)
  80. {
  81. return tuple_compare_int (a, b, FIELD_TRACK_NUMBER);
  82. }
  83. static const PlaylistStringCompareFunc filename_comparisons[] = {
  84. [PLAYLIST_SORT_PATH] = string_compare_encoded,
  85. [PLAYLIST_SORT_FILENAME] = filename_compare_basename,
  86. [PLAYLIST_SORT_TITLE] = NULL,
  87. [PLAYLIST_SORT_ALBUM] = NULL,
  88. [PLAYLIST_SORT_ARTIST] = NULL,
  89. [PLAYLIST_SORT_DATE] = NULL,
  90. [PLAYLIST_SORT_TRACK] = NULL,
  91. [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
  92. static const PlaylistTupleCompareFunc tuple_comparisons[] = {
  93. [PLAYLIST_SORT_PATH] = NULL,
  94. [PLAYLIST_SORT_FILENAME] = NULL,
  95. [PLAYLIST_SORT_TITLE] = tuple_compare_title,
  96. [PLAYLIST_SORT_ALBUM] = tuple_compare_album,
  97. [PLAYLIST_SORT_ARTIST] = tuple_compare_artist,
  98. [PLAYLIST_SORT_DATE] = tuple_compare_date,
  99. [PLAYLIST_SORT_TRACK] = tuple_compare_track,
  100. [PLAYLIST_SORT_FORMATTED_TITLE] = NULL};
  101. static const PlaylistStringCompareFunc title_comparisons[] = {
  102. [PLAYLIST_SORT_PATH] = NULL,
  103. [PLAYLIST_SORT_FILENAME] = NULL,
  104. [PLAYLIST_SORT_TITLE] = NULL,
  105. [PLAYLIST_SORT_ALBUM] = NULL,
  106. [PLAYLIST_SORT_ARTIST] = NULL,
  107. [PLAYLIST_SORT_DATE] = NULL,
  108. [PLAYLIST_SORT_TRACK] = NULL,
  109. [PLAYLIST_SORT_FORMATTED_TITLE] = string_compare};
  110. void playlist_sort_by_scheme (int playlist, int scheme)
  111. {
  112. if (filename_comparisons[scheme] != NULL)
  113. playlist_sort_by_filename (playlist, filename_comparisons[scheme]);
  114. else if (tuple_comparisons[scheme] != NULL)
  115. playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
  116. else if (title_comparisons[scheme] != NULL)
  117. playlist_sort_by_title (playlist, title_comparisons[scheme]);
  118. }
  119. void playlist_sort_selected_by_scheme (int playlist, int scheme)
  120. {
  121. if (filename_comparisons[scheme] != NULL)
  122. playlist_sort_selected_by_filename (playlist,
  123. filename_comparisons[scheme]);
  124. else if (tuple_comparisons[scheme] != NULL)
  125. playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]);
  126. else if (title_comparisons[scheme] != NULL)
  127. playlist_sort_selected_by_title (playlist, title_comparisons[scheme]);
  128. }
  129. /* Fix me: This considers empty fields as duplicates. */
  130. void playlist_remove_duplicates_by_scheme (int playlist, int scheme)
  131. {
  132. int entries = playlist_entry_count (playlist);
  133. int count;
  134. if (entries < 1)
  135. return;
  136. playlist_select_all (playlist, FALSE);
  137. if (filename_comparisons[scheme] != NULL)
  138. {
  139. int (* compare) (const char * a, const char * b) =
  140. filename_comparisons[scheme];
  141. playlist_sort_by_filename (playlist, compare);
  142. char * last = playlist_entry_get_filename (playlist, 0);
  143. for (count = 1; count < entries; count ++)
  144. {
  145. char * current = playlist_entry_get_filename (playlist, count);
  146. if (compare (last, current) == 0)
  147. playlist_entry_set_selected (playlist, count, TRUE);
  148. str_unref (last);
  149. last = current;
  150. }
  151. str_unref (last);
  152. }
  153. else if (tuple_comparisons[scheme] != NULL)
  154. {
  155. int (* compare) (const Tuple * a, const Tuple * b) =
  156. tuple_comparisons[scheme];
  157. playlist_sort_by_tuple (playlist, compare);
  158. Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE);
  159. for (count = 1; count < entries; count ++)
  160. {
  161. Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE);
  162. if (last != NULL && current != NULL && compare (last, current) == 0)
  163. playlist_entry_set_selected (playlist, count, TRUE);
  164. if (last)
  165. tuple_unref (last);
  166. last = current;
  167. }
  168. if (last)
  169. tuple_unref (last);
  170. }
  171. playlist_delete_selected (playlist);
  172. }
  173. void playlist_remove_failed (int playlist)
  174. {
  175. int entries = playlist_entry_count (playlist);
  176. int count;
  177. playlist_select_all (playlist, FALSE);
  178. for (count = 0; count < entries; count ++)
  179. {
  180. char * filename = playlist_entry_get_filename (playlist, count);
  181. /* vfs_file_test() only works for file:// URIs currently */
  182. if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename,
  183. G_FILE_TEST_EXISTS))
  184. playlist_entry_set_selected (playlist, count, TRUE);
  185. str_unref (filename);
  186. }
  187. playlist_delete_selected (playlist);
  188. }
  189. void playlist_select_by_patterns (int playlist, const Tuple * patterns)
  190. {
  191. const int fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST,
  192. FIELD_FILE_NAME};
  193. int entries = playlist_entry_count (playlist);
  194. int field, entry;
  195. playlist_select_all (playlist, TRUE);
  196. for (field = 0; field < G_N_ELEMENTS (fields); field ++)
  197. {
  198. char * pattern = tuple_get_str (patterns, fields[field], NULL);
  199. regex_t regex;
  200. if (! pattern || ! pattern[0] || regcomp (& regex, pattern, REG_ICASE))
  201. {
  202. str_unref (pattern);
  203. continue;
  204. }
  205. for (entry = 0; entry < entries; entry ++)
  206. {
  207. if (! playlist_entry_get_selected (playlist, entry))
  208. continue;
  209. Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE);
  210. char * string = tuple ? tuple_get_str (tuple, fields[field], NULL) : NULL;
  211. if (! string || regexec (& regex, string, 0, NULL, 0))
  212. playlist_entry_set_selected (playlist, entry, FALSE);
  213. str_unref (string);
  214. if (tuple)
  215. tuple_unref (tuple);
  216. }
  217. regfree (& regex);
  218. str_unref (pattern);
  219. }
  220. }
  221. static char * make_playlist_path (int playlist)
  222. {
  223. if (! playlist)
  224. return g_strdup_printf ("%s/playlist.xspf", get_path (AUD_PATH_USER_DIR));
  225. return g_strdup_printf ("%s/playlist_%02d.xspf",
  226. get_path (AUD_PATH_PLAYLISTS_DIR), 1 + playlist);
  227. }
  228. static void load_playlists_real (void)
  229. {
  230. /* old (v3.1 and earlier) naming scheme */
  231. int count;
  232. for (count = 0; ; count ++)
  233. {
  234. char * path = make_playlist_path (count);
  235. if (! g_file_test (path, G_FILE_TEST_EXISTS))
  236. {
  237. g_free (path);
  238. break;
  239. }
  240. char * uri = filename_to_uri (path);
  241. playlist_insert (count);
  242. playlist_insert_playlist_raw (count, 0, uri);
  243. playlist_set_modified (count, TRUE);
  244. g_free (path);
  245. g_free (uri);
  246. }
  247. /* unique ID-based naming scheme */
  248. char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
  249. char * order_string;
  250. g_file_get_contents (order_path, & order_string, NULL, NULL);
  251. g_free (order_path);
  252. if (! order_string)
  253. goto DONE;
  254. char * * order = g_strsplit (order_string, " ", -1);
  255. g_free (order_string);
  256. for (int i = 0; order[i]; i ++)
  257. {
  258. char * path = g_strdup_printf ("%s/%s.audpl", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
  259. if (! g_file_test (path, G_FILE_TEST_EXISTS))
  260. {
  261. g_free (path);
  262. path = g_strdup_printf ("%s/%s.xspf", get_path (AUD_PATH_PLAYLISTS_DIR), order[i]);
  263. }
  264. char * uri = filename_to_uri (path);
  265. playlist_insert_with_id (count + i, atoi (order[i]));
  266. playlist_insert_playlist_raw (count + i, 0, uri);
  267. playlist_set_modified (count + i, FALSE);
  268. if (g_str_has_suffix (path, ".xspf"))
  269. playlist_set_modified (count + i, TRUE);
  270. g_free (path);
  271. g_free (uri);
  272. }
  273. g_strfreev (order);
  274. DONE:
  275. if (! playlist_count ())
  276. playlist_insert (0);
  277. playlist_set_active (0);
  278. }
  279. static void save_playlists_real (void)
  280. {
  281. int lists = playlist_count ();
  282. const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR);
  283. /* save playlists */
  284. char * * order = g_malloc (sizeof (char *) * (lists + 1));
  285. GHashTable * saved = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  286. for (int i = 0; i < lists; i ++)
  287. {
  288. int id = playlist_get_unique_id (i);
  289. order[i] = g_strdup_printf ("%d", id);
  290. if (playlist_get_modified (i))
  291. {
  292. char * path = g_strdup_printf ("%s/%d.audpl", folder, id);
  293. char * uri = filename_to_uri (path);
  294. playlist_save (i, uri);
  295. playlist_set_modified (i, FALSE);
  296. g_free (path);
  297. g_free (uri);
  298. }
  299. g_hash_table_insert (saved, g_strdup_printf ("%d.audpl", id), NULL);
  300. }
  301. order[lists] = NULL;
  302. char * order_string = g_strjoinv (" ", order);
  303. g_strfreev (order);
  304. GError * error = NULL;
  305. char * order_path = g_strdup_printf ("%s/order", get_path (AUD_PATH_PLAYLISTS_DIR));
  306. char * old_order_string;
  307. g_file_get_contents (order_path, & old_order_string, NULL, NULL);
  308. if (! old_order_string || strcmp (old_order_string, order_string))
  309. {
  310. if (! g_file_set_contents (order_path, order_string, -1, & error))
  311. {
  312. fprintf (stderr, "Cannot write to %s: %s\n", order_path, error->message);
  313. g_error_free (error);
  314. }
  315. }
  316. g_free (order_string);
  317. g_free (order_path);
  318. g_free (old_order_string);
  319. /* clean up deleted playlists and files from old naming scheme */
  320. char * path = make_playlist_path (0);
  321. remove (path);
  322. g_free (path);
  323. DIR * dir = opendir (folder);
  324. if (! dir)
  325. goto DONE;
  326. struct dirent * entry;
  327. while ((entry = readdir (dir)))
  328. {
  329. if (! g_str_has_suffix (entry->d_name, ".audpl")
  330. && ! g_str_has_suffix (entry->d_name, ".xspf"))
  331. continue;
  332. if (! g_hash_table_lookup_extended (saved, entry->d_name, NULL, NULL))
  333. {
  334. char * path = g_strdup_printf ("%s/%s", folder, entry->d_name);
  335. remove (path);
  336. g_free (path);
  337. }
  338. }
  339. closedir (dir);
  340. DONE:
  341. g_hash_table_destroy (saved);
  342. }
  343. static bool_t hooks_added, state_changed;
  344. static void update_cb (void * data, void * user)
  345. {
  346. if (GPOINTER_TO_INT (data) < PLAYLIST_UPDATE_METADATA)
  347. return;
  348. state_changed = TRUE;
  349. }
  350. static void state_cb (void * data, void * user)
  351. {
  352. state_changed = TRUE;
  353. }
  354. void load_playlists (void)
  355. {
  356. load_playlists_real ();
  357. playlist_load_state ();
  358. state_changed = FALSE;
  359. if (! hooks_added)
  360. {
  361. hook_associate ("playlist update", update_cb, NULL);
  362. hook_associate ("playlist activate", state_cb, NULL);
  363. hook_associate ("playlist position", state_cb, NULL);
  364. hooks_added = TRUE;
  365. }
  366. }
  367. void save_playlists (bool_t exiting)
  368. {
  369. save_playlists_real ();
  370. /* on exit, save resume states */
  371. if (state_changed || exiting)
  372. {
  373. playlist_save_state ();
  374. state_changed = FALSE;
  375. }
  376. if (exiting && hooks_added)
  377. {
  378. hook_dissociate ("playlist update", update_cb);
  379. hook_dissociate ("playlist activate", state_cb);
  380. hook_dissociate ("playlist position", state_cb);
  381. hooks_added = FALSE;
  382. }
  383. }