/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include #include #include Database artist_db("artist.db", true); Database album_db("album.db", true); Database genre_db("genre.db", true); Database library_db("library.db", true); Database track_db("track.db", false); /** * * Artist tag * */ Artist :: Artist() {} Artist :: Artist(const std::string &s) : name(s), lower(filter :: lowercase(name)) { } const std::string Artist :: primary_key() const { return name; } void Artist :: read(File &f) { name = f.getline(); lower = filter :: lowercase(name); } void Artist :: write(File &f) { f << name; } /** * * Album tag * */ Album :: Album() {} Album :: Album(const std::string &s, unsigned int y) : name(s), lower(filter :: lowercase(name)), year(y) { } const std::string Album :: primary_key() const { std::stringstream ss; ss << year << "." << name; return ss.str(); } void Album :: read(File &f) { f >> year; name = f.getline(); lower = filter :: lowercase(name); } void Album :: write(File &f) { f << year << " " << name; } /** * * Genre tag * */ Genre :: Genre() {} Genre :: Genre(const std::string &s) : name(s), lower(filter :: lowercase(name)) { } const std::string Genre :: primary_key() const { return name; } void Genre :: read(File &f) { name = f.getline(); lower = filter :: lowercase(name); } void Genre :: write(File &f) { f << name; } /** * * Library tag * */ Library :: Library() : count(0), enabled(false) { } Library :: Library(const std::string &s) : root_path(s), count(0), enabled(true) { } const std::string Library :: primary_key() const { return root_path; } void Library :: read(File &f) { f >> enabled; root_path = f.getline(); } void Library :: write(File &f) { f << enabled << " " << root_path; } /** * * Track tag * */ Track :: Track() : library(NULL), artist(NULL), album(NULL), genre(NULL){} Track :: Track(const std::string &f, Library *l) : library(l), artist(NULL), album(NULL), genre(NULL), play_count(0), last_year(0), last_month(0), last_day(0), filepath(f.substr(l->root_path.size() + 1)) { library->count++; } Track :: ~Track() { library->count--; } const std::string Track :: primary_key() const { return path(); } void Track :: read(File &f) { unsigned int library_id, artist_id, album_id, genre_id; f >> library_id >> artist_id >> album_id >> genre_id; f >> track >> last_year >> last_month >> last_day; f >> play_count >> length; title = f.getline(); filepath = f.getline(); library = library_db.at(library_id); artist = artist_db.at(artist_id); album = album_db.at(album_id); genre = genre_db.at(genre_id); title_lower = filter :: add(title, id); filter :: add(artist->name, id); filter :: add(album->name, id); library->count++; set_length_str(); } void Track :: write(File &f) { f << library->id << " " << artist->id << " " << album->id << " "; f << genre->id << " " << track << " "; f << last_year << " " << last_month << " " << last_day << " "; f << play_count << " " << length << " " << title << std::endl; f << filepath << std::endl; } void Track :: set_length_str() { std::stringstream ss; unsigned int minutes = length / 60; unsigned int seconds = length % 60; ss << minutes << ":"; if (seconds < 10) ss << "0"; ss << seconds; length_str = ss.str(); } static inline const std::string format_tag(const TagLib::String &str) { return str.stripWhiteSpace().to8Bit(true); } template static T *find_or_insert(const T &tag, Database &db) { T *ret = db.find(tag.primary_key()); if (!ret) ret = db.insert(tag); return ret; } void Track :: tag() { TagLib :: Tag *tag; TagLib :: AudioProperties *audio; TagLib :: FileRef ref(path().c_str(), true, TagLib::AudioProperties::Fast); if (ref.isNull()) { print("ERROR: Could not read tags for file %s\n", path().c_str()); return; } tag = ref.tag(); audio = ref.audioProperties(); artist = find_or_insert(Artist(format_tag(tag->artist())), artist_db); album = find_or_insert(Album(format_tag(tag->album()), tag->year()), album_db); genre = find_or_insert(Genre(format_tag(tag->genre())), genre_db); track = tag->track(); length = audio->length(); title = format_tag(tag->title()); title_lower = filter :: add(title, id); set_length_str(); filter :: add(artist->name, id); filter :: add(album->name, id); library->count++; } const std::string Track :: path() const { return library->root_path + "/" + filepath; } void Track :: played() { time_t the_time = time(NULL); struct tm *now = localtime(&the_time); play_count++; last_day = now->tm_mday; last_month = now->tm_mon + 1; last_year = now->tm_year + 1900; tagdb :: commit(); } /* * Returns: * 0: lhs == rhs * < 0: lhs < rhs, or rhs is empty * > 0: lhs > rhs, or lhs is empty */ static inline int compare_string(const std::string &a, const std::string &b) { if (a.size() == 0) return 1; else if (b.size() == 0) return -1; return a.compare(b); } static inline int compare_uint(unsigned int a, unsigned int b) { if (a == b) return 0; if (a < b) return -1; return 1; } int Track :: less_than(Track *rhs, sort_t field) { int ret; switch (field) { case SORT_ARTIST: return compare_string(artist->lower, rhs->artist->lower); case SORT_ALBUM: return compare_string(album->lower, rhs->album->lower); case SORT_COUNT: return compare_uint(play_count, rhs->play_count); case SORT_GENRE: return compare_string(genre->lower, rhs->genre->lower); case SORT_LENGTH: return compare_uint(length, rhs->length); case SORT_PLAYED: ret = compare_uint(last_year, rhs->last_year); if (ret == 0) { ret = compare_uint(last_month, rhs->last_month); if (ret == 0) ret = compare_uint(last_day, rhs->last_day); } return ret; case SORT_TITLE: return compare_string(title_lower, rhs->title_lower); case SORT_TRACK: return compare_uint(track, rhs->track); case SORT_YEAR: return compare_uint(album->year, rhs->album->year); } return 0; } /** * * Tagdb functions * */ void tagdb :: init() { artist_db.load(); album_db.load(); genre_db.load(); library_db.load(); track_db.load(); } void tagdb :: commit() { track_db.save(); } void tagdb :: commit_library() { library_db.save(); } Track *tagdb :: add_track(const std::string &filepath, Library *library) { Track *track = track_db.insert(Track(filepath, library)); if (track) track->tag(); return track; } Library *tagdb :: add_library(const std::string &filepath) { return library_db.insert(Library(filepath)); } void tagdb :: remove_track(unsigned int track_id) { track_db.remove(track_id); } void tagdb :: remove_library(unsigned int library_id) { Database::iterator it; for (it = track_db.begin(); it != track_db.end(); it++) { if ((*it)->library->id == library_id) track_db.remove((*it)->id); } tagdb :: commit(); library_db.remove(library_id); } Track *tagdb :: lookup(unsigned int track_id) { return track_db.at(track_id); } Library *tagdb :: lookup_library(unsigned int library_id) { return library_db.at(library_id); } Database &tagdb :: get_track_db() { return track_db; } Database &tagdb :: get_library_db() { return library_db; }