449 lines
11 KiB
C
449 lines
11 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 "track.h"
|
|
#include "lib.h"
|
|
#include "iter.h"
|
|
#include "search_mode.h"
|
|
#include "window.h"
|
|
#include "options.h"
|
|
#include "uchar.h"
|
|
#include "xmalloc.h"
|
|
#include "debug.h"
|
|
#include "misc.h"
|
|
|
|
#include <string.h>
|
|
|
|
void simple_track_init(struct simple_track *track, struct track_info *ti)
|
|
{
|
|
track->info = ti;
|
|
track->marked = 0;
|
|
RB_CLEAR_NODE(&track->tree_node);
|
|
}
|
|
|
|
struct simple_track *simple_track_new(struct track_info *ti)
|
|
{
|
|
struct simple_track *t = xnew(struct simple_track, 1);
|
|
|
|
track_info_ref(ti);
|
|
simple_track_init(t, ti);
|
|
return t;
|
|
}
|
|
|
|
GENERIC_ITER_PREV(simple_track_get_prev, struct simple_track, node)
|
|
GENERIC_ITER_NEXT(simple_track_get_next, struct simple_track, node)
|
|
|
|
int simple_track_search_get_current(void *data, struct iter *iter, enum search_direction dir)
|
|
{
|
|
return window_get_sel(data, iter);
|
|
}
|
|
|
|
int _simple_track_search_matches(struct iter *iter, const char *text)
|
|
{
|
|
unsigned int flags = TI_MATCH_TITLE;
|
|
struct simple_track *track = iter_to_simple_track(iter);
|
|
|
|
if (!search_restricted)
|
|
flags |= TI_MATCH_ARTIST | TI_MATCH_ALBUM | TI_MATCH_ALBUMARTIST;
|
|
|
|
return track_info_matches(track->info, text, flags);
|
|
}
|
|
|
|
int simple_track_search_matches(void *data, struct iter *iter, const char *text)
|
|
{
|
|
int rc = _simple_track_search_matches(iter, text);
|
|
if (rc)
|
|
window_set_sel(data, iter);
|
|
return rc;
|
|
}
|
|
|
|
void shuffle_insert(struct rb_root *root, struct shuffle_info *previous, struct shuffle_info *next)
|
|
{
|
|
BUG_ON(root == NULL);
|
|
BUG_ON(next == NULL);
|
|
|
|
if (previous == next)
|
|
return;
|
|
rb_erase(&next->tree_node, root);
|
|
|
|
struct rb_node *parent = previous ? &previous->tree_node : NULL;
|
|
struct rb_node **new = parent ? &parent->rb_right : &root->rb_node;
|
|
while (*new) {
|
|
parent = *new;
|
|
new = &(*new)->rb_left;
|
|
}
|
|
|
|
rb_link_node(&next->tree_node, parent, new);
|
|
rb_insert_color(&next->tree_node, root);
|
|
}
|
|
|
|
struct shuffle_info *shuffle_list_get_next(struct rb_root *root, struct shuffle_info *cur,
|
|
int (*filter_callback)(const struct album *))
|
|
{
|
|
struct rb_node *node;
|
|
|
|
if (!cur) {
|
|
if (auto_reshuffle)
|
|
shuffle_list_reshuffle(root);
|
|
return tree_node_to_shuffle_info(rb_first(root));
|
|
}
|
|
|
|
node = rb_next(&cur->tree_node);
|
|
again:
|
|
while (node) {
|
|
struct shuffle_info *track = tree_node_to_shuffle_info(node);
|
|
|
|
if (filter_callback == NULL || filter_callback(track->album))
|
|
return track;
|
|
node = rb_next(node);
|
|
}
|
|
if (repeat) {
|
|
if (auto_reshuffle)
|
|
shuffle_list_reshuffle(root);
|
|
node = rb_first(root);
|
|
goto again;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct shuffle_info *shuffle_list_get_prev(struct rb_root *root, struct shuffle_info *cur,
|
|
int (*filter_callback)(const struct album *))
|
|
{
|
|
struct rb_node *node;
|
|
|
|
if (!cur) {
|
|
if (auto_reshuffle)
|
|
shuffle_list_reshuffle(root);
|
|
return tree_node_to_shuffle_info(rb_last(root));
|
|
}
|
|
|
|
node = rb_prev(&cur->tree_node);
|
|
again:
|
|
while (node) {
|
|
struct shuffle_info *track = tree_node_to_shuffle_info(node);
|
|
|
|
if (filter_callback == NULL || filter_callback(track->album))
|
|
return track;
|
|
node = rb_prev(node);
|
|
}
|
|
if (repeat) {
|
|
if (auto_reshuffle)
|
|
shuffle_list_reshuffle(root);
|
|
node = rb_last(root);
|
|
goto again;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct simple_track *simple_list_get_next(struct list_head *head, struct simple_track *cur,
|
|
int (*filter_callback)(const struct album *), bool allow_repeat)
|
|
{
|
|
struct list_head *item;
|
|
|
|
if (cur == NULL) {
|
|
if (!allow_repeat)
|
|
return NULL;
|
|
return to_simple_track(head->next);
|
|
}
|
|
|
|
item = cur->node.next;
|
|
again:
|
|
while (item != head) {
|
|
struct simple_track *track = to_simple_track(item);
|
|
|
|
if (filter_callback == NULL || filter_callback(((struct tree_track *)track)->album))
|
|
return track;
|
|
item = item->next;
|
|
}
|
|
item = head->next;
|
|
if (allow_repeat && repeat)
|
|
goto again;
|
|
return NULL;
|
|
}
|
|
|
|
struct simple_track *simple_list_get_prev(struct list_head *head, struct simple_track *cur,
|
|
int (*filter_callback)(const struct album *), bool allow_repeat)
|
|
{
|
|
struct list_head *item;
|
|
|
|
if (cur == NULL) {
|
|
if (!allow_repeat)
|
|
return NULL;
|
|
return to_simple_track(head->prev);
|
|
}
|
|
|
|
item = cur->node.prev;
|
|
again:
|
|
while (item != head) {
|
|
struct simple_track *track = to_simple_track(item);
|
|
|
|
if (filter_callback == NULL || filter_callback(((struct tree_track *)track)->album))
|
|
return track;
|
|
item = item->prev;
|
|
}
|
|
item = head->prev;
|
|
if (allow_repeat && repeat)
|
|
goto again;
|
|
return NULL;
|
|
}
|
|
|
|
void sorted_list_add_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track,
|
|
const sort_key_t *keys, int tiebreak)
|
|
{
|
|
struct rb_node **new = &(tree_root->rb_node), *parent = NULL, *curr, *next;
|
|
struct list_head *node;
|
|
int result = 0;
|
|
|
|
/* try to locate track in tree */
|
|
while (*new) {
|
|
const struct simple_track *t = tree_node_to_simple_track(*new);
|
|
result = track_info_cmp(track->info, t->info, keys);
|
|
|
|
parent = *new;
|
|
if (result < 0)
|
|
new = &(parent->rb_left);
|
|
else if (result > 0)
|
|
new = &(parent->rb_right);
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* duplicate is present in the tree */
|
|
if (parent && result == 0) {
|
|
if (tiebreak < 0) {
|
|
node = &(tree_node_to_simple_track(parent)->node);
|
|
rb_replace_node(parent, &track->tree_node, tree_root);
|
|
RB_CLEAR_NODE(parent);
|
|
} else {
|
|
next = rb_next(parent);
|
|
node = next ? &(tree_node_to_simple_track(next)->node) : head;
|
|
}
|
|
} else {
|
|
rb_link_node(&track->tree_node, parent, new);
|
|
curr = *new;
|
|
rb_insert_color(&track->tree_node, tree_root);
|
|
if (result < 0) {
|
|
node = &(tree_node_to_simple_track(parent)->node);
|
|
} else if (result > 0) {
|
|
next = rb_next(curr);
|
|
node = next ? &(tree_node_to_simple_track(next)->node) : head;
|
|
} else {
|
|
/* rbtree was empty, just add after list head */
|
|
node = head;
|
|
}
|
|
}
|
|
list_add(&track->node, node->prev);
|
|
}
|
|
|
|
void sorted_list_remove_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track)
|
|
{
|
|
struct simple_track *next_track;
|
|
struct rb_node *tree_next;
|
|
|
|
if (!RB_EMPTY_NODE(&track->tree_node)) {
|
|
next_track = (track->node.next != head) ? to_simple_track(track->node.next) : NULL;
|
|
tree_next = rb_next(&track->tree_node);
|
|
|
|
if (next_track && (!tree_next || tree_node_to_simple_track(tree_next) != next_track)) {
|
|
rb_replace_node(&track->tree_node, &next_track->tree_node, tree_root);
|
|
RB_CLEAR_NODE(&track->tree_node);
|
|
} else
|
|
rb_erase(&track->tree_node, tree_root);
|
|
}
|
|
list_del(&track->node);
|
|
}
|
|
|
|
void rand_list_rebuild(struct list_head *head, struct rb_root *tree_root)
|
|
{
|
|
struct list_head *item, *tmp;
|
|
struct rb_root tmp_tree = RB_ROOT;
|
|
struct simple_track **track_array;
|
|
static const sort_key_t empty_sort_keys[] = { SORT_INVALID };
|
|
|
|
unsigned int len = 0, track_cnt = 0;
|
|
|
|
list_for_each(item, head) {
|
|
len++;
|
|
}
|
|
track_array = xmalloc(len * sizeof(track_array[0]));
|
|
|
|
LIST_HEAD(tmp_head);
|
|
list_for_each_safe(item, tmp, head) {
|
|
struct simple_track *track = to_simple_track(item);
|
|
sorted_list_remove_track(head, tree_root, track);
|
|
track_array[track_cnt] = track;
|
|
track_cnt++;
|
|
}
|
|
shuffle_array(track_array, len, sizeof(track_array[0]));
|
|
for (unsigned int i=0; i<len; i++) {
|
|
sorted_list_add_track(&tmp_head, &tmp_tree, track_array[i], empty_sort_keys, 0);
|
|
}
|
|
free(track_array);
|
|
|
|
tree_root->rb_node = tmp_tree.rb_node;
|
|
_list_add(head, tmp_head.prev, tmp_head.next);
|
|
}
|
|
|
|
void sorted_list_rebuild(struct list_head *head, struct rb_root *tree_root, const sort_key_t *keys)
|
|
{
|
|
struct list_head *item, *tmp;
|
|
struct rb_root tmp_tree = RB_ROOT;
|
|
LIST_HEAD(tmp_head);
|
|
|
|
list_for_each_safe(item, tmp, head) {
|
|
struct simple_track *track = to_simple_track(item);
|
|
sorted_list_remove_track(head, tree_root, track);
|
|
sorted_list_add_track(&tmp_head, &tmp_tree, track, keys, 0);
|
|
}
|
|
tree_root->rb_node = tmp_tree.rb_node;
|
|
_list_add(head, tmp_head.prev, tmp_head.next);
|
|
}
|
|
|
|
static int compare_rand(const struct rb_node *a, const struct rb_node *b)
|
|
{
|
|
struct shuffle_info *tr_a = tree_node_to_shuffle_info(a);
|
|
struct shuffle_info *tr_b = tree_node_to_shuffle_info(b);
|
|
|
|
if (tr_a->rand < tr_b->rand)
|
|
return -1;
|
|
if (tr_a->rand > tr_b->rand)
|
|
return +1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shuffle_info_init(struct shuffle_info *info, struct album *album)
|
|
{
|
|
info->rand = rand() / ((double) RAND_MAX + 1);
|
|
info->album = album;
|
|
}
|
|
|
|
void shuffle_list_add(struct shuffle_info *track, struct rb_root *tree_root, struct album *album)
|
|
{
|
|
struct rb_node **new = &(tree_root->rb_node), *parent = NULL;
|
|
|
|
shuffle_info_init(track, album);
|
|
|
|
/* try to locate track in tree */
|
|
while (*new) {
|
|
int result = compare_rand(&track->tree_node, *new);
|
|
|
|
parent = *new;
|
|
if (result < 0)
|
|
new = &((*new)->rb_left);
|
|
else if (result > 0)
|
|
new = &((*new)->rb_right);
|
|
else {
|
|
/* very unlikely, try again! */
|
|
shuffle_list_add(track, tree_root, album);
|
|
return;
|
|
}
|
|
}
|
|
|
|
rb_link_node(&track->tree_node, parent, new);
|
|
rb_insert_color(&track->tree_node, tree_root);
|
|
}
|
|
|
|
void shuffle_list_reshuffle(struct rb_root *tree_root)
|
|
{
|
|
struct rb_node *node, *tmp;
|
|
struct rb_root tmptree = RB_ROOT;
|
|
|
|
rb_for_each_safe(node, tmp, tree_root) {
|
|
struct shuffle_info *track = tree_node_to_shuffle_info(node);
|
|
struct album *album = track->album;
|
|
rb_erase(node, tree_root);
|
|
shuffle_list_add(track, &tmptree, album);
|
|
}
|
|
|
|
tree_root->rb_node = tmptree.rb_node;
|
|
}
|
|
|
|
/* expensive */
|
|
void list_add_rand(struct list_head *head, struct list_head *node, int nr)
|
|
{
|
|
struct list_head *item;
|
|
int pos;
|
|
|
|
pos = rand() % (nr + 1);
|
|
item = head;
|
|
if (pos <= nr / 2) {
|
|
while (pos) {
|
|
item = item->next;
|
|
pos--;
|
|
}
|
|
/* add after item */
|
|
list_add(node, item);
|
|
} else {
|
|
pos = nr - pos;
|
|
while (pos) {
|
|
item = item->prev;
|
|
pos--;
|
|
}
|
|
/* add before item */
|
|
list_add_tail(node, item);
|
|
}
|
|
}
|
|
|
|
int simple_list_for_each_marked(struct list_head *head, track_info_cb cb,
|
|
void *data, int reverse)
|
|
{
|
|
struct simple_track *t;
|
|
int rc = 0;
|
|
|
|
if (reverse) {
|
|
list_for_each_entry_reverse(t, head, node) {
|
|
if (t->marked) {
|
|
rc = cb(data, t->info);
|
|
if (rc)
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
list_for_each_entry(t, head, node) {
|
|
if (t->marked) {
|
|
rc = cb(data, t->info);
|
|
if (rc)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int simple_list_for_each(struct list_head *head, track_info_cb cb, void *data,
|
|
int reverse)
|
|
{
|
|
struct simple_track *t;
|
|
int rc = 0;
|
|
|
|
if (reverse) {
|
|
list_for_each_entry_reverse(t, head, node) {
|
|
if ((rc = cb(data, t->info)))
|
|
break;
|
|
}
|
|
} else {
|
|
list_for_each_entry(t, head, node) {
|
|
if ((rc = cb(data, t->info)))
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|