e274d6399b
Whenever a Track is destructed, library->count is decremented. This means that even if tagging fails we need to increment library->count to keep this value consistent. Signed-off-by: Anna Schumaker <Anna@OcarinaProject.net>
418 lines
7.4 KiB
C++
418 lines
7.4 KiB
C++
/*
|
|
* Copyright 2014 (c) Anna Schumaker.
|
|
*/
|
|
|
|
#include <core/tags.h>
|
|
#include <core/filter.h>
|
|
#include <core/print.h>
|
|
|
|
#include <sstream>
|
|
#include <taglib/tag.h>
|
|
#include <taglib/fileref.h>
|
|
|
|
Database<Artist> artist_db("artist.db", true);
|
|
Database<Album> album_db("album.db", true);
|
|
Database<Genre> genre_db("genre.db", true);
|
|
Database<Library> library_db("library.db", true);
|
|
Database<Track> 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()
|
|
{
|
|
if (library)
|
|
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 <class T>
|
|
static T *find_or_insert(const T &tag, Database<T> &db)
|
|
{
|
|
T *ret = db.find(tag.primary_key());
|
|
if (!ret)
|
|
ret = db.insert(tag);
|
|
return ret;
|
|
}
|
|
|
|
bool Track :: tag()
|
|
{
|
|
TagLib :: Tag *tag;
|
|
TagLib :: AudioProperties *audio;
|
|
TagLib :: FileRef ref(path().c_str(), true, TagLib::AudioProperties::Fast);
|
|
|
|
library->count++;
|
|
if (ref.isNull()) {
|
|
print("WARNING: Could not read tags for file %s\n", path().c_str());
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
|
|
return true;
|
|
}
|
|
|
|
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()) {
|
|
remove_track(track->id);
|
|
track = NULL;
|
|
}
|
|
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<Track>::iterator it;
|
|
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(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<Track> &tagdb :: get_track_db()
|
|
{
|
|
return track_db;
|
|
}
|
|
|
|
Database<Library> &tagdb :: get_library_db()
|
|
{
|
|
return library_db;
|
|
}
|