This commit is contained in:
2026-03-29 14:01:52 +03:00
commit 0611279128
210 changed files with 60454 additions and 0 deletions

928
lib.c Normal file
View File

@@ -0,0 +1,928 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <pthread.h>
#include <string.h>
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);
}