/* * Copyright 2008-2013 Various Authors * Copyright 2004-2006 Timo Hirvonen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "lib.h" #include "editable.h" #include "track_info.h" #include "options.h" #include "xmalloc.h" #include "rbtree.h" #include "debug.h" #include "utils.h" #include "u_collate.h" #include "ui_curses.h" /* cur_view */ #include #include struct editable lib_editable; static struct editable_shared lib_editable_shared; struct tree_track *lib_cur_track = NULL; unsigned int play_sorted = 0; enum aaa_mode aaa_mode = AAA_MODE_ALL; /* used in ui_curses.c for status display */ char *lib_live_filter = NULL; struct rb_root lib_shuffle_root; struct rb_root lib_album_shuffle_root; static struct expr *filter = NULL; static struct expr *add_filter = NULL; static int remove_from_hash = 1; static struct expr *live_filter_expr = NULL; static struct track_info *cur_track_ti = NULL; static struct track_info *sel_track_ti = NULL; const char *artist_sort_name(const struct artist *a) { if (a->sort_name) return a->sort_name; if (smart_artist_sort && a->auto_sort_name) return a->auto_sort_name; return a->name; } static inline void sorted_track_to_iter(struct tree_track *track, struct iter *iter) { iter->data0 = &lib_editable.head; iter->data1 = track; iter->data2 = NULL; } static void all_wins_changed(void) { lib_tree_win->changed = 1; lib_track_win->changed = 1; lib_editable.shared->win->changed = 1; } static void shuffle_add(struct tree_track *track) { shuffle_list_add(&track->simple_track.shuffle_info, &lib_shuffle_root, track->album); } static void album_shuffle_list_add(struct album *album) { shuffle_list_add(&album->shuffle_info, &lib_album_shuffle_root, album); } static void album_shuffle_list_remove(struct album *album) { rb_erase(&album->shuffle_info.tree_node, &lib_album_shuffle_root); } static void views_add_track(struct track_info *ti) { struct tree_track *track = xnew(struct tree_track, 1); /* NOTE: does not ref ti */ simple_track_init((struct simple_track *)track, ti); /* both the hash table and views have refs */ track_info_ref(ti); tree_add_track(track, album_shuffle_list_add); shuffle_add(track); editable_add(&lib_editable, (struct simple_track *)track); } struct fh_entry { struct fh_entry *next; /* ref count is increased when added to this hash */ struct track_info *ti; }; #define FH_SIZE (1024) static struct fh_entry *ti_hash[FH_SIZE] = { NULL, }; static int hash_insert(struct track_info *ti) { const char *filename = ti->filename; unsigned int pos = hash_str(filename) % FH_SIZE; struct fh_entry **entryp; struct fh_entry *e; entryp = &ti_hash[pos]; e = *entryp; while (e) { if (strcmp(e->ti->filename, filename) == 0) { /* found, don't insert */ return 0; } e = e->next; } e = xnew(struct fh_entry, 1); track_info_ref(ti); e->ti = ti; e->next = *entryp; *entryp = e; return 1; } static void hash_remove(struct track_info *ti) { const char *filename = ti->filename; unsigned int pos = hash_str(filename) % FH_SIZE; struct fh_entry **entryp; entryp = &ti_hash[pos]; while (1) { struct fh_entry *e = *entryp; BUG_ON(e == NULL); if (strcmp(e->ti->filename, filename) == 0) { *entryp = e->next; track_info_unref(e->ti); free(e); break; } entryp = &e->next; } } static int is_filtered(struct track_info *ti) { if (live_filter_expr && !expr_eval(live_filter_expr, ti)) return 1; if (!live_filter_expr && lib_live_filter && !track_info_matches(ti, lib_live_filter, TI_MATCH_ALL)) return 1; if (filter && !expr_eval(filter, ti)) return 1; return 0; } static bool track_exists(struct track_info *ti) { struct rb_node *node; struct artist *artist; struct album *album; struct tree_track *track; if (!ti->collkey_title) return false; char *artist_collkey_name = u_strcasecoll_key(tree_artist_name(ti)); rb_for_each_entry(artist, node, &lib_artist_root, tree_node) { if (strcmp(artist->collkey_name, artist_collkey_name) == 0) break; } free(artist_collkey_name); if (!artist) return false; char *album_collkey_name = u_strcasecoll_key(tree_album_name(ti)); rb_for_each_entry(album, node, &artist->album_root, tree_node) { if (strcmp(album->collkey_name, album_collkey_name) == 0) break; } free(album_collkey_name); if (!album) return false; rb_for_each_entry(track, node, &album->track_root, tree_node) { struct track_info *iter_ti = tree_track_info(track); if (iter_ti->tracknumber == ti->tracknumber && iter_ti->discnumber == ti->discnumber && iter_ti->collkey_title && strcmp(iter_ti->collkey_title, ti->collkey_title) == 0) return true; } return false; } void lib_add_track(struct track_info *ti, void *opaque) { if (!ti) return; if (add_filter && !expr_eval(add_filter, ti)) { /* filter any files excluded by lib_add_filter */ return; } if (ignore_duplicates && track_exists(ti)) return; if (!hash_insert(ti)) { /* duplicate files not allowed */ return; } if (!is_filtered(ti)) views_add_track(ti); } static struct tree_track *album_first_track(const struct album *album) { return to_tree_track(rb_first(&album->track_root)); } static struct tree_track *artist_first_track(const struct artist *artist) { return album_first_track(to_album(rb_first(&artist->album_root))); } static struct tree_track *normal_get_first(void) { return artist_first_track(to_artist(rb_first(&lib_artist_root))); } static struct tree_track *album_last_track(const struct album *album) { return to_tree_track(rb_last(&album->track_root)); } static struct tree_track *artist_last_track(const struct artist *artist) { return album_last_track(to_album(rb_last(&artist->album_root))); } static struct tree_track *normal_get_last(void) { return artist_last_track(to_artist(rb_last(&lib_artist_root))); } static int aaa_mode_filter(const struct album *album) { if (aaa_mode == AAA_MODE_ALBUM) return CUR_ALBUM == album; if (aaa_mode == AAA_MODE_ARTIST) return CUR_ARTIST == album->artist; /* AAA_MODE_ALL */ return 1; } static int cur_album_filter(const struct album *album) { return CUR_ALBUM == album; } /* set next/prev (tree) {{{ */ static struct tree_track *normal_get_next(enum aaa_mode aaa, bool allow_repeat, bool skip_album) { if (lib_cur_track == NULL) { if (!allow_repeat) return NULL; return normal_get_first(); } /* not last track of the album? */ if (!skip_album && rb_next(&lib_cur_track->tree_node)) { /* next track of the current album */ return to_tree_track(rb_next(&lib_cur_track->tree_node)); } if (aaa == AAA_MODE_ALBUM) { if (!allow_repeat || !repeat) return NULL; /* first track of the current album */ return album_first_track(CUR_ALBUM); } /* not last album of the artist? */ if (rb_next(&CUR_ALBUM->tree_node) != NULL) { /* first track of the next album */ return album_first_track(to_album(rb_next(&CUR_ALBUM->tree_node))); } if (aaa == AAA_MODE_ARTIST) { if (!allow_repeat || !repeat) return NULL; /* first track of the first album of the current artist */ return artist_first_track(CUR_ARTIST); } /* not last artist of the library? */ if (rb_next(&CUR_ARTIST->tree_node) != NULL) { /* first track of the next artist */ return artist_first_track(to_artist(rb_next(&CUR_ARTIST->tree_node))); } if (!allow_repeat || !repeat) return NULL; /* first track */ return normal_get_first(); } static struct tree_track *normal_get_prev(enum aaa_mode aaa, bool allow_repeat, bool skip_album) { if (lib_cur_track == NULL) { if (!allow_repeat) return NULL; return normal_get_last(); } /* not first track of the album? */ if (!skip_album && rb_prev(&lib_cur_track->tree_node)) { /* prev track of the album */ return to_tree_track(rb_prev(&lib_cur_track->tree_node)); } if (aaa == AAA_MODE_ALBUM) { if (!allow_repeat || !repeat) return NULL; /* last track of the album */ return album_last_track(CUR_ALBUM); } /* not first album of the artist? */ if (rb_prev(&CUR_ALBUM->tree_node) != NULL) { /* last track of the prev album of the artist */ return album_last_track(to_album(rb_prev(&CUR_ALBUM->tree_node))); } if (aaa == AAA_MODE_ARTIST) { if (!allow_repeat || !repeat) return NULL; /* last track of the last album of the artist */ return album_last_track(to_album(rb_last(&CUR_ARTIST->album_root))); } /* not first artist of the library? */ if (rb_prev(&CUR_ARTIST->tree_node) != NULL) { /* last track of the last album of the prev artist */ return artist_last_track(to_artist(rb_prev(&CUR_ARTIST->tree_node))); } if (!allow_repeat || !repeat) return NULL; /* last track */ return normal_get_last(); } static struct tree_track *shuffle_album_get_next(void) { struct shuffle_info *shuffle_info = NULL; struct album *album; if (lib_cur_track != NULL) shuffle_info = &lib_cur_track->album->shuffle_info; album = (struct album *)shuffle_list_get_next(&lib_album_shuffle_root, shuffle_info, aaa_mode_filter); if (album != NULL) return album_first_track(album); return NULL; } static struct tree_track *shuffle_album_get_prev(void) { struct shuffle_info *shuffle_info = NULL; struct album *album; if (lib_cur_track != NULL) shuffle_info = &lib_cur_track->album->shuffle_info; album = (struct album *)shuffle_list_get_prev(&lib_album_shuffle_root, shuffle_info, aaa_mode_filter); if (album != NULL) return album_last_track(album); return NULL; } static struct tree_track *sorted_album_first_track(struct tree_track *track) { struct tree_track *prev = track; while (true) { prev = (struct tree_track *)simple_list_get_prev(&lib_editable.head, (struct simple_track *)prev, NULL, false); if (prev == NULL) return track; if (prev->album == track->album) track = prev; } } static struct tree_track *sorted_album_last_track(struct tree_track *track) { struct tree_track *next = track; while (true) { next = (struct tree_track *)simple_list_get_next(&lib_editable.head, (struct simple_track *)next, NULL, false); if (next == NULL) return track; if (next->album == track->album) track = next; } } /* set next/prev (tree) }}} */ void lib_reshuffle(void) { shuffle_list_reshuffle(&lib_shuffle_root); shuffle_list_reshuffle(&lib_album_shuffle_root); if (lib_cur_track) { shuffle_insert(&lib_shuffle_root, NULL, &lib_cur_track->simple_track.shuffle_info); shuffle_insert(&lib_album_shuffle_root, NULL, &lib_cur_track->album->shuffle_info); } } void lib_sort_artists(void) { tree_sort_artists(album_shuffle_list_add, album_shuffle_list_remove); } static void free_lib_track(struct editable *e, struct list_head *item) { struct tree_track *track = (struct tree_track *)to_simple_track(item); struct track_info *ti = tree_track_info(track); if (track == lib_cur_track) lib_cur_track = NULL; if (remove_from_hash) hash_remove(ti); rb_erase(&track->simple_track.shuffle_info.tree_node, &lib_shuffle_root); tree_remove(track, album_shuffle_list_remove); track_info_unref(ti); free(track); } void lib_init(void) { editable_shared_init(&lib_editable_shared, free_lib_track); editable_init(&lib_editable, &lib_editable_shared, 1); tree_init(); srand(time(NULL)); } struct track_info *lib_set_track(struct tree_track *track) { struct track_info *ti = NULL; if (track) { lib_cur_track = track; ti = tree_track_info(track); track_info_ref(ti); if (follow) { tree_sel_current(auto_expand_albums_follow); sorted_sel_current(); } all_wins_changed(); } return ti; } struct track_info *lib_goto_next(void) { struct tree_track *track; if (rb_root_empty(&lib_artist_root)) { BUG_ON(lib_cur_track != NULL); return NULL; } if (shuffle == SHUFFLE_TRACKS) { track = (struct tree_track *)shuffle_list_get_next(&lib_shuffle_root, (struct shuffle_info *)lib_cur_track, aaa_mode_filter); } else if (shuffle == SHUFFLE_ALBUMS) { if (play_sorted) track = (struct tree_track *)simple_list_get_next(&lib_editable.head, (struct simple_track *)lib_cur_track, cur_album_filter, false); else track = normal_get_next(AAA_MODE_ALBUM, false, false); if (track == NULL) { track = shuffle_album_get_next(); if (play_sorted) track = sorted_album_first_track(track); } } else if (play_sorted) { track = (struct tree_track *)simple_list_get_next(&lib_editable.head, (struct simple_track *)lib_cur_track, aaa_mode_filter, true); } else { track = normal_get_next(aaa_mode, true, false); } return lib_set_track(track); } struct track_info *lib_goto_prev(void) { struct tree_track *track; if (rb_root_empty(&lib_artist_root)) { BUG_ON(lib_cur_track != NULL); return NULL; } if (shuffle == SHUFFLE_TRACKS) { track = (struct tree_track *)shuffle_list_get_prev(&lib_shuffle_root, (struct shuffle_info *)lib_cur_track, aaa_mode_filter); } else if (shuffle == SHUFFLE_ALBUMS) { if (play_sorted) track = (struct tree_track *)simple_list_get_prev(&lib_editable.head, (struct simple_track *)lib_cur_track, cur_album_filter, false); else track = normal_get_prev(AAA_MODE_ALBUM, false, false); if (track == NULL) { track = shuffle_album_get_prev(); if (play_sorted) track = sorted_album_last_track(track); } } else if (play_sorted) { track = (struct tree_track *)simple_list_get_prev(&lib_editable.head, (struct simple_track *)lib_cur_track, aaa_mode_filter, true); } else { track = normal_get_prev(aaa_mode, true, false); } return lib_set_track(track); } struct track_info *lib_goto_next_album(void) { struct tree_track *track = NULL; if (rb_root_empty(&lib_artist_root)) { BUG_ON(lib_cur_track != NULL); return NULL; } if (shuffle == SHUFFLE_TRACKS) { return lib_goto_next(); } else if (shuffle == SHUFFLE_ALBUMS) { track = shuffle_album_get_next(); if (play_sorted) track = sorted_album_first_track(track); } else if (play_sorted) { track = sorted_album_last_track(lib_cur_track); track = (struct tree_track *)simple_list_get_next(&lib_editable.head, (struct simple_track *)track, aaa_mode_filter, true); } else { track = normal_get_next(aaa_mode, true, true); } return lib_set_track(track); } struct track_info *lib_goto_prev_album(void) { struct tree_track *track = NULL; if (rb_root_empty(&lib_artist_root)) { BUG_ON(lib_cur_track != NULL); return NULL; } if (shuffle == SHUFFLE_TRACKS) { return lib_goto_prev(); } else if (shuffle == SHUFFLE_ALBUMS) { track = shuffle_album_get_prev(); if (play_sorted) track = sorted_album_first_track(track); else if (track) track = album_first_track(track->album); } else if (play_sorted) { track = sorted_album_first_track(lib_cur_track); track = (struct tree_track *)simple_list_get_prev(&lib_editable.head, (struct simple_track *)track, aaa_mode_filter, true); track = sorted_album_first_track(track); } else { track = normal_get_prev(aaa_mode, true, true); if (track) track = album_first_track(track->album); } return lib_set_track(track); } static struct tree_track *sorted_get_selected(void) { struct iter sel; if (list_empty(&lib_editable.head)) return NULL; window_get_sel(lib_editable.shared->win, &sel); return iter_to_sorted_track(&sel); } struct track_info *sorted_activate_selected(void) { return lib_set_track(sorted_get_selected()); } static void hash_add_to_views(void) { int i; for (i = 0; i < FH_SIZE; i++) { struct fh_entry *e; e = ti_hash[i]; while (e) { struct track_info *ti = e->ti; if (!is_filtered(ti) && !(ignore_duplicates && track_exists(ti))) views_add_track(ti); e = e->next; } } } struct tree_track *lib_find_track(struct track_info *ti) { struct simple_track *track; list_for_each_entry(track, &lib_editable.head, node) { if (strcmp(track->info->filename, ti->filename) == 0) { struct tree_track *tt = (struct tree_track *)track; return tt; } } return NULL; } void lib_store_cur_track(struct track_info *ti) { if (cur_track_ti) track_info_unref(cur_track_ti); cur_track_ti = ti; track_info_ref(cur_track_ti); } struct track_info *lib_get_cur_stored_track(void) { if (cur_track_ti && lib_find_track(cur_track_ti)) return cur_track_ti; return NULL; } static void restore_cur_track(struct track_info *ti) { struct tree_track *tt = lib_find_track(ti); if (tt) lib_cur_track = tt; } static int is_filtered_cb(void *data, struct track_info *ti) { return is_filtered(ti); } static void do_lib_filter(int clear_before) { /* try to save cur_track */ if (lib_cur_track) lib_store_cur_track(tree_track_info(lib_cur_track)); if (clear_before) d_print("filter results could grow, clear tracks and re-add (slow)\n"); remove_from_hash = 0; if (clear_before) { editable_clear(&lib_editable); hash_add_to_views(); } else editable_remove_matching_tracks(&lib_editable, is_filtered_cb, NULL); remove_from_hash = 1; window_changed(lib_editable.shared->win); window_goto_top(lib_editable.shared->win); lib_cur_win = lib_tree_win; window_goto_top(lib_tree_win); /* restore cur_track */ if (cur_track_ti && !lib_cur_track) restore_cur_track(cur_track_ti); } static void unset_live_filter(void) { free(lib_live_filter); lib_live_filter = NULL; free(live_filter_expr); live_filter_expr = NULL; } void lib_set_filter(struct expr *expr) { int clear_before = lib_live_filter || filter; unset_live_filter(); if (filter) expr_free(filter); filter = expr; do_lib_filter(clear_before); } void lib_set_add_filter(struct expr *expr) { if (add_filter) expr_free(add_filter); add_filter = expr; } static struct tree_track *get_sel_track(void) { switch (cur_view) { case TREE_VIEW: return tree_get_selected(); case SORTED_VIEW: return sorted_get_selected(); } return NULL; } static void set_sel_track(struct tree_track *tt) { struct iter iter; switch (cur_view) { case TREE_VIEW: tree_sel_track(tt, auto_expand_albums_selcur); break; case SORTED_VIEW: sorted_track_to_iter(tt, &iter); window_set_sel(lib_editable.shared->win, &iter); break; } } static void store_sel_track(void) { struct tree_track *tt = get_sel_track(); if (tt) { sel_track_ti = tree_track_info(tt); track_info_ref(sel_track_ti); } } static void restore_sel_track(void) { if (sel_track_ti) { struct tree_track *tt = lib_find_track(sel_track_ti); if (tt) { set_sel_track(tt); track_info_unref(sel_track_ti); sel_track_ti = NULL; } } } /* determine if filter results could grow, in which case all tracks must be cleared and re-added */ static int do_clear_before(const char *str, struct expr *expr) { if (!lib_live_filter) return 0; if (!str) return 1; if ((!expr && live_filter_expr) || (expr && !live_filter_expr)) return 1; if (!expr || expr_is_harmless(expr)) return !strstr(str, lib_live_filter); return 1; } void lib_set_live_filter(const char *str) { int clear_before; struct expr *expr = NULL; if (strcmp0(str, lib_live_filter) == 0) return; if (str && expr_is_short(str)) { expr = expr_parse(str); if (!expr) return; } clear_before = do_clear_before(str, expr); if (!str) store_sel_track(); unset_live_filter(); lib_live_filter = str ? xstrdup(str) : NULL; live_filter_expr = expr; do_lib_filter(clear_before); if (expr) { unsigned int match_type = expr_get_match_type(expr); if (match_type & TI_MATCH_ALBUM) tree_expand_all(); if (match_type & TI_MATCH_TITLE) tree_sel_first(); } else if (str) tree_expand_matching(str); if (!str) restore_sel_track(); } int lib_remove(struct track_info *ti) { struct simple_track *track; list_for_each_entry(track, &lib_editable.head, node) { if (track->info == ti) { editable_remove_track(&lib_editable, track); return 1; } } return 0; } void lib_clear_store(void) { int i; for (i = 0; i < FH_SIZE; i++) { struct fh_entry *e, *next; e = ti_hash[i]; while (e) { next = e->next; track_info_unref(e->ti); free(e); e = next; } ti_hash[i] = NULL; } } void sorted_sel_current(void) { if (lib_cur_track) { struct iter iter; sorted_track_to_iter(lib_cur_track, &iter); window_set_sel(lib_editable.shared->win, &iter); } } static int ti_cmp(const void *a, const void *b) { const struct track_info *ai = *(const struct track_info **)a; const struct track_info *bi = *(const struct track_info **)b; return track_info_cmp(ai, bi, lib_editable.shared->sort_keys); } static int do_lib_for_each(int (*cb)(void *data, struct track_info *ti), void *data, int filtered) { int i, rc = 0, count = 0, size = 1024; struct track_info **tis; tis = xnew(struct track_info *, size); /* collect all track_infos */ for (i = 0; i < FH_SIZE; i++) { struct fh_entry *e; e = ti_hash[i]; while (e) { if (count == size) { size *= 2; tis = xrenew(struct track_info *, tis, size); } if (!filtered || !filter || expr_eval(filter, e->ti)) tis[count++] = e->ti; e = e->next; } } /* sort to speed up playlist loading */ qsort(tis, count, sizeof(struct track_info *), ti_cmp); for (i = 0; i < count; i++) { rc = cb(data, tis[i]); if (rc) break; } free(tis); return rc; } int lib_for_each(int (*cb)(void *data, struct track_info *ti), void *data, void *opaque) { return do_lib_for_each(cb, data, 0); } int lib_for_each_filtered(int (*cb)(void *data, struct track_info *ti), void *data, void *opaque) { return do_lib_for_each(cb, data, 1); }