407 lines
9.4 KiB
C
407 lines
9.4 KiB
C
/*
|
|
* Copyright 2014 (c) Anna Schumaker.
|
|
*/
|
|
#include <core/idle.h>
|
|
#include <core/string.h>
|
|
#include <core/version.h>
|
|
#include <core/tags/album.h>
|
|
#include <core/tags/genre.h>
|
|
#include <coverart/caa_c.h>
|
|
#include <musicbrainz5/mb5_c.h>
|
|
|
|
#ifdef CONFIG_TESTING
|
|
#define OCARINA_AGENT "ocarina-test"
|
|
#else
|
|
#define OCARINA_AGENT OCARINA_NAME
|
|
#endif
|
|
|
|
#define ALBUM_DB_MIN 0 /* Ocarina 6.0 */
|
|
static struct database album_db;
|
|
static bool album_db_upgraded = false;
|
|
|
|
struct album_cache_file {
|
|
struct file ac_file;
|
|
gchar *ac_subdir;
|
|
gchar *ac_name;
|
|
};
|
|
|
|
struct album_cache_file *__album_alloc_file(struct album *al)
|
|
{
|
|
struct album_cache_file *ret = g_malloc(sizeof(struct album_cache_file));
|
|
gchar *name = g_uri_escape_string(al->al_name, " ", true);
|
|
|
|
ret->ac_subdir = g_strdup_printf("%d", al->al_year);
|
|
ret->ac_name = g_strdup_printf("%s.jpg", name);
|
|
file_init_cache(&ret->ac_file, ret->ac_subdir, ret->ac_name);
|
|
|
|
g_free(name);
|
|
return ret;
|
|
}
|
|
|
|
static inline void __album_free_file(struct album_cache_file *acf)
|
|
{
|
|
g_free(acf->ac_subdir);
|
|
g_free(acf->ac_name);
|
|
g_free(acf);
|
|
}
|
|
|
|
static bool __album_fetch_cover(struct album *album, gchar *releaseid)
|
|
{
|
|
struct album_cache_file *file;
|
|
CaaImageData image;
|
|
CaaCoverArt *caa;
|
|
gchar error[256];
|
|
|
|
caa = caa_coverart_new(OCARINA_AGENT);
|
|
if (!caa)
|
|
return false;
|
|
|
|
image = caa_coverart_fetch_front(caa, releaseid);
|
|
if (!image) {
|
|
caa_coverart_get_lasterrormessage(caa, error, sizeof(error));
|
|
g_printf("Cover Art Archive: %s\n", error);
|
|
goto out;
|
|
}
|
|
|
|
file = __album_alloc_file(album);
|
|
if (file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
|
|
file_write(&file->ac_file, caa_imagedata_data(image),
|
|
caa_imagedata_size(image));
|
|
file_close(&file->ac_file);
|
|
}
|
|
__album_free_file(file);
|
|
|
|
caa_imagedata_delete(image);
|
|
out:
|
|
caa_coverart_delete(caa);
|
|
return album_artwork_exists(album);
|
|
}
|
|
|
|
static bool __album_foreach_fetch(struct album *album, Mb5Metadata metadata)
|
|
{
|
|
Mb5ReleaseList list = mb5_metadata_get_releaselist(metadata);
|
|
gchar releaseid[40];
|
|
Mb5Release release;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < mb5_release_list_size(list); i++) {
|
|
release = mb5_release_list_item(list, i);
|
|
if (!release)
|
|
break;
|
|
mb5_release_get_id(release, releaseid, sizeof(releaseid));
|
|
if (__album_fetch_cover(album, releaseid))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool __album_run_query(struct album *album, gchar *term1,
|
|
gchar *term2, gchar *term3)
|
|
{
|
|
gchar *param, *query = "query";
|
|
Mb5Metadata data = NULL;
|
|
unsigned int code;
|
|
gchar error[256];
|
|
bool ret = false;
|
|
Mb5Query *mb5;
|
|
|
|
param = g_strjoin(" AND ", term1, term2, term3, NULL);
|
|
|
|
do {
|
|
mb5 = mb5_query_new(OCARINA_AGENT, NULL, 0);
|
|
if (!mb5)
|
|
break;
|
|
|
|
data = mb5_query_query(mb5, "release", "", "", 1, &query, ¶m);
|
|
code = mb5_query_get_lasthttpcode(mb5);
|
|
if (mb5_query_get_lastresult(mb5) != 0) {
|
|
mb5_query_get_lasterrormessage(mb5, error, sizeof(error));
|
|
g_printf("MusicBrainz: %s\n", error);
|
|
}
|
|
|
|
mb5_query_delete(mb5);
|
|
} while (code == 503);
|
|
|
|
if (data) {
|
|
ret = __album_foreach_fetch(album, data);
|
|
mb5_metadata_delete(data);
|
|
}
|
|
|
|
g_free(param);
|
|
return ret;
|
|
}
|
|
|
|
static bool __album_query_artist(struct album *album, struct artist *al_artist,
|
|
gchar *lower)
|
|
{
|
|
gchar *release, *artist, *year;
|
|
bool found = false;
|
|
|
|
if (!al_artist || !string_length(al_artist->ar_name) ||
|
|
strcmp(al_artist->ar_tokens[0], "various") == 0)
|
|
return false;
|
|
|
|
release = g_strdup_printf("release:\"%s\"~", lower);
|
|
artist = g_strdup_printf("artist:\"%s\"~", al_artist->ar_name);
|
|
year = g_strdup_printf("date:%d*", album->al_year);
|
|
|
|
if (album->al_year > 0)
|
|
found = __album_run_query(album, release, artist, year);
|
|
if (!found)
|
|
found = __album_run_query(album, release, artist, NULL);
|
|
if (!found && album->al_year > 0)
|
|
found = __album_run_query(album, lower, artist, year);
|
|
if (!found)
|
|
found = __album_run_query(album, lower, artist, NULL);
|
|
|
|
g_free(release);
|
|
g_free(artist);
|
|
g_free(year);
|
|
return found;
|
|
}
|
|
|
|
static bool __album_fetch_artwork(struct album *album)
|
|
{
|
|
gchar *lower;
|
|
|
|
if (album_artwork_exists(album))
|
|
return true;
|
|
if (string_length(album->al_name) == 0)
|
|
return true;
|
|
|
|
lower = g_strjoinv(" ", album->al_tokens);
|
|
if (!__album_query_artist(album, album->al_artist, lower))
|
|
__album_run_query(album, lower, NULL, NULL);
|
|
|
|
g_free(lower);
|
|
return true;
|
|
}
|
|
|
|
static gchar *__album_key(struct artist *artist, struct genre *genre,
|
|
const gchar *name, unsigned int year)
|
|
{
|
|
if (!artist || !genre)
|
|
return g_strdup_printf("%u/%s", year, name);
|
|
return g_strdup_printf("%u/%u/%u/%s", artist_index(artist),
|
|
genre_index(genre), year, name);
|
|
}
|
|
|
|
static struct album *__album_alloc(struct artist *artist, struct genre *genre,
|
|
gchar *name, unsigned int year)
|
|
{
|
|
struct album *album = g_malloc(sizeof(struct album));
|
|
|
|
dbe_init(&album->al_dbe, album);
|
|
album->al_year = year;
|
|
album->al_name = name;
|
|
album->al_tokens = g_str_tokenize_and_fold(name, NULL, &album->al_alts);
|
|
album->al_artist = artist;
|
|
album->al_genre = genre;
|
|
|
|
if (!album_artwork_exists(album) && artist && genre)
|
|
idle_schedule(IDLE_ASYNC, IDLE_FUNC(__album_fetch_artwork), album);
|
|
return album;
|
|
}
|
|
|
|
|
|
static struct db_entry *__album_alloc_v0(const gchar *key)
|
|
{
|
|
unsigned int year;
|
|
gchar *name;
|
|
|
|
if (sscanf(key, "%u/%m[^\n]", &year, &name) == 1)
|
|
name = g_strdup("");
|
|
return &__album_alloc(NULL, NULL, name, year)->al_dbe;
|
|
}
|
|
|
|
static struct db_entry *album_alloc(const gchar *key, unsigned int index)
|
|
{
|
|
unsigned int artist_id, genre_id, year, n;
|
|
gchar *name;
|
|
|
|
n = sscanf(key, "%u/%u/%u/%m[^\n]", &artist_id, &genre_id, &year, &name);
|
|
if (n == 1)
|
|
return __album_alloc_v0(key);
|
|
else if (n == 3)
|
|
name = g_strdup("");
|
|
return &__album_alloc(artist_get(artist_id), genre_get(genre_id),
|
|
name, year)->al_dbe;
|
|
}
|
|
|
|
static void album_free(struct db_entry *dbe)
|
|
{
|
|
g_free(ALBUM(dbe)->al_name);
|
|
g_strfreev(ALBUM(dbe)->al_tokens);
|
|
g_strfreev(ALBUM(dbe)->al_alts);
|
|
g_free(ALBUM(dbe));
|
|
}
|
|
|
|
static gchar *album_key(struct db_entry *dbe)
|
|
{
|
|
return __album_key(ALBUM(dbe)->al_artist, ALBUM(dbe)->al_genre,
|
|
ALBUM(dbe)->al_name, ALBUM(dbe)->al_year);
|
|
}
|
|
|
|
static struct album *__album_parse_v0(gchar *line)
|
|
{
|
|
unsigned int year;
|
|
gchar *name;
|
|
|
|
if (sscanf(line, "%u %m[^\n]", &year, &name) == 1)
|
|
name = g_strdup("");
|
|
return __album_alloc(NULL, NULL, name, year);
|
|
}
|
|
|
|
static struct db_entry *album_read(struct file *file, unsigned int index)
|
|
{
|
|
unsigned int year, artist_id, genre_id, n;
|
|
struct album *album;
|
|
gchar *line, *name;
|
|
|
|
line = file_readl(file);
|
|
if (file_version(file) == 0) {
|
|
album = __album_parse_v0(line);
|
|
album_db_upgraded = true;
|
|
goto out;
|
|
}
|
|
|
|
n = sscanf(line, "%u %u %u %m[^\n]", &artist_id, &genre_id, &year, &name);
|
|
if (n == 3)
|
|
name = g_strdup("");
|
|
|
|
album = __album_alloc(artist_get(artist_id),
|
|
genre_get(genre_id), name, year);
|
|
out:
|
|
g_free(line);
|
|
return &album->al_dbe;
|
|
}
|
|
|
|
static void album_write(struct file *file, struct db_entry *dbe)
|
|
{
|
|
struct album *album = ALBUM(dbe);
|
|
struct artist *artist = album->al_artist;
|
|
struct genre *genre = album->al_genre;
|
|
|
|
file_writef(file, "%u %u %u %s", artist ? artist_index(artist) : 0,
|
|
genre ? genre_index(genre) : 0,
|
|
album->al_year, album->al_name);
|
|
}
|
|
|
|
|
|
static const struct db_ops album_ops = {
|
|
.dbe_alloc = album_alloc,
|
|
.dbe_free = album_free,
|
|
.dbe_key = album_key,
|
|
.dbe_read = album_read,
|
|
.dbe_write = album_write,
|
|
};
|
|
|
|
|
|
void album_db_init()
|
|
{
|
|
db_init(&album_db, "album.db", true, &album_ops, ALBUM_DB_MIN);
|
|
db_load(&album_db);
|
|
}
|
|
|
|
void album_db_deinit()
|
|
{
|
|
db_deinit(&album_db);
|
|
}
|
|
|
|
bool album_db_defrag()
|
|
{
|
|
return db_defrag(&album_db);
|
|
}
|
|
|
|
bool album_db_upgrade_done()
|
|
{
|
|
struct db_entry *dbe, *next;
|
|
|
|
if (album_db_upgraded == false)
|
|
return false;
|
|
|
|
db_for_each(dbe, next, &album_db) {
|
|
if (!ALBUM(dbe)->al_artist && !ALBUM(dbe)->al_genre)
|
|
db_remove(&album_db, dbe);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct album *album_find(struct artist *artist, struct genre *genre,
|
|
const gchar *name, unsigned int year)
|
|
{
|
|
gchar *key = __album_key(artist, genre, name, year);
|
|
struct album *album = ALBUM(db_find(&album_db, key));
|
|
g_free(key);
|
|
return album;
|
|
}
|
|
|
|
struct album *album_get(const unsigned int index)
|
|
{
|
|
return ALBUM(db_at(&album_db, index));
|
|
}
|
|
|
|
int album_compare(struct album *lhs, struct album *rhs)
|
|
{
|
|
return string_compare_tokens(lhs->al_tokens, rhs->al_tokens);
|
|
}
|
|
|
|
int album_compare_year(struct album *lhs, struct album *rhs)
|
|
{
|
|
if (lhs->al_year - rhs->al_year == 0)
|
|
return album_compare(lhs, rhs);
|
|
return lhs->al_year - rhs->al_year;
|
|
}
|
|
|
|
bool album_match_token(struct album *album, const gchar *string)
|
|
{
|
|
return string_match_token(string, album->al_tokens) ||
|
|
string_match_token(string, album->al_alts);
|
|
}
|
|
|
|
bool album_artwork_exists(struct album *album)
|
|
{
|
|
struct album_cache_file *file;
|
|
bool ret;
|
|
|
|
file = __album_alloc_file(album);
|
|
ret = file_exists(&file->ac_file);
|
|
__album_free_file(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gchar *album_artwork_path(struct album *album)
|
|
{
|
|
struct album_cache_file *file;
|
|
gchar *ret = NULL;
|
|
|
|
file = __album_alloc_file(album);
|
|
if (file_exists(&file->ac_file))
|
|
ret = file_path(&file->ac_file);
|
|
__album_free_file(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool album_artwork_import(struct album *album, gchar *path)
|
|
{
|
|
struct album_cache_file *file;
|
|
bool ret = false;
|
|
|
|
file = __album_alloc_file(album);
|
|
if (path && file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
|
|
ret = file_import(&file->ac_file, path);
|
|
file_close(&file->ac_file);
|
|
}
|
|
__album_free_file(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_TESTING
|
|
const struct db_ops *test_album_ops() { return &album_ops; }
|
|
#endif /* CONFIG_TESTING */
|