ocarina/lib/library.cpp
Anna Schumaker 8d29ce7169 library: Use track id directly
Calculating through the iterator may lead to unexpected problems.  We
already know the id of every database entry, so we might as well use it
directly.

Signed-off-by: Anna Schumaker <anna@ocarinaproject.net>
2014-04-09 19:28:04 -04:00

634 lines
14 KiB
C++

/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <callback.h>
#include <filter.h>
#include <idle.h>
#include <library.h>
#include <print.h>
#include <glib.h>
#include <sstream>
#include <time.h>
static Database<library :: Album> album_db("album.db", false);
static Database<library :: AGInfo> artist_db("artist.db", false);
static Database<library :: AGInfo> genre_db("genre.db", false);
static Database<library :: Track> track_db("track.db", false);
static Database<library :: Library> 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 :: 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 :: 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 :: lowercase(name);
}
void library :: AGInfo :: write(File &f)
{
f << name;
}
/*
* library :: Album: Album tag information
*/
library :: Album :: Album()
: name(""), year(0)
{
}
library :: Album :: Album(TagLib :: Tag *tag, unsigned int artist)
: name(tag->album().stripWhiteSpace().to8Bit(true)),
year(tag->year())
{
name_lower = filter :: lowercase(name);
}
library :: Album :: Album(const std::string &str, unsigned int yr, unsigned int artist)
: name(str), year(yr)
{
name_lower = filter :: lowercase(name);
}
const std::string library :: Album :: primary_key()
{
std::stringstream ss;
ss << name << "." << year;
return ss.str();
}
void library :: Album :: read(File &f)
{
f >> year;
name = f.getline();
name_lower = filter :: lowercase(name);
}
void library :: Album :: write(File &f)
{
f << 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 :: 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 :: 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)
{
std::stringstream ss;
unsigned int minutes, seconds;
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();
title_lower = filter :: lowercase(title);
full_path = library_db.at(library_id)->root_path + "/" + filepath;
library_db.at(library_id)->size++;
minutes = length / 60;
seconds = length % 60;
ss << minutes << ":";
if (seconds < 10)
ss << "0";
ss << seconds;
length_str = ss.str();
}
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 << " "; // << 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));
if (track_db.at(track_id)->valid == false)
return;
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<library :: Track>::iterator it;
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
if ((*it).library_id != lib_id)
continue;
path = it->primary_key();
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)));
save_all_dbs();
}
}
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()
{
album_db.load();
artist_db.load();
genre_db.load();
library_db.load();
track_db.load();
Database<Track>::iterator it;
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
filter::add(artist_db.at((*it).artist_id)->name, it->id);
filter::add(album_db.at((*it).album_id)->name, it->id);
filter::add((*it).title, it->id);
if (library_db.at((*it).library_id)->enabled)
get_callbacks()->on_library_track_add(it->id);
}
Database<Library>::iterator l_it;
for (l_it = library_db.begin(); l_it != library_db.end(); l_it = library_db.next(l_it))
get_callbacks()->on_library_add(l_it->id, &(*library_db.at(l_it->id)));
}
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<Track>::iterator it;
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
if ((*it).library_id == id) {
get_callbacks()->on_library_track_del(it->id);
track_db.remove(it->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<Library>::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<Track>::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 */