/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include #include #include #include #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 */