/* * Copyright 2013 (c) Anna Schumaker. */ #include #include #include #include #include #include #include #include static Database album_db("album.db", false); static Database artist_db("artist.db", false); static Database genre_db("genre.db", false); static Database track_db("track.db", false); static Database library_db("library.db", false); struct ImportData { std::string filepath; std::string title; unsigned int track; unsigned int last_day; unsigned int last_month; unsigned int last_year; unsigned int length; unsigned int count; }; /* * library :: Artist: Artist tag information */ library :: AGInfo :: AGInfo() { } library :: AGInfo :: AGInfo(DB_Type type, TagLib :: Tag *tag) : db_type(type) { if (db_type == DB_ARTIST) name = tag->artist().stripWhiteSpace().to8Bit(true); else if (db_type == DB_GENRE) name = tag->genre().stripWhiteSpace().to8Bit(true); else throw -E_INVAL; key_lower = filter :: to_lowercase(name); } library :: AGInfo :: AGInfo(DB_Type type, const std::string &str) : db_type(type) { if ((db_type == DB_ARTIST) || (db_type == DB_GENRE)) { name = str; key_lower = filter :: to_lowercase(name); } else throw -E_INVAL; } const std::string library :: AGInfo :: primary_key() { return name; } void library :: AGInfo :: read(File &f) { name = f.getline(); key_lower = filter :: to_lowercase(name); } void library :: AGInfo :: write(File &f) { f << name; } /* * library :: Album: Album tag information */ library :: Album :: Album() : name(""), year(0), artist_id(0) { } library :: Album :: Album(TagLib :: Tag *tag, unsigned int artist) : name(tag->album().stripWhiteSpace().to8Bit(true)), year(tag->year()), artist_id(artist) { name_lower = filter :: to_lowercase(name); } library :: Album :: Album(const std::string &str, unsigned int yr, unsigned int artist) : name(str), year(yr), artist_id(artist) { name_lower = filter :: to_lowercase(name); } const std::string library :: Album :: primary_key() { std::stringstream ss; ss << artist_id << "." << name << "." << year; return ss.str(); } void library :: Album :: read(File &f) { f >> artist_id >> year; name = f.getline(); name_lower = filter :: to_lowercase(name); } void library :: Album :: write(File &f) { f << artist_id << " " << year << " " << name; } /* * library :: Library: Basic information about each directory in the library */ library :: Library :: Library() : root_path(""), size(0), enabled(false) { } library :: Library :: Library(const std::string &path, bool is_enabled) : root_path(path), size(0), enabled(is_enabled) { } const std::string library :: Library :: primary_key() { return root_path; } void library :: Library :: read(File &f) { f >> enabled; root_path = f.getline(); size = 0; } void library :: Library :: write(File &f) { f << enabled << " " << root_path; } /* * library :: Track: Track tag information */ library :: Track :: Track() : library_id(0), artist_id(0), album_id(0), genre_id(0) { } library :: Track :: Track(TagLib :: Tag *tag, TagLib :: AudioProperties *audio, unsigned int lib, unsigned int artist, unsigned int album, unsigned int genre, const std :: string &path) : library_id(lib), artist_id(artist), album_id(album), genre_id(genre), track(tag->track()), last_year(0), last_month(0), last_day(0), play_count(0), length(audio->length()), title(tag->title().stripWhiteSpace().to8Bit(true)) { std::stringstream ss; unsigned int minutes, seconds; full_path = path; filepath = path.substr(library_db.at(library_id)->root_path.size() + 1); title_lower = filter :: to_lowercase(title); minutes = length / 60; seconds = length % 60; ss << minutes << ":"; if (seconds < 10) ss << "0"; ss << seconds; length_str = ss.str(); } library :: Track :: Track(struct ImportData *data, unsigned int lib, unsigned int artist, unsigned int album, unsigned int genre) : library_id(lib), artist_id(artist), album_id(album), genre_id(genre), track(data->track), last_year(data->last_year), last_month(data->last_month), last_day(data->last_day), play_count(data->count), length(data->length), title(data->title) { std::stringstream ss; unsigned int minutes, seconds; full_path = data->filepath; filepath = full_path.substr(library_db.at(library_id)->root_path.size() + 1); title_lower = filter :: to_lowercase(title); minutes = length / 60; seconds = length % 60; ss << minutes << ":"; if (seconds < 10) ss << "0"; ss << seconds; length_str = ss.str(); } const std::string library :: Track :: primary_key() { return full_path; } void library :: Track :: read(File &f) { f >> library_id >> artist_id >> album_id >> genre_id; f >> track >> last_year >> last_month >> last_day; f >> play_count >> length; length_str = f.getline(); title = f.getline(); filepath = f.getline(); title_lower = filter :: to_lowercase(title); full_path = library_db.at(library_id)->root_path + "/" + filepath; library_db.at(library_id)->size++; } void library :: Track :: write(File &f) { f << library_id << " " << artist_id << " " << album_id << " " << genre_id; f << " " << track << " " << last_year << " " << last_month << " " << last_day; f << " " << play_count << " " << length << " " << length_str << std :: endl; f << title << std :: endl; f << filepath; } /* * Internal library functions */ struct scan_info { unsigned int lib_id; std :: string path; }; static void do_scan_path(struct scan_info &); static void read_tags(unsigned int lib_id, const std :: string &path) { TagLib :: Tag *tag; TagLib :: AudioProperties *audio; TagLib :: FileRef ref(path.c_str(), true, TagLib :: AudioProperties :: Fast); unsigned int artist_id, album_id, genre_id, track_id; if (ref.isNull()) { print("ERROR: Could not read tags for file %s\n", path.c_str()); return; } tag = ref.tag(); audio = ref.audioProperties(); artist_id = artist_db.insert(library :: AGInfo(library :: DB_ARTIST, tag)); album_id = album_db.insert(library :: Album(tag, artist_id)); genre_id = genre_db.insert(library :: AGInfo(library :: DB_GENRE, tag)); track_id = track_db.insert(library :: Track(tag, audio, lib_id, artist_id, album_id, genre_id, path)); library_db.at(lib_id)->size++; filter::add(artist_db.at(artist_id)->name, track_id); filter::add(album_db.at(album_id)->name, track_id); filter::add(track_db.at(track_id)->title, track_id); get_callbacks()->on_library_track_add(track_id); } static bool process_path(unsigned int lib_id, const std :: string &dir, const std :: string &name) { struct scan_info scan; bool changed = false; std :: string path = dir + "/" + name; if (g_file_test(path.c_str(), G_FILE_TEST_IS_DIR) == true) { scan.lib_id = lib_id; scan.path = path; idle :: schedule (do_scan_path, scan); } else { if (track_db.find(path) != track_db.end()) { read_tags(lib_id, path); changed = true; } } return changed; } static void save_all_dbs() { artist_db.save(); album_db.save(); genre_db.save(); track_db.save(); } static void do_scan_path(struct scan_info &scan) { GDir *dir; const char *name; bool changed = false; dir = g_dir_open(scan.path.c_str(), 0, NULL); if (dir == NULL) return; name = g_dir_read_name(dir); while (name != NULL) { if (process_path(scan.lib_id, scan.path, name)) changed = true; name = g_dir_read_name(dir); } if (changed == true) { save_all_dbs(); get_callbacks()->on_library_update(scan.lib_id, &(*library_db.at(scan.lib_id))); } } static void do_validate_library(unsigned int &lib_id) { std :: string path; bool changed = false; if (track_db.size() == 0) return; Database::iterator it; for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) { if ((*it).library_id != lib_id) continue; path = library_db.at(lib_id)->root_path + "/" + (*it).filepath; if (g_file_test(path.c_str(), G_FILE_TEST_EXISTS) == false) { dprint("Removing file: %s\n", path.c_str()); track_db.remove(it - track_db.begin()); library_db.at(lib_id)->size--; changed = true; } } if (changed == true) get_callbacks()->on_library_update(lib_id, &(*library_db.at(lib_id))); } static void do_update_library(unsigned int lib_id) { struct scan_info scan = { lib_id, library_db.at(lib_id)->root_path }; idle :: schedule(do_validate_library, lib_id); idle :: schedule(do_scan_path, scan); } static void do_import_track(File &f, unsigned int lib_id) { struct ImportData data; std::string artist, album, genre; unsigned int artist_id, album_id, genre_id, track_id, year, banned, tmp; data.filepath = f.getline(); data.title = f.getline(); artist = f.getline(); album = f.getline(); f.getline(); /* comment */ genre = f.getline(); f.getline(); /* lenstr */ f >> tmp /* id */ >> year >> data.track >> data.count; f >> data.last_day >> data.last_month >> data.last_year >> data.length; f >> tmp >> tmp >>tmp >> banned; /* bitrate, sample, channels, banned */ f.getline(); /* get rest of line */ artist_id = artist_db.insert(library :: AGInfo(library :: DB_ARTIST, artist)); album_id = album_db.insert(library :: Album(album, year, artist_id)); genre_id = genre_db.insert(library :: AGInfo(library :: DB_GENRE, genre)); track_id = track_db.insert(library :: Track(&data, lib_id, artist_id, album_id, genre_id)); library_db.at(lib_id)->size++; filter::add(artist_db.at(artist_id)->name, track_id); filter::add(album_db.at(album_id)->name, track_id); filter::add(track_db.at(track_id)->title, track_id); get_callbacks()->on_library_track_add(track_id); if (banned == true) get_callbacks()->on_library_import_ban(track_id); } static void do_import_library(std::string &s) { unsigned int id, next_id, size; std::string path; bool enabled; File f(s, FILE_TYPE_LEGACY); print("Importing: %s\n", f.get_filepath()); f.open(OPEN_READ); if (f.get_version() != 2) { print("Version mismatch: %u != 2\n", f.get_version()); return; } path = f.getline(); f >> id >> enabled >> next_id >> size; /* Assign this path a new id */ if (library_db.find(path) != library_db.end()) { print("Library already contains path: %s, skipping\n", path.c_str()); return; } print("Adding path: %s\n", path.c_str()); id = library_db.insert(library :: Library(path, enabled)); get_callbacks()->on_library_add(id, &(*library_db.at(id))); library_db.save(); f.getline(); /* Get rest of line */ for (unsigned int i = 0; i < size; i++) do_import_track(f, id); save_all_dbs(); get_callbacks()->on_library_update(id, &(*library_db.at(id))); library :: update_path(id); } /* * API used by the GUI begins here */ void library :: init() { unsigned int i; album_db.load(); artist_db.load(); genre_db.load(); library_db.load(); track_db.load(); Database::iterator it; for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) { i = (it - track_db.begin()); filter::add(artist_db.at((*it).artist_id)->name, i); filter::add(album_db.at((*it).album_id)->name, i); filter::add((*it).title, i); if (library_db.at((*it).library_id)->enabled) get_callbacks()->on_library_track_add(i); } Database::iterator l_it; for (l_it = library_db.begin(); l_it != library_db.end(); l_it = library_db.next(l_it)) { i = l_it - library_db.begin(); get_callbacks()->on_library_add(i, &(*library_db.at(i))); } } void library :: add_path(const std::string &dir) { unsigned int id; if (g_file_test(dir.c_str(), G_FILE_TEST_IS_DIR) == false) throw -E_INVAL; if (library_db.find(dir) != library_db.end()) return; id = library_db.insert(library :: Library(dir, true)); library_db.save(); get_callbacks()->on_library_add(id, &(*library_db.at(id))); update_path(id); } void library :: del_path(unsigned int id) { Database::iterator it; for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) { if ((*it).library_id == id) { unsigned int track_id = it - track_db.begin(); get_callbacks()->on_library_track_del(track_id); track_db.remove(track_id); } } library_db.remove(id); track_db.save(); library_db.save(); } void library :: update_path(unsigned int id) { if (id > library_db.size()) return; if (library_db.at(id)->valid == false) return; do_update_library(id); } void library :: update_all() { Database::iterator it; for (it = library_db.begin(); it != library_db.end(); it = library_db.next(it)) update_path(it - library_db.begin()); } void library :: set_enabled(unsigned int id, bool enabled) { unsigned int t; Database::iterator it; library_db.at(id)->enabled = enabled; library_db.save(); for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) { if ((*it).library_id == id) { t = it - track_db.begin(); if (enabled) get_callbacks()->on_library_track_add(t); else get_callbacks()->on_library_track_del(t); } } } void library :: lookup(unsigned int id, library :: Song *song) { if (id >= track_db.actual_size()) throw -E_EXIST; song->track = &(*track_db.at(id)); if (song->track->valid == false) throw -E_EXIST; song->track_id = id; song->artist = &(*artist_db.at(song->track->artist_id)); song->album = &(*album_db.at(song->track->album_id)); song->genre = &(*genre_db.at(song->track->genre_id)); song->library = &(*library_db.at(song->track->library_id)); } library :: Library *library :: lookup_path(unsigned int id) { if (id >= library_db.actual_size()) throw -E_EXIST; if (library_db.at(id)->valid == false) throw -E_EXIST; return &(*library_db.at(id)); } void library :: import() { unsigned int i = 0; std::string name; do { std::stringstream ss; ss << i; name = ss.str(); File f(name, FILE_TYPE_LEGACY); if (f.exists() == false) break; idle :: schedule(do_import_library, name); ss.clear(); i++; } while (true); } void library :: track_played(unsigned int id) { time_t the_time = time(NULL); struct tm *now = localtime(&the_time); track_db.at(id)->play_count++; track_db.at(id)->last_day = now->tm_mday; track_db.at(id)->last_month = now->tm_mon + 1; track_db.at(id)->last_year = now->tm_year + 1900; track_db.save(); get_callbacks()->on_library_track_updated(id); } #ifdef CONFIG_TEST void library :: print_db(DB_Type type) { switch (type) { case DB_ALBUM: break; case DB_ARTIST: break; case DB_GENRE: break; case DB_LIBRARY: break; case DB_TRACK: break; } } void library :: reset() { } #endif /* CONFIG_TEST */