Anna Schumaker
58cd43c330
This is useful for user playlists so they can store their playlist-id directly, letting us remove the entire playlist_get_id() function. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
355 lines
7.8 KiB
C
355 lines
7.8 KiB
C
/*
|
|
* Copyright 2014 (c) Anna Schumaker.
|
|
*/
|
|
#include <core/string.h>
|
|
#include <core/tags/track.h>
|
|
|
|
#include <glib.h>
|
|
#include <taglib/tag_c.h>
|
|
|
|
#define TRACK_DB_MIN 0 /* Ocarina 6.0 */
|
|
static struct database track_db;
|
|
static unsigned int unplayed_count = 0;
|
|
static unsigned int play_count = 0;
|
|
|
|
static gchar *__track_key(struct library *library, gchar *path)
|
|
{
|
|
gchar *res;
|
|
|
|
if (library)
|
|
res = g_strdup_printf("%u/%s", library_index(library), path);
|
|
else
|
|
res = g_strdup("");
|
|
|
|
g_free(path);
|
|
return res;
|
|
}
|
|
|
|
static gchar *__track_path(struct track *track)
|
|
{
|
|
gchar *path;
|
|
|
|
sscanf(track->tr_path, "%*u/%m[^\n]", &path);
|
|
return path;
|
|
}
|
|
|
|
static struct track *__track_alloc()
|
|
{
|
|
struct track *track = g_malloc(sizeof(struct track));
|
|
|
|
dbe_init(&track->tr_dbe, track);
|
|
return track;
|
|
}
|
|
|
|
|
|
struct db_entry *track_alloc(const gchar *key, unsigned int index)
|
|
{
|
|
const TagLib_AudioProperties *audio;
|
|
struct library *library;
|
|
struct artist *artist;
|
|
struct genre *genre;
|
|
struct track *track = NULL;
|
|
unsigned int lib_id;
|
|
TagLib_File *file;
|
|
TagLib_Tag *tag;
|
|
char *fullpath, *path;
|
|
|
|
sscanf(key, "%u/%m[^\n]", &lib_id, &path);
|
|
library = library_get(lib_id);
|
|
fullpath = library_file(library, path);
|
|
file = taglib_file_new(fullpath);
|
|
if (!file || !taglib_file_is_valid(file)) {
|
|
g_printerr("WARNING: Could not read tags for: %s\n", fullpath);
|
|
goto out;
|
|
}
|
|
|
|
track = __track_alloc();
|
|
tag = taglib_file_tag(file);
|
|
audio = taglib_file_audioproperties(file);
|
|
artist = artist_find(taglib_tag_artist(tag));
|
|
genre = genre_find(taglib_tag_genre(tag));
|
|
|
|
track->tr_album = album_find(artist, genre, taglib_tag_album(tag),
|
|
taglib_tag_year(tag));
|
|
track->tr_library = library;
|
|
|
|
unplayed_count++;
|
|
track->tr_count = 0;
|
|
track->tr_length = taglib_audioproperties_length(audio);
|
|
track->tr_track = taglib_tag_track(tag);
|
|
date_set(&track->tr_date, 0, 0, 0);
|
|
|
|
track->tr_path = g_strdup(key);
|
|
track->tr_title = g_strdup(taglib_tag_title(tag));
|
|
track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
|
|
&track->tr_alts);
|
|
|
|
taglib_tag_free_strings();
|
|
taglib_file_free(file);
|
|
out:
|
|
g_free(path);
|
|
g_free(fullpath);
|
|
return track ? &track->tr_dbe : NULL;
|
|
}
|
|
|
|
static void track_free(struct db_entry *dbe)
|
|
{
|
|
struct track *track = TRACK(dbe);
|
|
|
|
play_count -= track->tr_count;
|
|
if (track->tr_count == 0)
|
|
unplayed_count--;
|
|
|
|
g_strfreev(track->tr_tokens);
|
|
g_strfreev(track->tr_alts);
|
|
g_free(track->tr_title);
|
|
g_free(track);
|
|
}
|
|
|
|
static gchar *track_key(struct db_entry *dbe)
|
|
{
|
|
return TRACK(dbe)->tr_path;
|
|
}
|
|
|
|
static void track_read_v0(struct file *file, struct track *track)
|
|
{
|
|
unsigned int artist_id, album_id, genre_id;
|
|
struct artist *artist;
|
|
struct genre *genre;
|
|
struct album *album;
|
|
|
|
file_readf(file, "%u %u %u %hu", &artist_id, &album_id, &genre_id,
|
|
&track->tr_track);
|
|
date_read(file, &track->tr_date);
|
|
|
|
album = album_get(album_id);
|
|
artist = artist_get(artist_id);
|
|
genre = genre_get(genre_id);
|
|
|
|
if (album->al_artist != artist || album->al_genre != genre)
|
|
album = album_find(artist, genre, album->al_name, album->al_year);
|
|
|
|
track->tr_album = album;
|
|
}
|
|
|
|
static struct db_entry *track_read(struct file *file, unsigned int index)
|
|
{
|
|
struct track *track = __track_alloc();
|
|
unsigned int library_id, album_id;
|
|
|
|
file_readf(file, "%u", &library_id);
|
|
track->tr_library = library_get(library_id);
|
|
|
|
if (file_version(file) == 0)
|
|
track_read_v0(file, track);
|
|
else {
|
|
file_readf(file, "%u %hu", &album_id, &track->tr_track);
|
|
track->tr_album = album_get(album_id);
|
|
date_read_stamp(file, &track->tr_date);
|
|
}
|
|
|
|
file_readf(file, "%hu %hu", &track->tr_count, &track->tr_length);
|
|
|
|
play_count += track->tr_count;
|
|
if (track->tr_count == 0)
|
|
unplayed_count++;
|
|
|
|
track->tr_title = file_readl(file);
|
|
track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
|
|
&track->tr_alts);
|
|
track->tr_path = __track_key(track->tr_library, file_readl(file));
|
|
return &track->tr_dbe;
|
|
|
|
}
|
|
|
|
static void track_write(struct file *file, struct db_entry *dbe)
|
|
{
|
|
struct track *track = TRACK(dbe);
|
|
gchar *path = __track_path(track);
|
|
|
|
file_writef(file, "%u %u %hu ", library_index(track->tr_library),
|
|
album_index(track->tr_album),
|
|
track->tr_track);
|
|
date_write_stamp(file, &track->tr_date);
|
|
file_writef(file, " %hu %hu %s\n%s", track->tr_count,
|
|
track->tr_length,
|
|
track->tr_title,
|
|
path);
|
|
|
|
g_free(path);
|
|
}
|
|
|
|
|
|
static const struct db_ops track_ops = {
|
|
.dbe_alloc = track_alloc,
|
|
.dbe_free = track_free,
|
|
.dbe_key = track_key,
|
|
.dbe_read = track_read,
|
|
.dbe_write = track_write,
|
|
};
|
|
|
|
|
|
void track_db_init()
|
|
{
|
|
db_init(&track_db, "track.db", false, &track_ops, TRACK_DB_MIN);
|
|
db_load(&track_db);
|
|
}
|
|
|
|
void track_db_deinit()
|
|
{
|
|
db_deinit(&track_db);
|
|
}
|
|
|
|
void track_db_commit()
|
|
{
|
|
db_save(&track_db);
|
|
}
|
|
|
|
bool track_db_defrag()
|
|
{
|
|
return db_defrag(&track_db);
|
|
}
|
|
|
|
void track_db_rekey()
|
|
{
|
|
struct db_entry *dbe, *next;
|
|
struct track *track;
|
|
unsigned int lib;
|
|
gchar *path;
|
|
|
|
db_for_each(dbe, next, &track_db) {
|
|
track = TRACK(dbe);
|
|
|
|
sscanf(track->tr_path, "%u/%m[^\n]", &lib, &path);
|
|
if (lib != library_index(track->tr_library)) {
|
|
track->tr_path = __track_key(track->tr_library, path);
|
|
db_rekey(&track_db, dbe);
|
|
} else
|
|
g_free(path);
|
|
}
|
|
}
|
|
|
|
const struct database *track_db_get()
|
|
{
|
|
return &track_db;
|
|
}
|
|
|
|
unsigned int track_db_count_unplayed()
|
|
{
|
|
return unplayed_count;
|
|
}
|
|
|
|
unsigned int track_db_count_plays()
|
|
{
|
|
return play_count;
|
|
}
|
|
|
|
unsigned int track_db_average_plays()
|
|
{
|
|
if (unplayed_count == track_db.db_size)
|
|
return 0;
|
|
return play_count / (track_db.db_size - unplayed_count);
|
|
}
|
|
|
|
struct track *track_add(struct library *library, const gchar *filepath)
|
|
{
|
|
unsigned int offset = strlen(library->li_path) + 1;
|
|
gchar *key = __track_key(library, g_strdup(filepath + offset));
|
|
struct track *track = NULL;
|
|
|
|
if (!db_get(&track_db, key))
|
|
track = TRACK(db_insert(&track_db, key));
|
|
|
|
g_free(key);
|
|
return track;
|
|
}
|
|
|
|
void track_remove(struct track *track)
|
|
{
|
|
db_remove(&track_db, &track->tr_dbe);
|
|
}
|
|
|
|
void track_remove_all(struct library *library)
|
|
{
|
|
struct db_entry *it, *next;
|
|
|
|
db_for_each(it, next, &track_db) {
|
|
if (TRACK(it)->tr_library == library)
|
|
db_remove(&track_db, it);
|
|
}
|
|
track_db_commit();
|
|
}
|
|
|
|
struct track *track_get(const unsigned int index)
|
|
{
|
|
return TRACK(db_at(&track_db, index));
|
|
}
|
|
|
|
int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare)
|
|
{
|
|
switch (compare) {
|
|
case COMPARE_ARTIST:
|
|
return artist_compare(lhs->tr_album->al_artist,
|
|
rhs->tr_album->al_artist);
|
|
case COMPARE_ALBUM:
|
|
return album_compare(lhs->tr_album, rhs->tr_album);
|
|
case COMPARE_COUNT:
|
|
return lhs->tr_count - rhs->tr_count;
|
|
case COMPARE_GENRE:
|
|
return genre_compare(lhs->tr_album->al_genre,
|
|
rhs->tr_album->al_genre);
|
|
case COMPARE_LENGTH:
|
|
return lhs->tr_length - rhs->tr_length;
|
|
case COMPARE_PLAYED:
|
|
return date_compare(&lhs->tr_date, &rhs->tr_date);
|
|
case COMPARE_TITLE:
|
|
return string_compare_tokens(lhs->tr_tokens, rhs->tr_tokens);
|
|
case COMPARE_TRACK:
|
|
return lhs->tr_track - rhs->tr_track;
|
|
case COMPARE_YEAR:
|
|
return album_compare_year(lhs->tr_album, rhs->tr_album);
|
|
}
|
|
|
|
return 0; /* We should never get here. */
|
|
}
|
|
|
|
bool track_match_token(struct track *track, const gchar *token)
|
|
{
|
|
return string_match_token(token, track->tr_tokens) ||
|
|
string_match_token(token, track->tr_alts);
|
|
}
|
|
|
|
gchar *track_path(struct track *track)
|
|
{
|
|
gchar *path, *res;
|
|
|
|
if (track->tr_library) {
|
|
path = __track_path(track);
|
|
res = library_file(track->tr_library, path);
|
|
g_free(path);
|
|
return res;
|
|
}
|
|
return g_strdup("");
|
|
}
|
|
|
|
void track_played(struct track *track)
|
|
{
|
|
if (track->tr_count == 0)
|
|
unplayed_count--;
|
|
track->tr_count++;
|
|
play_count++;
|
|
date_today(&track->tr_date);
|
|
track_db_commit();
|
|
}
|
|
|
|
gchar *track_last_play(struct track *track)
|
|
{
|
|
if (track->tr_count > 0)
|
|
return date_string(&track->tr_date);
|
|
return g_strdup("Never");
|
|
}
|
|
|
|
#ifdef CONFIG_TESTING
|
|
const struct db_ops *test_track_ops() { return &track_ops; }
|
|
#endif /* CONFIG_TESTING */
|