diff --git a/readme.md b/readme.md index fbdb4ee..2dbd1fa 100644 --- a/readme.md +++ b/readme.md @@ -12,18 +12,18 @@ A Groove Music like music player. ## Changes since lastest release - add resource collection +- add tag caching - fix library hovering -- improve song transition +- improve start time - improve load time - improve cache performance +- improve song transition ## Things to do ### Internal - move library into music library - use a member query -- split utils but keep header -- cache all audio information - use QFileSystemWatcher ### Cosmetic diff --git a/src/core/audio.cpp b/src/core/audio.cpp index 1e29feb..519a353 100644 --- a/src/core/audio.cpp +++ b/src/core/audio.cpp @@ -26,6 +26,34 @@ Audio::Audio(const QString &path) : m_title = FileUtil::fileName(m_path); } +/* + * Constructor. Used to read audios from the sql database. + * + * :param path: path + * :param title: title + * :param artist: artist + * :param album: album + * :param genre: genre + * :param year: year + * :param length: length + * :param coverId: cover id + */ +Audio::Audio(const QString &path, const QString &title, const QString &artist, const QString &album, + const QString &genre, int year, int track, int length, int coverId) : + m_valid(true), + m_path(path), + m_title(title), + m_artist(artist), + m_album(album), + m_genre(genre), + m_year(year), + m_track(track), + m_length(length), + m_coverId(coverId) +{ + +} + /* * Destructor. */ diff --git a/src/core/audio.hpp b/src/core/audio.hpp index 7a3c536..1427cf2 100644 --- a/src/core/audio.hpp +++ b/src/core/audio.hpp @@ -22,6 +22,8 @@ class Audio { public: Audio(const QString &path); + Audio(const QString &path, const QString &title, const QString &artist, const QString &album, + const QString &genre, int year, int track, int length, int coverId); ~Audio(); bool isValid() const; diff --git a/src/core/cache.cpp b/src/core/cache.cpp index 39e7255..55aa017 100644 --- a/src/core/cache.cpp +++ b/src/core/cache.cpp @@ -6,13 +6,13 @@ */ Cache::Cache() { - if (QSqlDatabase::contains(dbName())) - return; - - QSqlDatabase::addDatabase("QSQLITE", dbName()); + if (!QSqlDatabase::contains(dbName())) + { + QSqlDatabase::addDatabase("QSQLITE", dbName()); - createCovers(); - createAudios(); + createCovers(); + createAudios(); + } } /* @@ -24,16 +24,89 @@ Cache::~Cache() } /* - * Inserts audio into cache. It adds the cover into the covers table and the - * path with a cover id into the audios table. No tags are stored inside the - * cache because TagLib is fast enough to reload tags at every startup. - * Also sets the cover id for later use. + * Loads an audio file from the cache. If it does not exist a nullptr will be + * returned instead. + * + * :param path: path + * :return: audio, nullptr at failure + */ +Audio * Cache::load(const QString &path) +{ + Audio *audio = nullptr; + + QSqlQuery query(db()); + query.prepare("SELECT * FROM audios WHERE path = :path"); + query.bindValue(":path", path); + + if (!query.exec()) + handleError(query); + + if (query.first()) + { + audio = new Audio( + query.value(0).toString(), + query.value(1).toString(), + query.value(2).toString(), + query.value(3).toString(), + query.value(4).toString(), + query.value(5).toInt(), + query.value(6).toInt(), + query.value(7).toInt(), + query.value(8).toInt() + ); + } + return audio; +} + +/* + * Inserts audio tags into the cache. + * + * :param audio: audio + * :return: success + */ +bool Cache::insertAudio(Audio *audio) +{ + QSqlQuery query(db()); + query.prepare( + "INSERT INTO audios VALUES (" + " :path," + " :title," + " :artist," + " :album," + " :genre," + " :year," + " :track," + " :length," + " :coverid" + ")" + ); + query.bindValue(":path", audio->path()); + query.bindValue(":title", audio->title()); + query.bindValue(":artist", audio->artist()); + query.bindValue(":album", audio->album()); + query.bindValue(":genre", audio->genre()); + query.bindValue(":year", audio->year()); + query.bindValue(":track", audio->track()); + query.bindValue(":length", audio->length(false)); + query.bindValue(":coverid", audio->coverId()); + + if (!query.exec()) + { + handleError(query); + return false; + } + return true; +} + +/* + * Inserts an audio cover into the cache. This assumes that the audio already + * has an entry in the audio table. * * :param audio: audio * :param size: cover size, default 200 * :return: success */ -bool Cache::insert(Audio *audio, int size) +bool Cache::insertCover(Audio *audio, int size) { int id = getOrInsertCover(audio->cover(size)); if (id == -1) @@ -42,7 +115,11 @@ bool Cache::insert(Audio *audio, int size) audio->setCoverId(id); QSqlQuery query(db()); - query.prepare("INSERT INTO audios VALUES (:path, :coverid)"); + query.prepare( + "UPDATE audios " + "SET coverid = :coverid " + "WHERE path = :path" + ); query.bindValue(":path", audio->path()); query.bindValue(":coverid", id); @@ -55,7 +132,9 @@ bool Cache::insert(Audio *audio, int size) } /* - * Checks if database contains audio. Also sets the cover id for later use. + * Checks if database contains audio. Also sets the cover id for later use. If + * an audio object has a valid cover id it definitely exists already because it + * only gets set inside the cache. * * :param audio: audio * :return: contains @@ -78,7 +157,11 @@ bool Cache::contains(Audio *audio) if (!query.first()) return false; - audio->setCoverId(query.value(0).toInt()); + int id = query.value(0).toInt(); + if (id == -1) + return false; + + audio->setCoverId(id); return true; } @@ -182,8 +265,14 @@ void Cache::createAudios() QString createAudios = "CREATE TABLE IF NOT EXISTS audios(" " path TEXT PRIMARY KEY," - " coverid INTEGER," - " FOREIGN KEY (coverid) REFERENCES covers(id)" + " title TEXT," + " artist TEXT," + " album TEXT," + " genre TEXT," + " year INTEGER," + " track INTEGER," + " length INTEGER," + " coverid INTEGER" ")"; QSqlQuery query(db()); diff --git a/src/core/cache.hpp b/src/core/cache.hpp index 18c5fa7..eb65a80 100644 --- a/src/core/cache.hpp +++ b/src/core/cache.hpp @@ -19,7 +19,11 @@ class Cache Cache(); ~Cache(); - bool insert(Audio *audio, int size = 200); + Audio * load(const QString &path); + + bool insertAudio(Audio *audio); + bool insertCover(Audio *audio, int size = 200); + bool contains(Audio *audio); QPixmap cover(Audio *audio, int size = 200); diff --git a/src/core/library.cpp b/src/core/library.cpp index 3e5d4f3..44ea98e 100644 --- a/src/core/library.cpp +++ b/src/core/library.cpp @@ -8,11 +8,12 @@ Library::Library(QObject *parent) : QObject(parent), m_sorted(false), - pm_audioPool(new ThreadPool(this)), - pm_cachePool(new ThreadPool(this)) + pm_audioLoader(new AudioLoader(this)), + pm_cacheBuilder(new CacheBuilder(this)) { - connect(pm_audioPool, SIGNAL(finished()), this, SLOT(onAudioPoolFinished())); - connect(pm_audioPool, SIGNAL(finished()), this, SIGNAL(loaded())); + connect(pm_audioLoader, SIGNAL(loaded(Audio *)), this, SLOT(insert(Audio *))); + connect(pm_audioLoader, SIGNAL(finished()), this, SLOT(onAudioLoaderFinished())); + connect(pm_audioLoader, SIGNAL(finished()), this, SIGNAL(loaded())); } /* @@ -89,14 +90,8 @@ Audios Library::audios() const */ void Library::load(const StringList &paths) { - QVector chunks = Util::chunk(uniqueFiles(paths), pm_audioPool->advised()); - for (const StringList &chunk : chunks) - { - AudioLoader *loader = new AudioLoader(chunk); - connect(loader, SIGNAL(loaded(Audio *)), this, SLOT(insert(Audio *))); - pm_audioPool->add(loader); - } - pm_audioPool->start(); + pm_audioLoader->setFiles(uniqueFiles(paths)); + pm_audioLoader->start(); } /* @@ -107,21 +102,19 @@ void Library::load(const StringList &paths) */ void Library::insert(Audio *audio) { - m_mutex.lock(); if (m_sorted) insertBinary(audio); else append(audio); - m_mutex.unlock(); } /* * Gets called when the library is loaded and creates the cache builder. */ -void Library::onAudioPoolFinished() +void Library::onAudioLoaderFinished() { - pm_cachePool->add(new CacheBuilder(m_audios)); - pm_cachePool->start(); + pm_cacheBuilder->setAudios(m_audios); + pm_cacheBuilder->start(); } /* diff --git a/src/core/library.hpp b/src/core/library.hpp index 115fd0d..7859f91 100644 --- a/src/core/library.hpp +++ b/src/core/library.hpp @@ -39,7 +39,7 @@ public slots: void inserted(Audio *, int); private slots: - void onAudioPoolFinished(); + void onAudioLoaderFinished(); private: int lowerBound(Audio *audio); @@ -50,8 +50,8 @@ private slots: bool m_sorted; Audios m_audios; - ThreadPool *pm_audioPool; - ThreadPool *pm_cachePool; + AudioLoader *pm_audioLoader; + CacheBuilder *pm_cacheBuilder; QMutex m_mutex; QSet m_paths; diff --git a/src/core/threading/abstractthread.cpp b/src/core/threading/abstractthread.cpp index 6d1b523..9d5ecd6 100644 --- a/src/core/threading/abstractthread.cpp +++ b/src/core/threading/abstractthread.cpp @@ -40,6 +40,7 @@ bool AbstractThread::isAbort() const void AbstractThread::abort() { m_abort = true; - quit(); - wait(); + + if (!wait(5000)) + Logger::log("AbstractThread: Could not abort within 5 seconds"); } diff --git a/src/core/threading/abstractthread.hpp b/src/core/threading/abstractthread.hpp index 4979eb6..e716fb3 100644 --- a/src/core/threading/abstractthread.hpp +++ b/src/core/threading/abstractthread.hpp @@ -3,6 +3,8 @@ #include +#include "logger.hpp" + class AbstractThread : public QThread { Q_OBJECT diff --git a/src/core/threading/audioloader.cpp b/src/core/threading/audioloader.cpp index 63a838b..a72f056 100644 --- a/src/core/threading/audioloader.cpp +++ b/src/core/threading/audioloader.cpp @@ -47,12 +47,19 @@ void AudioLoader::setFiles(const StringList &files) */ void AudioLoader::run() { + Cache cache; for (const QString &file : m_files) { if (isAbort()) return; - Audio *audio = new Audio(file); + Audio *audio = cache.load(file); + if (!audio) + { + audio = new Audio(file); + cache.insertAudio(audio); + } + if (audio->isValid()) emit loaded(audio); else diff --git a/src/core/threading/audioloader.hpp b/src/core/threading/audioloader.hpp index 0e152d3..05dd3ac 100644 --- a/src/core/threading/audioloader.hpp +++ b/src/core/threading/audioloader.hpp @@ -3,6 +3,7 @@ #include "abstractthread.hpp" #include "audio.hpp" +#include "cache.hpp" #include "logger.hpp" #include "types.hpp" #include "utils.hpp" diff --git a/src/core/threading/cachebuilder.cpp b/src/core/threading/cachebuilder.cpp index 5b51d92..6df7924 100644 --- a/src/core/threading/cachebuilder.cpp +++ b/src/core/threading/cachebuilder.cpp @@ -45,15 +45,19 @@ void CacheBuilder::setAudios(const Audios &audios) /* * Loads audio covers. */ +#include void CacheBuilder::run() { Cache cache; for (Audio *audio : m_audios) { if (isAbort()) + { + qDebug() << "aborted :|"; return; + } if (!cache.contains(audio)) - cache.insert(audio); + cache.insertCover(audio); } }