Files
cmus/tree.c
2026-03-29 14:01:52 +03:00

1396 lines
34 KiB
C

/*
* Copyright 2008-2013 Various Authors
* Copyright 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 "search_mode.h"
#include "xmalloc.h"
#include "utils.h"
#include "debug.h"
#include "mergesort.h"
#include "options.h"
#include "u_collate.h"
#include "rbtree.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
struct searchable *tree_searchable;
struct window *lib_tree_win;
struct window *lib_track_win;
struct window *lib_cur_win;
struct rb_root lib_artist_root;
struct track_iter {
struct rb_root *root;
struct tree_track *track;
struct album *album;
};
static inline int tree_album_selected(void)
{
return iter_to_album(&lib_tree_win->sel) != NULL;
}
static inline int track_visible(struct tree_track *track)
{
if (tree_album_selected())
return track->album == iter_to_album(&lib_tree_win->sel);
else if (show_all_tracks)
return track->album->artist == iter_to_artist(&lib_tree_win->sel);
else
return 0;
}
static inline void tree_search_track_to_iter(struct tree_track *track, struct iter *iter)
{
iter->data0 = &lib_artist_root;
iter->data1 = track;
iter->data2 = NULL;
}
static inline void album_to_iter(struct album *album, struct iter *iter)
{
iter->data0 = &lib_artist_root;
iter->data1 = album->artist;
iter->data2 = album;
}
static inline void album_to_track_iter(struct album *album, struct track_iter *iter)
{
iter->root = &album->artist->album_root;
iter->album = album;
iter->track = (struct tree_track *)album;
}
static inline void artist_to_iter(struct artist *artist, struct iter *iter)
{
iter->data0 = &lib_artist_root;
iter->data1 = artist;
iter->data2 = NULL;
}
static inline void tree_track_to_track_iter(struct tree_track *track, struct track_iter *iter)
{
if (tree_album_selected()) {
iter->root = &track->album->track_root;
iter->album = NULL;
} else {
iter->root = &track->album->artist->album_root;
iter->album = track->album;
}
iter->track = track;
}
static void tree_set_expand_artist(struct artist *artist, int expand)
{
struct iter sel;
if (!expand) {
/* deselect album, select artist */
artist_to_iter(artist, &sel);
window_set_sel(lib_tree_win, &sel);
if (!show_all_tracks)
lib_cur_win = lib_tree_win;
}
artist->expanded = expand;
window_changed(lib_tree_win);
}
/* tree (search) iterators {{{ */
static int tree_search_get_prev(struct iter *iter)
{
struct rb_root *root = iter->data0;
struct tree_track *track = iter->data1;
struct artist *artist;
struct album *album;
BUG_ON(iter->data2);
if (root == NULL)
return 0;
if (track == NULL) {
/* head, get last track */
if (rb_root_empty(root)) {
/* empty, iter points to the head already */
return 0;
}
artist = to_artist(rb_last(root));
album = to_album(rb_last(&artist->album_root));
iter->data1 = to_tree_track(rb_last(&album->track_root));
return 1;
}
/* prev track */
if (rb_prev(&track->tree_node) == NULL || search_restricted) {
/* prev album */
if (rb_prev(&track->album->tree_node) == NULL) {
/* prev artist */
if (rb_prev(&track->album->artist->tree_node) == NULL)
return 0;
artist = to_artist(rb_prev(&track->album->artist->tree_node));
album = to_album(rb_last(&artist->album_root));
track = to_tree_track(rb_last(&album->track_root));
} else {
album = to_album(rb_prev(&track->album->tree_node));
track = to_tree_track(rb_last(&album->track_root));
}
} else {
track = to_tree_track(rb_prev(&track->tree_node));
}
iter->data1 = track;
return 1;
}
static int tree_search_get_next(struct iter *iter)
{
struct rb_root *root = iter->data0;
struct tree_track *track = iter->data1;
struct artist *artist;
struct album *album;
BUG_ON(iter->data2);
if (root == NULL)
return 0;
if (track == NULL) {
/* head, get first track */
if (rb_root_empty(root)) {
/* empty, iter points to the head already */
return 0;
}
artist = to_artist(rb_first(root));
album = to_album(rb_first(&artist->album_root));
iter->data1 = to_tree_track(rb_first(&album->track_root));
return 1;
}
/* next track */
if (rb_next(&track->tree_node) == NULL || search_restricted) {
/* next album */
if (rb_next(&track->album->tree_node) == NULL) {
/* next artist */
if (rb_next(&track->album->artist->tree_node) == NULL)
return 0;
artist = to_artist(rb_next(&track->album->artist->tree_node));
album = to_album(rb_first(&artist->album_root));
track = to_tree_track(rb_first(&album->track_root));
} else {
album = to_album(rb_next(&track->album->tree_node));
track = to_tree_track(rb_first(&album->track_root));
}
} else {
track = to_tree_track(rb_next(&track->tree_node));
}
iter->data1 = track;
return 1;
}
/* }}} */
/* tree window iterators {{{ */
static int tree_get_prev(struct iter *iter)
{
struct rb_root *root = iter->data0;
struct artist *artist = iter->data1;
struct album *album = iter->data2;
BUG_ON(root == NULL);
BUG_ON(artist == NULL && album != NULL);
if (artist == NULL) {
/* head, get last artist and/or album */
if (rb_root_empty(root)) {
/* empty, iter points to the head already */
return 0;
}
artist = to_artist(rb_last(root));
if (artist->expanded) {
album = to_album(rb_last(&artist->album_root));
} else {
album = NULL;
}
iter->data1 = artist;
iter->data2 = album;
return 1;
}
if (artist->expanded && album) {
/* prev album */
if (rb_prev(&album->tree_node) == NULL) {
iter->data2 = NULL;
return 1;
} else {
iter->data2 = to_album(rb_prev(&album->tree_node));
return 1;
}
}
/* prev artist */
if (rb_prev(&artist->tree_node) == NULL) {
iter->data1 = NULL;
iter->data2 = NULL;
return 0;
}
artist = to_artist(rb_prev(&artist->tree_node));
iter->data1 = artist;
iter->data2 = NULL;
if (artist->expanded) {
/* last album */
iter->data2 = to_album(rb_last(&artist->album_root));
}
return 1;
}
static int tree_get_next(struct iter *iter)
{
struct rb_root *root = iter->data0;
struct artist *artist = iter->data1;
struct album *album = iter->data2;
BUG_ON(root == NULL);
BUG_ON(artist == NULL && album != NULL);
if (artist == NULL) {
/* head, get first artist */
if (rb_root_empty(root)) {
/* empty, iter points to the head already */
return 0;
}
iter->data1 = to_artist(rb_first(root));
iter->data2 = NULL;
return 1;
}
if (artist->expanded) {
/* next album */
if (album == NULL) {
/* first album */
iter->data2 = to_album(rb_first(&artist->album_root));
return 1;
}
if (rb_next(&album->tree_node) != NULL) {
iter->data2 = to_album(rb_next(&album->tree_node));
return 1;
}
}
/* next artist */
if (rb_next(&artist->tree_node) == NULL) {
iter->data1 = NULL;
iter->data2 = NULL;
return 0;
}
iter->data1 = to_artist(rb_next(&artist->tree_node));
iter->data2 = NULL;
return 1;
}
/* }}} */
static GENERIC_TREE_ITER_PREV(tree_track_get_prev_by_album, struct tree_track, tree_node)
static GENERIC_TREE_ITER_NEXT(tree_track_get_next_by_album, struct tree_track, tree_node)
/* track window iterators by artist */
static int tree_track_get_prev_by_artist(struct iter *iter)
{
struct track_iter *it = (struct track_iter *)iter;
/* get last album */
if (it->album == NULL) {
if (rb_root_empty(it->root))
return 0;
it->album = to_album(rb_last(it->root));
it->track = NULL;
return tree_track_get_prev_by_artist(iter);
}
/* get previous album */
if (it->track == (struct tree_track *)it->album) {
/* no more albums */
if (rb_prev(&it->album->tree_node) == NULL)
return 0;
it->album = to_album(rb_prev(&it->album->tree_node));
it->track = NULL;
return tree_track_get_prev_by_artist(iter);
}
/* get last track */
if (it->track == NULL) {
if (rb_root_empty(&it->album->track_root))
it->track = (struct tree_track *)it->album;
else
it->track = to_tree_track(rb_last(&it->album->track_root));
return 1;
}
/* get previous track */
if (rb_prev(&it->track->tree_node) != NULL) {
it->track = to_tree_track(rb_prev(&it->track->tree_node));
return 1;
}
/* no more tracks
* get album header */
it->track = (struct tree_track *)it->album;
return 1;
}
static int tree_track_get_next_by_artist(struct iter *iter)
{
struct track_iter *it = (struct track_iter *)iter;
/* get first album */
if (it->album == NULL) {
if (rb_root_empty(it->root))
return 0;
it->album = to_album(rb_first(it->root));
it->track = (struct tree_track *)it->album;
return 1;
}
/* get first track */
if (it->track == (struct tree_track *)it->album) {
/* this happens when the last track in an album is deleted but
* the album header is still referenced in lib_track_win->top
* set track = NULL to skip the next `if` without goto */
if (rb_root_empty(&it->album->track_root)) {
it->track = NULL;
} else {
it->track = to_tree_track(rb_first(&it->album->track_root));
return 1;
}
}
/* get next track */
if (it->track != NULL && rb_next(&it->track->tree_node) != NULL) {
it->track = to_tree_track(rb_next(&it->track->tree_node));
return 1;
}
/* no more tracks
* get next album */
if (rb_next(&it->album->tree_node) != NULL) {
it->album = to_album(rb_next(&it->album->tree_node));
it->track = (struct tree_track *)it->album;
return 1;
}
/* no more albums */
return 0;
}
/* track window iterators */
static int tree_track_get_prev(struct iter *iter)
{
if (tree_album_selected())
return tree_track_get_prev_by_album(iter);
else
return tree_track_get_prev_by_artist(iter);
}
static int tree_track_get_next(struct iter *iter)
{
if (tree_album_selected())
return tree_track_get_next_by_album(iter);
else
return tree_track_get_next_by_artist(iter);
}
/* search (tree) {{{ */
static int tree_search_get_current(void *data, struct iter *iter, enum search_direction dir)
{
struct artist *artist;
struct album *album;
struct tree_track *track;
struct iter tmpiter;
if (rb_root_empty(&lib_artist_root))
return 0;
if (window_get_sel(lib_track_win, &tmpiter)) {
track = iter_to_tree_track(&tmpiter);
if (search_restricted) {
/**
* tree_search_get_next/tree_search_get_prev only return
* the first/last (for forward/backwards) track node for
* restricted search, so we need to make this match so
* do_u_search's check for if we've gone back to the
* starting iter eventually succeeds
*/
if (dir == SEARCH_FORWARD) {
track = to_tree_track(rb_first(&track->album->track_root));
} else {
track = to_tree_track(rb_last(&track->album->track_root));
}
}
tree_search_track_to_iter(track, iter);
return 1;
}
/* artist not expanded. track_win is empty set tmp to the first/last
* track of the selected artist for forwards/backwards respectively */
window_get_sel(lib_tree_win, &tmpiter);
artist = iter_to_artist(&tmpiter);
album = to_album(rb_first(&artist->album_root));
if (search_restricted) {
if (dir == SEARCH_FORWARD) {
track = to_tree_track(rb_first(&album->track_root));
} else {
track = to_tree_track(rb_last(&album->track_root));
}
} else {
track = to_tree_track(rb_first(&album->track_root));
}
tree_search_track_to_iter(track, iter);
return 1;
}
static inline struct tree_track *iter_to_tree_search_track(const struct iter *iter)
{
BUG_ON(iter->data0 != &lib_artist_root);
return iter->data1;
}
static int tree_search_matches(void *data, struct iter *iter, const char *text);
static const struct searchable_ops tree_search_ops = {
.get_prev = tree_search_get_prev,
.get_next = tree_search_get_next,
.get_current = tree_search_get_current,
.matches = tree_search_matches
};
/* search (tree) }}} */
static void tree_sel_changed(void)
{
tree_sel_update(1);
}
void tree_sel_update(int changed)
{
struct iter sel;
struct album *album;
struct artist *artist;
window_get_sel(lib_tree_win, &sel);
album = iter_to_album(&sel);
artist = show_all_tracks ? iter_to_artist(&sel) : NULL;
if (album != NULL) {
if (changed)
window_set_contents(lib_track_win, &album->track_root);
} else if (artist != NULL) {
window_set_contents(lib_track_win, &artist->album_root);
} else {
if (lib_cur_win != lib_tree_win) {
lib_cur_win = lib_tree_win;
lib_tree_win->changed = 1;
}
window_set_empty(lib_track_win);
}
}
static inline void tree_win_get_selected(struct artist **artist, struct album **album)
{
struct iter sel;
*artist = NULL;
*album = NULL;
if (window_get_sel(lib_tree_win, &sel)) {
*artist = iter_to_artist(&sel);
*album = iter_to_album(&sel);
}
}
static char *auto_artist_sort_name(const char *name)
{
const char *name_orig = name;
char *buf;
if (strncasecmp(name, "the ", 4) != 0)
return NULL;
name += 4;
while (isspace((int)*name))
++name;
if (*name == '\0')
return NULL;
buf = xnew(char, strlen(name_orig) + 2);
sprintf(buf, "%s, %c%c%c", name, name_orig[0],
name_orig[1],
name_orig[2]);
return buf;
}
static struct artist *artist_new(const char *name, const char *sort_name, int is_compilation)
{
struct artist *a = xnew(struct artist, 1);
a->name = xstrdup(name);
a->sort_name = sort_name ? xstrdup(sort_name) : NULL;
a->auto_sort_name = auto_artist_sort_name(name);
a->collkey_name = u_strcasecoll_key(a->name);
a->collkey_sort_name = u_strcasecoll_key0(a->sort_name);
a->collkey_auto_sort_name = u_strcasecoll_key0(a->auto_sort_name);
a->expanded = 0;
a->is_compilation = is_compilation;
rb_root_init(&a->album_root);
return a;
}
static struct artist *artist_copy(const struct artist *artist)
{
return artist_new(artist->name, artist->sort_name, artist->is_compilation);
}
static void artist_free(struct artist *artist)
{
free(artist->name);
free(artist->sort_name);
free(artist->auto_sort_name);
free(artist->collkey_name);
free(artist->collkey_sort_name);
free(artist->collkey_auto_sort_name);
free(artist);
}
static struct album *album_new(struct artist *artist, const char *name,
const char *sort_name, int date)
{
struct album *album = xnew(struct album, 1);
album->name = xstrdup(name);
album->sort_name = sort_name ? xstrdup(sort_name) : NULL;
album->collkey_name = u_strcasecoll_key(name);
album->collkey_sort_name = u_strcasecoll_key0(sort_name);
album->date = date;
album->min_date = date;
rb_root_init(&album->track_root);
album->artist = artist;
return album;
}
static void album_free(struct album *album)
{
free(album->name);
free(album->sort_name);
free(album->collkey_name);
free(album->collkey_sort_name);
free(album);
}
static int track_selectable(struct iter *iter)
{
struct track_iter *it = (struct track_iter *)iter;
return it->album != (struct album *)it->track;
}
void tree_init(void)
{
struct iter iter;
rb_root_init(&lib_artist_root);
lib_tree_win = window_new(tree_get_prev, tree_get_next);
lib_track_win = window_new(tree_track_get_prev, tree_track_get_next);
lib_cur_win = lib_tree_win;
lib_tree_win->sel_changed = tree_sel_changed;
lib_track_win->selectable = track_selectable;
window_set_empty(lib_track_win);
window_set_contents(lib_tree_win, &lib_artist_root);
iter.data0 = &lib_artist_root;
iter.data1 = NULL;
iter.data2 = NULL;
tree_searchable = searchable_new(NULL, &iter, &tree_search_ops);
}
struct tree_track *tree_get_selected(void)
{
struct artist *artist;
struct album *album;
struct tree_track *track;
struct iter sel;
if (rb_root_empty(&lib_artist_root))
return NULL;
tree_win_get_selected(&artist, &album);
if (album == NULL && !show_all_tracks) {
/* track window is empty
* => get first album of the selected artist and first track of that album
*/
album = to_album(rb_first(&artist->album_root));
track = to_tree_track(rb_first(&album->track_root));
} else {
window_get_sel(lib_track_win, &sel);
track = iter_to_tree_track(&sel);
}
return track;
}
struct track_info *tree_activate_selected(void)
{
struct track_info *info;
lib_cur_track = tree_get_selected();
if (!lib_cur_track)
return NULL;
lib_tree_win->changed = 1;
lib_track_win->changed = 1;
info = tree_track_info(lib_cur_track);
track_info_ref(info);
return info;
}
static int special_name_cmp(const char *a, const char *collkey_a,
const char *b, const char *collkey_b)
{
/* keep <Stream> etc. top */
int cmp = (*a != '<') - (*b != '<');
if (cmp)
return cmp;
return strcmp(collkey_a, collkey_b);
}
static inline const char *album_sort_collkey(const struct album *a)
{
if (a->sort_name)
return a->collkey_sort_name;
return a->collkey_name;
}
static int special_album_cmp(const struct album *a, const struct album *b)
{
return special_name_cmp(a->name, album_sort_collkey(a), b->name, album_sort_collkey(b));
}
static int special_album_cmp_date(const struct album *a, const struct album *b)
{
/* keep <Stream> etc. top */
int cmp = (*a->name != '<') - (*b->name != '<');
if (cmp)
return cmp;
cmp = a->date - b->date;
if (cmp)
return cmp;
return strcmp(album_sort_collkey(a), album_sort_collkey(b));
}
/* has to follow the same logic as artist_sort_name() */
static inline const char *artist_sort_collkey(const struct artist *a)
{
if (a->sort_name)
return a->collkey_sort_name;
if (smart_artist_sort && a->auto_sort_name)
return a->collkey_auto_sort_name;
return a->collkey_name;
}
static struct artist *do_find_artist(const struct artist *artist,
struct rb_root *root,
struct rb_node ***p_new,
struct rb_node **p_parent)
{
struct rb_node **new = &(root->rb_node), *parent = NULL;
const char *a = artist_sort_name(artist);
const char *collkey_a = artist_sort_collkey(artist);
while (*new) {
struct artist *cur_artist = to_artist(*new);
const char *b = artist_sort_name(cur_artist);
const char *collkey_b = artist_sort_collkey(cur_artist);
int result = special_name_cmp(a, collkey_a, b, collkey_b);
parent = *new;
if (result < 0)
new = &((*new)->rb_left);
else if (result > 0)
new = &((*new)->rb_right);
else
return cur_artist;
}
if (p_new)
*p_new = new;
if (p_parent)
*p_parent = parent;
return NULL;
}
/* search (tree) {{{ */
static struct artist *collapse_artist;
static int tree_search_matches(void *data, struct iter *iter, const char *text)
{
struct tree_track *track;
struct iter tmpiter;
unsigned int flags = TI_MATCH_ARTIST | TI_MATCH_ALBUM | TI_MATCH_ALBUMARTIST;
if (!search_restricted)
flags |= TI_MATCH_TITLE;
track = iter_to_tree_search_track(iter);
if (!track_info_matches(tree_track_info(track), text, flags))
return 0;
if (auto_expand_albums_search) {
/* collapse old search result */
if (collapse_artist) {
struct artist *artist = do_find_artist(collapse_artist, &lib_artist_root, NULL, NULL);
if (artist && artist != track->album->artist) {
if (artist->expanded)
tree_set_expand_artist(artist, 0);
artist_free(collapse_artist);
collapse_artist = (!track->album->artist->expanded) ? artist_copy(track->album->artist) : NULL;
}
} else if (!track->album->artist->expanded) {
collapse_artist = artist_copy(track->album->artist);
}
track->album->artist->expanded = 1;
album_to_iter(track->album, &tmpiter);
} else {
artist_to_iter(track->album->artist, &tmpiter);
}
window_set_sel(lib_tree_win, &tmpiter);
tree_track_to_track_iter(track, (struct track_iter *)&tmpiter);
window_set_sel(lib_track_win, &tmpiter);
return 1;
}
/* search (tree) }}} */
static void insert_artist(struct artist *artist, struct rb_root *root)
{
struct rb_node **new = &(root->rb_node), *parent = NULL;
struct artist *found;
found = do_find_artist(artist, root, &new, &parent);
if (!found) {
rb_link_node(&artist->tree_node, parent, new);
rb_insert_color(&artist->tree_node, root);
}
}
static void add_artist(struct artist *artist)
{
insert_artist(artist, &lib_artist_root);
}
static struct artist *find_artist(const struct artist *artist)
{
return do_find_artist(artist, &lib_artist_root, NULL, NULL);
}
static struct album *do_find_album(const struct album *album,
int (*cmp)(const struct album *, const struct album *),
struct rb_node ***p_new,
struct rb_node **p_parent)
{
struct rb_node **new = &(album->artist->album_root.rb_node), *parent = NULL;
while (*new) {
struct album *a = to_album(*new);
int result = cmp(album, a);
parent = *new;
if (result < 0)
new = &((*new)->rb_left);
else if (result > 0)
new = &((*new)->rb_right);
else
return a;
}
if (p_new)
*p_new = new;
if (p_parent)
*p_parent = parent;
return NULL;
}
static struct album *find_album(const struct album *album)
{
struct album *a;
struct rb_node *tmp;
/* do a linear search because we want find albums with different date */
rb_for_each_entry(a, tmp, &album->artist->album_root, tree_node) {
if (special_album_cmp(album, a) == 0)
return a;
}
return NULL;
}
static void add_album(struct album *album)
{
struct rb_node **new = &(album->artist->album_root.rb_node), *parent = NULL;
struct album *found;
if (sort_albums_by_name || album->artist->is_compilation)
found = do_find_album(album, special_album_cmp, &new, &parent);
else
found = do_find_album(album, special_album_cmp_date, &new, &parent);
if (!found) {
rb_link_node(&album->tree_node, parent, new);
rb_insert_color(&album->tree_node, &album->artist->album_root);
}
}
static void album_add_track(struct album *album, struct tree_track *track)
{
/*
* NOTE: This is not perfect. You should ignore track numbers if
* either is unset and use filename instead, but usually you
* have all track numbers set or all unset (within one album
* of course).
*/
static const sort_key_t album_track_sort_keys[] = {
SORT_DISCNUMBER, SORT_TRACKNUMBER, SORT_FILENAME, SORT_INVALID
};
struct rb_node **new = &(album->track_root.rb_node), *parent = NULL;
track->album = album;
while (*new) {
const struct simple_track *a = (const struct simple_track *) track;
const struct simple_track *b = (const struct simple_track *) to_tree_track(*new);
int result = track_info_cmp(a->info, b->info, album_track_sort_keys);
parent = *new;
if (result < 0)
new = &((*new)->rb_left);
else
new = &((*new)->rb_right);
}
rb_link_node(&track->tree_node, parent, new);
rb_insert_color(&track->tree_node, &album->track_root);
}
const char *tree_artist_name(const struct track_info* ti)
{
const char *val = ti->albumartist;
if (ti->is_va_compilation)
val = "<Various Artists>";
if (!val || strcmp(val, "") == 0)
val = "<No Name>";
return val;
}
const char *tree_album_name(const struct track_info* ti)
{
const char *val = ti->album;
if (!val || strcmp(val, "") == 0)
val = "<No Name>";
return val;
}
static void remove_album(struct album *album)
{
if (album->artist->expanded) {
struct iter iter;
album_to_iter(album, &iter);
window_row_vanishes(lib_tree_win, &iter);
}
rb_erase(&album->tree_node, &album->artist->album_root);
}
static void remove_artist(struct artist *artist)
{
struct iter iter;
artist_to_iter(artist, &iter);
window_row_vanishes(lib_tree_win, &iter);
rb_erase(&artist->tree_node, &lib_artist_root);
}
void tree_add_track(struct tree_track *track,
void (*add_album_cb)(struct album *))
{
const struct track_info *ti = tree_track_info(track);
const char *album_name, *artist_name, *artistsort_name = NULL;
const char *albumsort_name = NULL;
struct artist *artist, *new_artist;
struct album *album, *new_album;
int date;
int is_va_compilation = 0;
date = ti->originaldate;
if (date < 0)
date = ti->date;
if (is_http_url(ti->filename)) {
artist_name = "<Stream>";
album_name = "<Stream>";
} else {
album_name = tree_album_name(ti);
artist_name = tree_artist_name(ti);
artistsort_name = ti->artistsort;
albumsort_name = ti->albumsort;
is_va_compilation = ti->is_va_compilation;
}
new_artist = artist_new(artist_name, artistsort_name, is_va_compilation);
album = NULL;
artist = find_artist(new_artist);
if (artist) {
artist_free(new_artist);
new_album = album_new(artist, album_name, albumsort_name, date);
album = find_album(new_album);
if (album)
album_free(new_album);
} else
new_album = album_new(new_artist, album_name, albumsort_name, date);
if (artist) {
int changed = 0;
/* If it makes sense to update sort_name, do it */
if (!artist->sort_name && artistsort_name) {
artist->sort_name = xstrdup(artistsort_name);
artist->collkey_sort_name = u_strcasecoll_key(artistsort_name);
changed = 1;
}
/* If names differ, update */
if (!artist->auto_sort_name) {
char *auto_sort_name = auto_artist_sort_name(artist_name);
if (auto_sort_name) {
free(artist->name);
free(artist->collkey_name);
artist->name = xstrdup(artist_name);
artist->collkey_name = u_strcasecoll_key(artist_name);
artist->auto_sort_name = auto_sort_name;
artist->collkey_auto_sort_name = u_strcasecoll_key(auto_sort_name);
changed = 1;
}
}
if (changed) {
remove_artist(artist);
add_artist(artist);
window_changed(lib_tree_win);
}
}
if (album) {
album_add_track(album, track);
/* If it makes sense to update album date, do it */
if (album->date < date) {
album->date = date;
remove_album(album);
add_album(album);
if (artist->expanded)
window_changed(lib_tree_win);
}
if (album->min_date <= 0 || (album->min_date > date && date > 0)) {
album->min_date = date;
remove_album(album);
add_album(album);
if (artist->expanded)
window_changed(lib_tree_win);
}
} else if (artist) {
add_album(new_album);
add_album_cb(new_album);
album_add_track(new_album, track);
if (artist->expanded)
window_changed(lib_tree_win);
} else {
add_artist(new_artist);
add_album(new_album);
add_album_cb(new_album);
album_add_track(new_album, track);
window_changed(lib_tree_win);
}
if (track_visible(track))
window_changed(lib_track_win);
}
static void remove_sel_artist(struct artist *artist)
{
struct rb_node *a_node, *a_tmp;
rb_for_each_safe(a_node, a_tmp, &artist->album_root) {
struct rb_node *t_node, *t_tmp;
struct album *album = to_album(a_node);
rb_for_each_safe(t_node, t_tmp, &album->track_root) {
struct tree_track *track = to_tree_track(t_node);
editable_remove_track(&lib_editable, (struct simple_track *)track);
}
/* all tracks removed => album removed
* if the last album was removed then the artist was removed too
*/
}
}
static void remove_sel_album(struct album *album)
{
struct rb_node *node, *tmp;
rb_for_each_safe(node, tmp, &album->track_root) {
struct tree_track *track = to_tree_track(node);
editable_remove_track(&lib_editable, (struct simple_track *)track);
}
}
static void tree_win_remove_sel(void)
{
struct artist *artist;
struct album *album;
tree_win_get_selected(&artist, &album);
if (album) {
remove_sel_album(album);
} else if (artist) {
remove_sel_artist(artist);
}
}
static void track_win_remove_sel(void)
{
struct iter sel;
struct tree_track *track;
if (window_get_sel(lib_track_win, &sel)) {
track = iter_to_tree_track(&sel);
BUG_ON(track == NULL);
editable_remove_track(&lib_editable, (struct simple_track *)track);
}
}
void tree_toggle_active_window(void)
{
if (lib_cur_win == lib_tree_win) {
struct artist *artist;
struct album *album;
tree_win_get_selected(&artist, &album);
if (album || (artist && show_all_tracks)) {
lib_cur_win = lib_track_win;
lib_tree_win->changed = 1;
lib_track_win->changed = 1;
}
} else if (lib_cur_win == lib_track_win) {
lib_cur_win = lib_tree_win;
lib_tree_win->changed = 1;
lib_track_win->changed = 1;
}
}
void tree_toggle_expand_artist(void)
{
struct track_iter sel;
struct artist *artist;
struct album *album;
struct tree_track *track;
tree_win_get_selected(&artist, &album);
if (album != NULL && show_all_tracks) {
window_get_sel(lib_track_win, (struct iter *)&sel);
track = sel.track;
tree_set_expand_artist(artist, !artist->expanded);
tree_track_to_track_iter(track, &sel);
window_set_sel(lib_track_win, (struct iter *)&sel);
} else if (artist != NULL) {
tree_set_expand_artist(artist, !artist->expanded);
}
}
void tree_expand_matching(const char *text)
{
struct artist *artist;
struct rb_node *tmp1;
int have_track_selected = 0;
rb_for_each_entry(artist, tmp1, &lib_artist_root, tree_node) {
struct album *album = NULL;
struct rb_node *tmp2;
int album_matched = 0;
rb_for_each_entry(album, tmp2, &artist->album_root, tree_node) {
struct tree_track *tree_track = to_tree_track(rb_first(&album->track_root));
struct track_info *ti = ((struct simple_track *) tree_track)->info;
album_matched = track_info_matches_full(ti, text, TI_MATCH_ALBUM, TI_MATCH_ARTIST | TI_MATCH_ALBUMARTIST, 0);
if (album_matched)
break;
}
artist->expanded = album_matched;
if (!have_track_selected) {
struct tree_track *tree_track;
int track_matched = 0;
if (!album)
album = to_album(rb_first(&artist->album_root));
rb_for_each_entry(tree_track, tmp2, &album->track_root, tree_node) {
struct track_info *ti = ((struct simple_track *) tree_track)->info;
track_matched = track_info_matches_full(ti, text, TI_MATCH_TITLE, 0, 0);
if (track_matched)
break;
}
if (album_matched || track_matched) {
if (!tree_track)
tree_track = to_tree_track(rb_first(&album->track_root));
tree_sel_track(tree_track, auto_expand_albums_search);
have_track_selected = 1;
}
}
}
window_changed(lib_tree_win);
}
void tree_expand_all(void)
{
struct artist *artist;
struct rb_node *tmp;
rb_for_each_entry(artist, tmp, &lib_artist_root, tree_node) {
artist->expanded = 1;
}
window_changed(lib_tree_win);
}
static void remove_track(struct tree_track *track)
{
if (track_visible(track)) {
struct track_iter iter;
tree_track_to_track_iter(track, &iter);
window_row_vanishes(lib_track_win, (struct iter *)&iter);
}
rb_erase(&track->tree_node, &track->album->track_root);
}
void tree_remove(struct tree_track *track,
void (*remove_album_cb)(struct album *))
{
struct album *album = track->album;
struct artist *sel_artist;
struct album *sel_album;
tree_win_get_selected(&sel_artist, &sel_album);
remove_track(track);
/* don't free the track */
if (rb_root_empty(&album->track_root)) {
struct artist *artist = album->artist;
if (sel_album == album)
lib_cur_win = lib_tree_win;
if (track_visible(track) && !tree_album_selected()) {
struct iter iter;
album_to_track_iter(album, (struct track_iter*)&iter);
window_row_vanishes(lib_track_win, &iter);
}
remove_album(album);
remove_album_cb(album);
album_free(album);
if (rb_root_empty(&artist->album_root)) {
artist->expanded = 0;
remove_artist(artist);
artist_free(artist);
}
}
}
void tree_remove_sel(void)
{
if (lib_cur_win == lib_tree_win) {
tree_win_remove_sel();
} else {
track_win_remove_sel();
}
}
void tree_sort_artists(void (*add_album_cb)(struct album *),
void (*remove_album_cb)(struct album *))
{
struct rb_node *a_node, *a_tmp;
rb_for_each_safe(a_node, a_tmp, &lib_artist_root) {
struct rb_node *l_node, *l_tmp;
struct artist *artist = to_artist(a_node);
rb_for_each_safe(l_node, l_tmp, &artist->album_root) {
struct rb_node *t_node, *t_tmp;
struct album *album = to_album(l_node);
remove_album(album);
add_album(album);
rb_for_each_safe(t_node, t_tmp, &album->track_root) {
struct tree_track *track = to_tree_track(t_node);
tree_remove(track, remove_album_cb);
tree_add_track(track, add_album_cb);
}
}
}
}
void tree_sel_current(int auto_expand_albums)
{
tree_sel_track(lib_cur_track, auto_expand_albums);
}
void tree_sel_first(void)
{
if (!rb_root_empty(&lib_artist_root)) {
struct artist *artist = to_artist(rb_first(&lib_artist_root));
struct album *album = to_album(rb_first(&artist->album_root));
struct tree_track *tree_track = to_tree_track(rb_first(&album->track_root));
tree_sel_track(tree_track, auto_expand_albums_search);
}
}
void tree_sel_track(struct tree_track *t, int auto_expand_albums)
{
if (t) {
struct iter iter;
if (auto_expand_albums)
t->album->artist->expanded = 1;
if (t->album->artist->expanded)
album_to_iter(t->album, &iter);
else
artist_to_iter(t->album->artist, &iter);
window_set_sel(lib_tree_win, &iter);
tree_track_to_track_iter(t, (struct track_iter *)&iter);
window_set_sel(lib_track_win, &iter);
if (lib_cur_win == lib_tree_win) {
lib_cur_win = lib_track_win;
lib_tree_win->changed = 1;
lib_track_win->changed = 1;
}
}
}
static int album_for_each_track(struct album *album, int (*cb)(void *data, struct track_info *ti),
void *data, int reverse)
{
struct tree_track *track;
struct rb_node *tmp;
int rc = 0;
if (reverse) {
rb_for_each_entry_reverse(track, tmp, &album->track_root, tree_node) {
rc = cb(data, tree_track_info(track));
if (rc)
break;
}
} else {
rb_for_each_entry(track, tmp, &album->track_root, tree_node) {
rc = cb(data, tree_track_info(track));
if (rc)
break;
}
}
return rc;
}
static int artist_for_each_track(struct artist *artist, int (*cb)(void *data, struct track_info *ti),
void *data, int reverse)
{
struct album *album;
struct rb_node *tmp;
int rc = 0;
if (reverse) {
rb_for_each_entry_reverse(album, tmp, &artist->album_root, tree_node) {
rc = album_for_each_track(album, cb, data, 1);
if (rc)
break;
}
} else {
rb_for_each_entry(album, tmp, &artist->album_root, tree_node) {
rc = album_for_each_track(album, cb, data, 0);
if (rc)
break;
}
}
return rc;
}
int _tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
{
int rc = 0;
if (lib_cur_win == lib_tree_win) {
struct artist *artist;
struct album *album;
tree_win_get_selected(&artist, &album);
if (artist) {
if (album == NULL) {
rc = artist_for_each_track(artist, cb, data, reverse);
} else {
rc = album_for_each_track(album, cb, data, reverse);
}
}
} else {
struct iter sel;
struct tree_track *track;
if (window_get_sel(lib_track_win, &sel)) {
track = iter_to_tree_track(&sel);
rc = cb(data, tree_track_info(track));
}
}
return rc;
}
int tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse, int advance)
{
int rc = _tree_for_each_sel(cb, data, reverse);
if (advance)
window_down(lib_cur_win, 1);
return rc;
}