ocarina/core/tags/album.c
Anna Schumaker 0cefd158d9 core/tags/album: Add artist and genre information to saved data
And perform an upgrade when reading back in.  After track tags are read,
we can save the database in the new format and remove the old tags.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2016-09-26 08:07:35 -04:00

393 lines
8.9 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 */
#define ALBUM_DB_CUR 1 /* Ocarina 6.5 */
static struct database album_db;
static bool album_db_upgraded = false;
static inline void __album_init_file(struct album *al, struct cache_file *f)
{
gchar *name = g_uri_escape_string(al->al_name, " ", true);
cache_file_init(f, g_strdup_printf("%d", al->al_year),
g_strdup_printf("%s.jpg", name));
g_free(name);
}
static inline void __album_deinit_file(struct cache_file *f)
{
g_free(f->cf_subdir);
g_free(f->cf_name);
}
static bool __album_fetch_cover(struct album *album, gchar *releaseid)
{
struct 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;
}
__album_init_file(album, &file);
if (cache_file_open(&file, OPEN_WRITE)) {
cache_file_write(&file, caa_imagedata_data(image),
caa_imagedata_size(image));
cache_file_close(&file);
}
__album_deinit_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, &param);
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;
if (!al_artist || 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 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 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 = {
album_alloc,
album_free,
album_key,
album_read,
NULL,
album_write,
};
void album_db_init()
{
db_init(&album_db, "album.db", true, &album_ops, ALBUM_DB_MIN,
ALBUM_DB_CUR);
db_load_idle(&album_db);
}
void album_db_deinit()
{
db_deinit(&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 cache_file file;
bool ret;
__album_init_file(album, &file);
ret = cache_file_exists(&file);
__album_deinit_file(&file);
return ret;
}
gchar *album_artwork_path(struct album *album)
{
struct cache_file file;
gchar *ret = NULL;
__album_init_file(album, &file);
if (cache_file_exists(&file))
ret = cache_file_path(&file);
__album_deinit_file(&file);
return ret;
}
bool album_artwork_import(struct album *album, gchar *path)
{
struct cache_file file;
bool ret = false;
__album_init_file(album, &file);
if (path && cache_file_open(&file, OPEN_WRITE)) {
ret = cache_file_import(&file, path);
cache_file_close(&file);
}
__album_deinit_file(&file);
return ret;
}
#ifdef CONFIG_TESTING
const struct db_ops *test_album_ops() { return &album_ops; }
#endif /* CONFIG_TESTING */