/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include #include #include static struct database album_db; 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_NAME); 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_NAME, 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; if (!al_artist || strncmp(al_artist->ar_lower, "various", 7) == 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(const gchar *name, unsigned int year) { return g_strdup_printf("%u/%s", year, name); } static struct album *__album_alloc(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 = NULL; if (!album_artwork_exists(album)) idle_schedule(IDLE_ASYNC, IDLE_FUNC(__album_fetch_artwork), album); return album; } static struct db_entry *album_alloc(const gchar *key) { unsigned int year; gchar *name; if (sscanf(key, "%u/%m[^\n]", &year, &name) == 1) name = g_strdup(""); return &__album_alloc(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_name, ALBUM(dbe)->al_year); } static struct db_entry *album_read(struct file *file) { unsigned int year; gchar *line, *name; line = file_readl(file); if (sscanf(line, "%u %m[^\n]", &year, &name) == 1) name = g_strdup(""); g_free(line); return &__album_alloc(name, year)->al_dbe; } static void album_write(struct file *file, struct db_entry *dbe) { file_writef(file, "%u %s", ALBUM(dbe)->al_year, ALBUM(dbe)->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); db_load_idle(&album_db); } void album_db_deinit() { db_deinit(&album_db); } struct album *album_find(const gchar *name, unsigned int year) { gchar *key = __album_key(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_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 */