/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include 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; } struct db_entry *track_alloc(const gchar *key) { const TagLib_AudioProperties *audio; struct library *library; struct track *track = NULL; unsigned int lib_id; TagLib_File *file; TagLib_Tag *tag; char *fullpath, *path; sscanf(key, "%u/%m[^\n]", &lib_id, &path); library = library_get(lib_id); fullpath = library_file(library, path); file = taglib_file_new(fullpath); if (!file || !taglib_file_is_valid(file)) { printf("WARNING: Could not read tags for: %s\n", fullpath); goto out; } track = __track_alloc(); tag = taglib_file_tag(file); audio = taglib_file_audioproperties(file); track->tr_album = album_find(taglib_tag_album(tag), taglib_tag_year(tag)); track->tr_artist = artist_find(taglib_tag_artist(tag)); track->tr_genre = genre_find(taglib_tag_genre(tag)); track->tr_library = library; if (track->tr_album->al_artist == NULL) track->tr_album->al_artist = track->tr_artist; if (track->tr_album->al_genre == NULL) track->tr_album->al_genre = track->tr_genre; unplayed_count++; 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_path = g_strdup(key); 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); out: 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--; if (track->tr_library) track->tr_library->li_size--; g_strfreev(track->tr_tokens); g_strfreev(track->tr_alts); g_free(track->tr_title); g_free(track); } static void track_setup(struct db_entry *dbe) { struct track *track = TRACK(dbe); track->tr_library->li_size++; } static gchar *track_key(struct db_entry *dbe) { return TRACK(dbe)->tr_path; } static struct db_entry *track_read(struct file *file) { unsigned int library_id, artist_id, album_id, genre_id; struct track *track = __track_alloc(); file_readf(file, "%u %u %u %u %hu", &library_id, &artist_id, &album_id, &genre_id, &track->tr_track); date_read(file, &track->tr_date); file_readf(file, "%hu %hu", &track->tr_count, &track->tr_length); play_count += track->tr_count; if (track->tr_count == 0) unplayed_count++; track->tr_album = album_get(album_id); track->tr_artist = artist_get(artist_id); track->tr_genre = genre_get(genre_id); track->tr_library = library_get(library_id); if (track->tr_album->al_artist == NULL) track->tr_album->al_artist = track->tr_artist; if (track->tr_album->al_genre == NULL) track->tr_album->al_genre = track->tr_genre; 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 %u %u %u ", library_index(track->tr_library), artist_index(track->tr_artist), album_index(track->tr_album), genre_index(track->tr_genre), track->tr_track); date_write(file, &track->tr_date); file_writef(file, " %hu %hu %s\n%s\n", track->tr_count, track->tr_length, track->tr_title, path); g_free(path); } static const struct db_ops track_ops = { track_alloc, track_free, track_key, track_read, track_setup, track_write, }; void track_db_init() { db_init(&track_db, "track.db", false, &track_ops); db_load_idle(&track_db); } void track_db_deinit() { db_deinit(&track_db); } void track_db_commit() { db_save(&track_db); } 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_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)); } int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare) { switch (compare) { case COMPARE_ARTIST: return artist_compare(lhs->tr_artist, rhs->tr_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_genre, rhs->tr_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(""); } void track_played(struct track *track) { 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 */