ocarina/lib/library.cpp

623 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 :: 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<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 = 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<Track>::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<Library>::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<Track>::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<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 */