/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include #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; } static struct track *__track_alloc_filepath(const gchar *filepath) { TagLib_File *file = taglib_file_new(filepath); const TagLib_AudioProperties *audio; struct track *track = NULL; struct artist *artist; struct genre *genre; TagLib_Tag *tag; if (!file || !taglib_file_is_valid(file)) { g_printerr("WARNING: Could not read tags for: %s\n", filepath); return NULL; } 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_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_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); return track; } static void __track_free(struct track *track) { g_strfreev(track->tr_tokens); g_strfreev(track->tr_alts); g_free(track->tr_title); g_free(track); } struct db_entry *track_alloc(const gchar *key, unsigned int index) { struct library *library; char *fullpath, *path; struct track *track; unsigned int lib_id; sscanf(key, "%u/%m[^\n]", &lib_id, &path); library = library_get(lib_id); fullpath = library_file(library, path); track = __track_alloc_filepath(fullpath); if (track) { track->tr_library = library; track->tr_path = g_strdup(key); unplayed_count++; } 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--; __track_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) { struct artist *artist = artist_get(file_readu(file)); struct album *album = album_get( file_readu(file)); struct genre *genre = genre_get( file_readu(file)); track->tr_track = file_readhu(file); date_read(file, &track->tr_date); 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(); track->tr_library = library_get(file_readu(file)); if (file_version(file) == 0) track_read_v0(file, track); else { track->tr_album = album_get(file_readu(file)); track->tr_track = file_readhu(file); date_read_stamp(file, &track->tr_date); } track->tr_count = file_readhu(file); track->tr_length = file_readhu(file); 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_alloc_external(const gchar *filepath) { struct track *track = __track_alloc_filepath(filepath); if (track) { track->tr_library = NULL; track->tr_path = g_strdup(filepath); } return track; } void track_free_external(struct track *track) { if (TRACK_IS_EXTERNAL(track)) { g_free(track->tr_path); __track_free(track); } } 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)); } struct track *track_lookup(const gchar *filepath) { struct library *library = library_lookup(filepath); unsigned int offset; struct track *track; gchar *key; if (!library) return NULL; offset = strlen(library->li_path) + 1; key = __track_key(library, g_strdup(filepath + offset)); track = TRACK(db_get(&track_db, key)); g_free(key); return track; } 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(track->tr_path); } void track_played(struct track *track) { if (TRACK_IS_EXTERNAL(track)) return; 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 */