core/playlists/user: Add support for user created playlists

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2016-08-14 10:20:15 -04:00
parent d6e5e6c773
commit dcbf2dff72
9 changed files with 331 additions and 8 deletions

8
TODO
View File

@ -4,14 +4,6 @@ Future work:
to the wild. This section will be a list of features that I want, but to the wild. This section will be a list of features that I want, but
should be deferred to a future release so basic support can be coded. should be deferred to a future release so basic support can be coded.
- User created song groups:
Basic add and remove features can be implemented using the
Library and Banned Songs groups. This will give me a chance
to test saving groups on a small scale before letting users
create anything they want.
- Save a user's playlist as a group:
- Fix track durations: - Fix track durations:
Some tracks in my library are tagged with the wrong duration, Some tracks in my library are tagged with the wrong duration,
so fix them as they are played. so fix them as they are played.

View File

@ -11,6 +11,7 @@ struct playlist_type *playlist_types[] = {
[PL_SYSTEM] = &pl_system, [PL_SYSTEM] = &pl_system,
[PL_ARTIST] = &pl_artist, [PL_ARTIST] = &pl_artist,
[PL_LIBRARY] = &pl_library, [PL_LIBRARY] = &pl_library,
[PL_USER] = &pl_user,
}; };
@ -18,6 +19,7 @@ void playlist_init(struct queue_ops *ops)
{ {
pl_system_init(ops); pl_system_init(ops);
pl_artist_init(ops); pl_artist_init(ops);
pl_user_init(ops);
pl_library_init(ops); pl_library_init(ops);
if (!settings_has(SETTINGS_CUR_TYPE) || if (!settings_has(SETTINGS_CUR_TYPE) ||
@ -29,6 +31,7 @@ void playlist_deinit()
{ {
pl_system_deinit(); pl_system_deinit();
pl_artist_deinit(); pl_artist_deinit();
pl_user_deinit();
pl_library_deinit(); pl_library_deinit();
} }

210
core/playlists/user.c Normal file
View File

@ -0,0 +1,210 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/playlists/user.h>
static struct queue_ops *user_pl_ops = NULL;
static struct database user_db;
static struct user_playlist *__user_db_alloc(gchar *name)
{
struct user_playlist *playlist = g_malloc(sizeof(struct user_playlist));
dbe_init(&playlist->pl_dbe, playlist);
playlist->pl_playlist.pl_name = name;
playlist->pl_playlist.pl_type = PL_USER;
playlist_generic_init(&playlist->pl_playlist, Q_REPEAT, user_pl_ops);
return playlist;
}
static struct db_entry *user_db_alloc(const gchar *name)
{
return &__user_db_alloc(g_strdup(name))->pl_dbe;
}
static void user_db_free(struct db_entry *dbe)
{
queue_deinit(&USER_PLAYLIST(dbe)->pl_playlist.pl_queue);
g_free(USER_PLAYLIST(dbe));
}
static gchar *user_db_key(struct db_entry *dbe)
{
return g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name);
}
static struct db_entry *user_db_read(struct file *file)
{
gchar *name = file_readl(file);
struct user_playlist *playlist = __user_db_alloc(name);
queue_load_flags(&playlist->pl_playlist.pl_queue, file, true);
queue_load_tracks(&playlist->pl_playlist.pl_queue, file);
queue_iter_set(&playlist->pl_playlist.pl_queue,
&playlist->pl_playlist.pl_queue.q_cur,
playlist->pl_playlist.pl_queue.q_cur.it_pos);
return &playlist->pl_dbe;
}
static void user_db_write(struct file *file, struct db_entry *dbe)
{
struct playlist *playlist = &USER_PLAYLIST(dbe)->pl_playlist;
file_writef(file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, file, true);
queue_save_tracks(&playlist->pl_queue, file);
}
static const struct db_ops user_db_ops = {
.dbe_alloc = user_db_alloc,
.dbe_free = user_db_free,
.dbe_key = user_db_key,
.dbe_read = user_db_read,
.dbe_write = user_db_write,
};
static struct playlist *__user_pl_lookup(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
static void pl_user_save(void)
{
db_save(&user_db);
}
static struct queue *pl_user_get_queue(const gchar *name)
{
struct playlist *playlist = __user_pl_lookup(name);
return playlist ? &playlist->pl_queue : NULL;
}
static unsigned int pl_user_get_id(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
return dbe ? dbe->dbe_index : -1;
}
static gchar *pl_user_get_name(unsigned int id)
{
struct db_entry *dbe = db_at(&user_db, id);
return dbe ? g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name) : NULL;
}
static bool pl_user_can_select(const gchar *name)
{
return db_get(&user_db, name) != NULL;
}
static bool pl_user_new(const gchar *name)
{
if (db_get(&user_db, name))
return false;
db_insert(&user_db, name);
return true;
}
static bool pl_user_delete(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
if (dbe) {
db_remove(&user_db, dbe);
db_defrag(&user_db);
}
return dbe != NULL;
}
static bool pl_user_add(const gchar *name, struct track *track)
{
struct playlist *playlist = __user_pl_lookup(name);
bool ret = false;
if (playlist) {
ret = playlist_generic_add_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
}
static bool pl_user_remove(const gchar *name, struct track *track)
{
struct playlist *playlist = __user_pl_lookup(name);
bool ret = false;
if (playlist) {
ret = playlist_generic_remove_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
}
static void pl_user_update(const gchar *name)
{
}
static void pl_user_set_flag(const gchar *name, enum queue_flags flag,
bool enabled)
{
struct playlist *playlist = __user_pl_lookup(name);
playlist_generic_set_flag(playlist, flag, enabled);
pl_user_save();
}
static void pl_user_sort(const gchar *name, enum compare_t sort, bool reset)
{
struct playlist *playlist = __user_pl_lookup(name);
playlist_generic_sort(playlist, sort, reset);
pl_user_save();
}
static struct track *pl_user_next(const gchar *name)
{
struct playlist *playlist = __user_pl_lookup(name);
struct track *track = playlist_generic_next(playlist);
pl_user_save();
return track;
}
struct playlist_type pl_user = {
.pl_save = pl_user_save,
.pl_get_queue = pl_user_get_queue,
.pl_get_id = pl_user_get_id,
.pl_get_name = pl_user_get_name,
.pl_can_select = pl_user_can_select,
.pl_new = pl_user_new,
.pl_delete = pl_user_delete,
.pl_add_track = pl_user_add,
.pl_remove_track = pl_user_remove,
.pl_update = pl_user_update,
.pl_set_flag = pl_user_set_flag,
.pl_sort = pl_user_sort,
.pl_next = pl_user_next,
};
void pl_user_init(struct queue_ops *ops)
{
user_pl_ops = ops;
db_init(&user_db, "playlist.user", true, &user_db_ops, 0, 0);
db_load_idle(&user_db);
}
void pl_user_deinit()
{
db_deinit(&user_db);
}
struct database *pl_user_db_get()
{
return &user_db;
}

View File

@ -11,6 +11,7 @@
#include <core/playlists/artist.h> #include <core/playlists/artist.h>
#include <core/playlists/library.h> #include <core/playlists/library.h>
#include <core/playlists/system.h> #include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <core/queue.h> #include <core/queue.h>

View File

@ -13,6 +13,7 @@ enum playlist_type_t {
PL_SYSTEM, PL_SYSTEM,
PL_ARTIST, PL_ARTIST,
PL_LIBRARY, PL_LIBRARY,
PL_USER,
PL_MAX_TYPE, PL_MAX_TYPE,
}; };

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_USER_H
#define OCARINA_CORE_PLAYLISTS_USER_H
#include <core/playlists/type.h>
struct user_playlist {
struct playlist pl_playlist;
struct db_entry pl_dbe;
};
#define USER_PLAYLIST(dbe) ((struct user_playlist *)DBE_DATA(dbe))
/* User playlist type. */
extern struct playlist_type pl_user;
/* Called to initialize user playlists. */
void pl_user_init(struct queue_ops *ops);
/* Called to deinitialize user playlists. */
void pl_user_deinit();
struct database *pl_user_db_get();
#endif /* OCARINA_CORE_PLAYLISTS_USER_H */

View File

@ -1,3 +1,4 @@
system system
artist artist
user
library library

View File

@ -6,4 +6,5 @@ endfunction()
playlist_unit_test(System) playlist_unit_test(System)
playlist_unit_test(Artist) playlist_unit_test(Artist)
playlist_unit_test(User)
playlist_unit_test(Library) playlist_unit_test(Library)

View File

@ -0,0 +1,86 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
#include <core/tags/artist.h>
#include <core/tags/library.h>
#include <core/tags/tags.h>
#include <tests/test.h>
void test_user()
{
struct database *db = pl_user_db_get();
g_assert_cmpuint(db->db_size, ==, 0);
g_assert_true( playlist_new(PL_USER, "Test Playlist"));
g_assert_false(playlist_new(PL_USER, "Test Playlist"));
g_assert_cmpuint(db->db_size, ==, 1);
g_assert_false(playlist_get_random(PL_USER, "Test Playlist"));
playlist_set_random(PL_USER, "Test Playlist", true);
g_assert_true(playlist_get_random(PL_USER, "Test Playlist"));
playlist_set_random(PL_USER, "Test Playlist", false);
g_assert_false(playlist_get_random(PL_USER, "Test Playlist"));
g_assert_cmpuint(playlist_get_id(PL_USER, "Test Playlist"), ==, 0);
g_assert_cmpuint(playlist_get_id(PL_USER, "No Playlist"), ==,
(unsigned int)-1);
g_assert_cmpstr_free(playlist_get_name(PL_USER, 0), ==, "Test Playlist");
g_assert_null(playlist_get_name(PL_USER, 1));
g_assert_true( playlist_select(PL_USER, "Test Playlist"));
g_assert_false(playlist_select(PL_USER, "No Playlist"));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 0);
g_assert_false(playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_add( PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_add( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1);
playlist_update(PL_USER, "Test Playlist");
pl_user_deinit();
g_assert_cmpuint(db->db_size, ==, 0);
pl_user_init(NULL);
while (idle_run_task()) {};
g_assert_cmpuint(db->db_size, ==, 1);
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1);
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_remove(PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_remove(PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 0);
g_assert_true( playlist_delete(PL_USER, "Test Playlist"));
g_assert_false(playlist_delete(PL_USER, "Test Playlist"));
g_assert_cmpuint(db->db_size, ==, 0);
g_assert_cmpuint(db_actual_size(db), ==, 0);
}
int main(int argc, char **argv)
{
int ret;
idle_init_sync();
settings_init();
tags_init();
playlist_init(NULL);
while (idle_run_task()) {};
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
while (idle_run_task()) {};
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Playlists/User", test_user);
ret = g_test_run();
playlist_deinit();
tags_deinit();
settings_deinit();
idle_deinit();
return ret;
}