ocarina/core/playlists/system.c
Anna Schumaker b17585237a core/playlist: Add a playlist_played() function
This is used to notify when tracks have been played so dynamic playlists
can be updated, and so the model can display the correct playcount.

The old system playlist tests are mostly unnecessary at this point, so I
remove them as part of this patch.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00

555 lines
14 KiB
C

/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/system.h>
#include <core/string.h>
static struct playlist *pl_system_lookup(const gchar *);
static struct playlist *pl_system_get(unsigned int);
static void pl_system_update(const gchar *);
static void __sys_pl_save();
static bool __sys_pl_load();
static struct file sys_file = FILE_INIT("playlist.db", 0);
static struct file sys_deck_f = FILE_INIT("deck", 1);
static struct file sys_collection_f = FILE_INIT("library.q", 0);
static struct file sys_pl_file = FILE_INIT("playlist.system", 0);
/*
* Generic system playlist operations.
*/
static bool sys_pl_delete_clear(struct playlist *playlist)
{
playlist_generic_clear(playlist);
__sys_pl_save();
return false;
}
static void sys_pl_generic_init(struct playlist *playlist, unsigned int flags,
struct queue_ops *ops)
{
playlist_generic_init(playlist, Q_REPEAT, ops);
}
static void sys_pl_update_init(struct playlist *playlist, unsigned int flags,
struct queue_ops *ops)
{
sys_pl_generic_init(playlist, flags, ops);
pl_system_update(playlist->pl_name);
}
static void sys_pl_save_partial(struct playlist *playlist, struct file *file)
{
file_writef(file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, file, true);
}
static void sys_pl_save_full(struct playlist *playlist, struct file *file)
{
sys_pl_save_partial(playlist, file);
queue_save_tracks(&playlist->pl_queue, file);
}
static void sys_pl_load_partial(struct playlist *playlist, struct file *file)
{
queue_load_flags(&playlist->pl_queue, file, true);
queue_set_flag(&playlist->pl_queue, Q_REPEAT);
}
static void sys_pl_load_full(struct playlist *playlist, struct file *file)
{
sys_pl_load_partial(playlist, file);
queue_load_tracks(&playlist->pl_queue, file);
}
static bool sys_pl_generic_add_front(struct playlist *playlist,
struct track *track)
{
if (queue_has(&pl_system_get(SYS_PL_HIDDEN)->pl_queue, track))
return false;
return playlist_generic_add_track_front(playlist, track);
}
/*
* Favorite tracks playlist operations.
*/
static struct playlist_ops favorites_ops = {
.pl_add = playlist_generic_add_track,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_delete_clear,
.pl_next = playlist_generic_next,
.pl_remove = playlist_generic_remove_track,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_favorites = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Favorites",
SYS_PL_FAVORITES, &favorites_ops),
.spl_init = playlist_generic_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
};
/*
* Hidden tracks playlist operations.
*/
static bool sys_pl_hidden_add(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_add_track(pl_system_get(SYS_PL_HIDDEN), track);
playlist_generic_remove_track(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_remove_track(pl_system_get(SYS_PL_UNPLAYED), track);
playlist_generic_remove_track(pl_system_get(SYS_PL_MOST_PLAYED), track);
playlist_generic_remove_track(pl_system_get(SYS_PL_LEAST_PLAYED), track);
return ret;
}
static bool sys_pl_hidden_remove(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_remove_track(playlist, track);
unsigned int average = track_db_average_plays();
unsigned int add_id = SYS_PL_LEAST_PLAYED;
add_id = (track->tr_count == 0) ? SYS_PL_UNPLAYED : add_id;
add_id = (track->tr_count > average) ? SYS_PL_MOST_PLAYED : add_id;
playlist_generic_add_track(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_add_track(pl_system_get(add_id), track);
return ret;
}
static bool sys_pl_hidden_clear(struct playlist *playlist)
{
struct track *track;
while (queue_size(&playlist->pl_queue) > 0) {
track = queue_at(&playlist->pl_queue, 0);
sys_pl_hidden_remove(playlist, track);
}
__sys_pl_save();
return false;
}
static struct playlist_ops hidden_ops = {
.pl_add = sys_pl_hidden_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_hidden_clear,
.pl_next = playlist_generic_next,
.pl_remove = sys_pl_hidden_remove,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_hidden = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Hidden",
SYS_PL_HIDDEN, &hidden_ops),
.spl_init = playlist_generic_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
};
/*
* Queued tracks playlist operations.
*/
static bool sys_pl_queued_load()
{
struct queue *queue = &pl_system_get(SYS_PL_QUEUED)->pl_queue;
unsigned int num, i, flags = 0;
if (!file_open(&sys_deck_f, OPEN_READ))
return true;
file_readf(&sys_deck_f, "%u", &num);
for (i = 0; i < num; i++) {
file_readf(&sys_deck_f, "%u", &flags);
flags &= Q_UNUSED_MASK;
if (i == 0)
queue->q_flags |= flags;
queue_load_tracks(queue, &sys_deck_f);
}
file_close(&sys_deck_f);
file_remove(&sys_deck_f);
return true;
}
static void sys_pl_queued_init(struct playlist *playlist,
unsigned int flags, struct queue_ops *ops)
{
queue_init(&playlist->pl_queue, Q_REPEAT, ops, playlist);
}
static struct track *sys_pl_queued_next(struct playlist *playlist)
{
struct track *track = playlist_generic_next(playlist);
unsigned int pos = playlist->pl_queue.q_cur.it_pos;
queue_iter_prev(&playlist->pl_queue.q_cur);
queue_remove(&playlist->pl_queue, pos);
return track;
}
static struct playlist_ops queued_ops = {
.pl_add = playlist_generic_add_track,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_delete_clear,
.pl_next = sys_pl_queued_next,
.pl_remove = playlist_generic_remove_track,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_queued = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Queued Tracks",
SYS_PL_QUEUED, &queued_ops),
.spl_init = sys_pl_queued_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
};
/*
* Collection playlist operations.
*/
static bool sys_pl_collection_load()
{
struct playlist *playlist = pl_system_get(SYS_PL_COLLECTION);
if (file_open(&sys_collection_f, OPEN_READ)) {
queue_load_flags(&playlist->pl_queue, &sys_collection_f, false);
file_close(&sys_collection_f);
file_remove(&sys_collection_f);
}
return true;
}
static bool sys_pl_collection_update(struct playlist *playlist,
struct track *track)
{
return sys_pl_generic_add_front(playlist, track) || true;
}
static struct playlist_ops collection_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_next = playlist_generic_next,
.pl_remove = sys_pl_hidden_add,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_collection = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Collection",
SYS_PL_COLLECTION, &collection_ops),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_update = sys_pl_collection_update,
};
/*
* History playlist operations.
*/
static void sys_pl_history_init(struct playlist *playlist,
unsigned int flags, struct queue_ops *ops)
{
queue_init(&playlist->pl_queue, Q_REPEAT, ops, playlist);
}
static bool sys_pl_history_add(struct playlist *playlist, struct track *track)
{
queue_add_front(&playlist->pl_queue, track);
queue_iter_set(&playlist->pl_queue, &playlist->pl_queue.q_cur, 0);
return true;
}
static struct playlist_ops history_ops = {
.pl_add = sys_pl_history_add,
.pl_next = playlist_generic_next,
};
static struct sys_playlist sys_history = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "History",
SYS_PL_HISTORY, &history_ops),
.spl_init = sys_pl_history_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
};
/*
* Unplayed tracks playlist operations.
*/
static bool sys_pl_unplayed_update(struct playlist *playlist,
struct track *track)
{
if (track->tr_count > 0)
return false;
return sys_pl_generic_add_front(playlist, track) || true;
}
static struct playlist_ops unplayed_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_next = playlist_generic_next,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_unplayed = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Unplayed",
SYS_PL_UNPLAYED, &unplayed_ops),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_update = sys_pl_unplayed_update,
};
/*
* Most played tracks playlist operations.
*/
static bool sys_pl_most_played_update(struct playlist *playlist,
struct track *track)
{
unsigned int average = track_db_average_plays();
if (track->tr_count <= average)
return false;
return sys_pl_generic_add_front(playlist, track) || true;
}
static struct playlist_ops most_played_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_next = playlist_generic_next,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_most_played = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Most Played",
SYS_PL_MOST_PLAYED, &most_played_ops),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_update = sys_pl_most_played_update,
};
/*
* Least played tracks playlist operations.
*/
static bool sys_pl_least_played_update(struct playlist *playlist,
struct track *track)
{
unsigned int average = track_db_average_plays();
if (!track->tr_count || track->tr_count > average)
return false;
return sys_pl_generic_add_front(playlist, track) || true;
}
static struct playlist_ops least_played_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_next = playlist_generic_next,
.pl_set_flag = playlist_generic_set_flag,
.pl_sort = playlist_generic_sort,
};
static struct sys_playlist sys_least_played = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Least Played",
SYS_PL_LEAST_PLAYED, &least_played_ops),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_update = sys_pl_least_played_update,
};
static struct sys_playlist *sys_playlists[SYS_PL_NUM_PLAYLISTS] = {
[SYS_PL_FAVORITES] = &sys_favorites,
[SYS_PL_HIDDEN] = &sys_hidden,
[SYS_PL_QUEUED] = &sys_queued,
[SYS_PL_COLLECTION] = &sys_collection,
[SYS_PL_HISTORY] = &sys_history,
[SYS_PL_UNPLAYED] = &sys_unplayed,
[SYS_PL_MOST_PLAYED] = &sys_most_played,
[SYS_PL_LEAST_PLAYED] = &sys_least_played,
};
static struct sys_playlist * __sys_pl_lookup(const gchar *name)
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
if (string_match(name, sys_playlists[i]->spl_playlist.pl_name))
return sys_playlists[i];
}
return NULL;
}
static void __sys_pl_save()
{
struct sys_playlist *sys_pl;
unsigned int i;
if (!file_open(&sys_pl_file, OPEN_WRITE))
return;
file_writef(&sys_pl_file, "%u\n", SYS_PL_NUM_PLAYLISTS);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_save(&sys_pl->spl_playlist, &sys_pl_file);
}
file_close(&sys_pl_file);
}
static bool __sys_pl_update_save()
{
__sys_pl_save();
return true;
}
static bool __sys_pl_load_new()
{
struct sys_playlist *sys_pl;
unsigned int i, n;
gchar *name;
if (!file_open(&sys_pl_file, OPEN_READ)) {
__sys_pl_load();
sys_pl_collection_load();
sys_pl_queued_load();
__sys_pl_update_save();
return true;
}
file_readf(&sys_pl_file, "%u\n", &n);
for (i = 0; i < n; i++) {
name = file_readl(&sys_pl_file);
sys_pl = __sys_pl_lookup(name);
if (sys_pl)
sys_pl->spl_load(&sys_pl->spl_playlist, &sys_pl_file);
g_free(name);
}
file_close(&sys_pl_file);
return true;
}
static bool __sys_pl_load()
{
struct sys_playlist *plist;
unsigned int i, n;
gchar *name;
if (!file_open(&sys_file, OPEN_READ))
return true;
file_readf(&sys_file, "%u\n", &n);
for (i = 0; i < n; i++) {
file_readf(&sys_file, "%*u %m[^\n]\n", &name);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
plist = __sys_pl_lookup(name);
if (plist)
queue_load_tracks(&plist->spl_playlist.pl_queue, &sys_file);
g_free(name);
}
file_close(&sys_file);
file_remove(&sys_file);
return true;
}
static void pl_system_save(void)
{
__sys_pl_save();
}
static struct playlist *pl_system_lookup(const gchar *name)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
return sys_pl ? &sys_pl->spl_playlist : NULL;
}
static struct playlist *pl_system_get(unsigned int id)
{
return (id < SYS_PL_NUM_PLAYLISTS) ? &sys_playlists[id]->spl_playlist : NULL;
}
static void pl_system_played(struct track *track)
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
queue_updated(&pl_system_get(i)->pl_queue, track);
pl_system_update("Unplayed");
pl_system_update("Most Played");
pl_system_update("Least Played");
}
static void pl_system_update(const gchar *name)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
if (sys_pl)
playlist_generic_update(&sys_pl->spl_playlist, sys_pl->spl_update);
}
struct playlist_type pl_system = {
.pl_save = pl_system_save,
.pl_lookup = pl_system_lookup,
.pl_get = pl_system_get,
.pl_played = pl_system_played,
.pl_update = pl_system_update,
};
void pl_system_init(struct queue_ops *ops)
{
struct sys_playlist *sys_pl;
unsigned int i;
idle_schedule(IDLE_SYNC, __sys_pl_load_new, NULL);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_init(&sys_pl->spl_playlist, Q_REPEAT, ops);
}
}
void pl_system_deinit()
{
for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
queue_deinit(&pl_system_get(i)->pl_queue);
}
void pl_system_new_track(struct track *track)
{
playlist_generic_add_track(pl_system_lookup("Collection"), track);
playlist_generic_add_track(pl_system_lookup("Unplayed"), track);
}
void pl_system_delete_track(struct track *track)
{
struct sys_playlist *sys_pl;
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
playlist_generic_remove_track(&sys_pl->spl_playlist, track);
}
}