mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-30 15:32:37 +00:00
Added SDK support for plugins that can index their own audio content.
- Added IIndexerSource interface: plugins will be able to implement this interface to add tracks to the library that will be indexed and maintained like all other tracks - Added IIndexerWriter interface: IIndexerSource plugins will use this interface to add/remove/update track info with the main app - Added IIndexerNotifier: interface used to notify the app that it needs to be re-indexed. - Added "source_id" , "external_id", and "visible" column to the tracks table, with appropriate indexes. - Updated all queries to take "source_id" and "visible" into account - Extracted TrackMetadataQuery from TrackList. Never should have been there to begin with, but was due to some technical limitations that no longer exist. - Fixed a really old indexer bug where the reported number of file scanned was not accurate. Strange this wasn't noticed before. - Added "SetIndexerNotifier" injection method - Fixed a bug in TrackMetadataQuery -- it was unnecessarily relying upon the paths table, which was causing query errors when the table was empty. - Use a cache for local file paths instead of requiring an async round trip when adding/removing - Remove use of manual "ANALYZE", it was causing strange performance degradation issues. Instead, move to a set of PRAGMAs that instructs sqlite to run these optimizations when necessary. - Updated ::GetInt32 and ::GetUint32 return types to be explicit as to play more nicely with clang -- may as well, we're updating the SDK version anyway.
This commit is contained in:
parent
c5e57094d5
commit
2dac91c429
@ -28,6 +28,7 @@ set(CORE_SOURCES
|
||||
./library/query/local/NowPlayingTrackListQuery.cpp
|
||||
./library/query/local/SavePlaylistQuery.cpp
|
||||
./library/query/local/SearchTrackListQuery.cpp
|
||||
./library/query/local/TrackMetadataQuery.cpp
|
||||
./library/metadata/MetadataMap.cpp
|
||||
./library/metadata/MetadataMapList.cpp
|
||||
./library/track/IndexerTrack.cpp
|
||||
|
@ -113,6 +113,7 @@
|
||||
<ClCompile Include="library\query\local\NowPlayingTrackListQuery.cpp" />
|
||||
<ClCompile Include="library\query\local\SavePlaylistQuery.cpp" />
|
||||
<ClCompile Include="library\query\local\SearchTrackListQuery.cpp" />
|
||||
<ClCompile Include="library\query\local\TrackMetadataQuery.cpp" />
|
||||
<ClCompile Include="library\track\IndexerTrack.cpp" />
|
||||
<ClCompile Include="library\track\LibraryTrack.cpp" />
|
||||
<ClCompile Include="library\track\RetainedTrack.cpp" />
|
||||
@ -170,6 +171,7 @@
|
||||
<ClInclude Include="library\query\local\SearchTrackListQuery.h" />
|
||||
<ClInclude Include="library\query\local\TrackListQueryBase.h" />
|
||||
<ClInclude Include="Library\query\local\LocalQueryBase.h" />
|
||||
<ClInclude Include="library\query\local\TrackMetadataQuery.h" />
|
||||
<ClInclude Include="library\track\IndexerTrack.h" />
|
||||
<ClInclude Include="library\track\LibraryTrack.h" />
|
||||
<ClInclude Include="library\track\RetainedTrack.h" />
|
||||
@ -192,6 +194,9 @@
|
||||
<ClInclude Include="sdk\IDSP.h" />
|
||||
<ClInclude Include="sdk\IDataStream.h" />
|
||||
<ClInclude Include="sdk\IDataStreamFactory.h" />
|
||||
<ClInclude Include="sdk\IIndexerNotifier.h" />
|
||||
<ClInclude Include="sdk\IIndexerSource.h" />
|
||||
<ClInclude Include="sdk\IIndexerWriter.h" />
|
||||
<ClInclude Include="sdk\IMetadataMap.h" />
|
||||
<ClInclude Include="sdk\IMetadataMapList.h" />
|
||||
<ClInclude Include="sdk\IMetadataReader.h" />
|
||||
@ -211,6 +216,7 @@
|
||||
<ClInclude Include="audio\Stream.h" />
|
||||
<ClInclude Include="sdk\IPreferences.h" />
|
||||
<ClInclude Include="sdk\IRetainedTrack.h" />
|
||||
<ClInclude Include="sdk\IRetainedTrackWriter.h" />
|
||||
<ClInclude Include="sdk\ISimpleDataProvider.h" />
|
||||
<ClInclude Include="sdk\ISpectrumVisualizer.h" />
|
||||
<ClInclude Include="sdk\ITrack.h" />
|
||||
|
@ -178,6 +178,9 @@
|
||||
<ClCompile Include="i18n\Locale.cpp">
|
||||
<Filter>src\i18n</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="library\query\local\TrackMetadataQuery.cpp">
|
||||
<Filter>src\library\query\local</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.hpp">
|
||||
@ -435,5 +438,20 @@
|
||||
<ClInclude Include="i18n\Locale.h">
|
||||
<Filter>src\i18n</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk\IIndexerSource.h">
|
||||
<Filter>src\sdk</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk\IRetainedTrackWriter.h">
|
||||
<Filter>src\sdk</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="library\query\local\TrackMetadataQuery.h">
|
||||
<Filter>src\library\query\local</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk\IIndexerWriter.h">
|
||||
<Filter>src\sdk</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk\IIndexerNotifier.h">
|
||||
<Filter>src\sdk</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -54,19 +54,23 @@ Connection::~Connection() {
|
||||
}
|
||||
|
||||
int Connection::Open(const char *database, unsigned int options, unsigned int cache) {
|
||||
int error;
|
||||
if (!this->connection) {
|
||||
int error;
|
||||
|
||||
#ifdef UTF_WIDECHAR
|
||||
error = sqlite3_open16(database, &this->connection);
|
||||
error = sqlite3_open16(database, &this->connection);
|
||||
#else
|
||||
error = sqlite3_open(database, &this->connection);
|
||||
error = sqlite3_open(database, &this->connection);
|
||||
#endif
|
||||
|
||||
if (error == SQLITE_OK) {
|
||||
this->Initialize(cache);
|
||||
if (error == SQLITE_OK) {
|
||||
this->Initialize(cache);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
return error;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int Connection::Open(const std::string &database, unsigned int options, unsigned int cache) {
|
||||
@ -109,7 +113,7 @@ int Connection::Execute(const char* sql) {
|
||||
}
|
||||
|
||||
int error = this->StepStatement(stmt);
|
||||
if (error != SQLITE_OK && error != SQLITE_DONE){
|
||||
if (error != SQLITE_OK && error != SQLITE_DONE) {
|
||||
sqlite3_finalize(stmt);
|
||||
return Error;
|
||||
}
|
||||
@ -150,14 +154,19 @@ void Connection::Checkpoint() {
|
||||
sqlite3_wal_checkpoint(this->connection, nullptr);
|
||||
}
|
||||
|
||||
int Connection::LastInsertedId(){
|
||||
int Connection::LastInsertedId() {
|
||||
return (int) sqlite3_last_insert_rowid(this->connection);
|
||||
}
|
||||
|
||||
int Connection::LastModifiedRowCount() {
|
||||
return (int) sqlite3_changes(this->connection);
|
||||
}
|
||||
|
||||
void Connection::Initialize(unsigned int cache) {
|
||||
sqlite3_busy_timeout(this->connection, 10000);
|
||||
|
||||
sqlite3_exec(this->connection, "PRAGMA synchronous=OFF", nullptr, nullptr, nullptr); // Not a critical DB. Sync set to OFF
|
||||
sqlite3_exec(this->connection, "PRAGMA optimize", nullptr, nullptr, nullptr); // Optimize the database when applicable
|
||||
sqlite3_exec(this->connection, "PRAGMA synchronous=NORMAL", nullptr, nullptr, nullptr); // NORMAL useful for auto-checkpointing with WAL
|
||||
sqlite3_exec(this->connection, "PRAGMA page_size=4096", nullptr, nullptr, nullptr); // According to windows standard page size
|
||||
sqlite3_exec(this->connection, "PRAGMA auto_vacuum=0", nullptr, nullptr, nullptr); // No autovaccum.
|
||||
sqlite3_exec(this->connection, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr); // Allow reading while writing (write-ahead-logging)
|
||||
|
@ -66,6 +66,7 @@ namespace musik { namespace core { namespace db {
|
||||
int Execute(const char* sql);
|
||||
int Execute(const wchar_t* sql);
|
||||
int LastInsertedId();
|
||||
int LastModifiedRowCount();
|
||||
|
||||
void Interrupt();
|
||||
void Checkpoint();
|
||||
|
@ -78,7 +78,7 @@ void ScopedTransaction::End() {
|
||||
}
|
||||
else {
|
||||
this->connection->Execute("COMMIT TRANSACTION");
|
||||
this->connection->Checkpoint();
|
||||
//this->connection->Checkpoint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,15 +42,12 @@ using namespace musik::core::db;
|
||||
|
||||
Statement::Statement(const char* sql, Connection &connection)
|
||||
: connection(&connection)
|
||||
, stmt(nullptr) {
|
||||
, stmt(nullptr)
|
||||
, modifiedRows(0) {
|
||||
std::unique_lock<std::mutex> lock(connection.mutex);
|
||||
|
||||
int err = sqlite3_prepare_v2(
|
||||
sqlite3_prepare_v2(
|
||||
this->connection->connection, sql, -1, &this->stmt, nullptr);
|
||||
|
||||
if (err!=SQLITE_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Statement::Statement(Connection &connection)
|
||||
@ -59,11 +56,11 @@ Statement::Statement(Connection &connection)
|
||||
}
|
||||
|
||||
Statement::~Statement() {
|
||||
int err = sqlite3_finalize(this->stmt);
|
||||
sqlite3_finalize(this->stmt);
|
||||
}
|
||||
|
||||
void Statement::Reset() {
|
||||
int err = sqlite3_reset(this->stmt);
|
||||
sqlite3_reset(this->stmt);
|
||||
}
|
||||
|
||||
void Statement::UnbindAll() {
|
||||
@ -71,10 +68,16 @@ void Statement::UnbindAll() {
|
||||
}
|
||||
|
||||
int Statement::Step() {
|
||||
return this->connection->StepStatement(this->stmt);
|
||||
int result = this->connection->StepStatement(this->stmt);
|
||||
|
||||
if (result == SQLITE_OK) {
|
||||
this->modifiedRows = this->connection->LastModifiedRowCount();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Statement::BindInt(int position,int bindInt) {
|
||||
void Statement::BindInt(int position, int bindInt) {
|
||||
sqlite3_bind_int(this->stmt, position + 1, bindInt);
|
||||
}
|
||||
|
||||
@ -91,7 +94,7 @@ void Statement::BindText(int position, const char* bindText) {
|
||||
SQLITE_STATIC);
|
||||
}
|
||||
|
||||
void Statement::BindText(int position ,const std::string &bindText) {
|
||||
void Statement::BindText(int position, const std::string &bindText) {
|
||||
sqlite3_bind_text(
|
||||
this->stmt, position + 1,
|
||||
bindText.c_str(),
|
||||
@ -99,7 +102,7 @@ void Statement::BindText(int position ,const std::string &bindText) {
|
||||
SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
void Statement::BindTextW(int position,const wchar_t* bindText){
|
||||
void Statement::BindTextW(int position, const wchar_t* bindText) {
|
||||
sqlite3_bind_text16(
|
||||
this->stmt,
|
||||
position + 1,
|
||||
@ -108,7 +111,7 @@ void Statement::BindTextW(int position,const wchar_t* bindText){
|
||||
SQLITE_STATIC);
|
||||
}
|
||||
|
||||
void Statement::BindTextW(int position,const std::wstring &bindText){
|
||||
void Statement::BindTextW(int position, const std::wstring &bindText) {
|
||||
sqlite3_bind_text16(
|
||||
this->stmt,
|
||||
position + 1,
|
||||
@ -117,6 +120,10 @@ void Statement::BindTextW(int position,const std::wstring &bindText){
|
||||
SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
void Statement::BindNull(int position) {
|
||||
sqlite3_bind_null(this->stmt, position + 1);
|
||||
}
|
||||
|
||||
int Statement::ColumnInt(int column) {
|
||||
return sqlite3_column_int(this->stmt, column);
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ namespace musik { namespace core { namespace db {
|
||||
void BindText(int position, const std::string &bindText);
|
||||
void BindTextW(int position, const wchar_t* bindText);
|
||||
void BindTextW(int position, const std::wstring &bindText);
|
||||
void BindNull(int position);
|
||||
|
||||
int ColumnInt(int column);
|
||||
uint64 ColumnInt64(int column);
|
||||
@ -74,6 +75,7 @@ namespace musik { namespace core { namespace db {
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
Connection *connection;
|
||||
int modifiedRows;
|
||||
};
|
||||
|
||||
} } }
|
||||
|
@ -43,7 +43,6 @@ namespace musik { namespace core {
|
||||
public:
|
||||
sigslot::signal0<> Started;
|
||||
sigslot::signal1<int> Finished;
|
||||
sigslot::signal0<> PathsUpdated;
|
||||
sigslot::signal1<int> Progress;
|
||||
|
||||
enum State {
|
||||
@ -51,12 +50,17 @@ namespace musik { namespace core {
|
||||
StateIndexing
|
||||
};
|
||||
|
||||
virtual ~IIndexer() { }
|
||||
enum class SyncType{
|
||||
All,
|
||||
Local,
|
||||
Sources,
|
||||
};
|
||||
|
||||
virtual ~IIndexer() { }
|
||||
virtual void AddPath(const std::string& path) = 0;
|
||||
virtual void RemovePath(const std::string& path) = 0;
|
||||
virtual void GetPaths(std::vector<std::string>& paths) = 0;
|
||||
virtual void Synchronize(bool restart = false) = 0;
|
||||
virtual void Schedule(SyncType type) = 0;
|
||||
virtual State GetState() = 0;
|
||||
};
|
||||
} }
|
||||
|
@ -41,18 +41,23 @@
|
||||
#include <core/config.h>
|
||||
#include <core/library/track/IndexerTrack.h>
|
||||
#include <core/library/track/LibraryTrack.h>
|
||||
#include <core/library/track/RetainedTrack.h>
|
||||
#include <core/library/LocalLibraryConstants.h>
|
||||
#include <core/db/Connection.h>
|
||||
#include <core/db/Statement.h>
|
||||
#include <core/plugin/PluginFactory.h>
|
||||
#include <core/support/Preferences.h>
|
||||
#include <core/support/PreferenceKeys.h>
|
||||
#include <core/sdk/IAnalyzer.h>
|
||||
#include <core/sdk/IIndexerSource.h>
|
||||
#include <core/audio/Stream.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/thread/xtime.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#define MULTI_THREADED_INDEXER 1
|
||||
#define MULTI_THREADED_INDEXER 0
|
||||
#define STRESS_TEST_DB 0
|
||||
|
||||
static const std::string TAG = "Indexer";
|
||||
@ -62,6 +67,7 @@ static const size_t TRANSACTION_INTERVAL = 300;
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::sdk;
|
||||
using namespace musik::core::audio;
|
||||
using namespace musik::core::library;
|
||||
|
||||
using Thread = std::unique_ptr<boost::thread>;
|
||||
|
||||
@ -82,7 +88,6 @@ static std::string normalizePath(const std::string& path) {
|
||||
|
||||
Indexer::Indexer(const std::string& libraryPath, const std::string& dbFilename)
|
||||
: thread(nullptr)
|
||||
, restart(false)
|
||||
, filesSaved(0)
|
||||
, exit(false)
|
||||
, state(StateIdle)
|
||||
@ -91,6 +96,13 @@ Indexer::Indexer(const std::string& libraryPath, const std::string& dbFilename)
|
||||
this->dbFilename = dbFilename;
|
||||
this->libraryPath = libraryPath;
|
||||
this->thread = new boost::thread(boost::bind(&Indexer::ThreadLoop, this));
|
||||
|
||||
db::Connection connection;
|
||||
connection.Open(this->dbFilename.c_str());
|
||||
db::Statement stmt("SELECT path FROM paths ORDER BY id", connection);
|
||||
while (stmt.Step() == db::Row) {
|
||||
this->paths.push_back(stmt.ColumnText(0));
|
||||
}
|
||||
}
|
||||
|
||||
Indexer::~Indexer() {
|
||||
@ -107,9 +119,25 @@ Indexer::~Indexer() {
|
||||
}
|
||||
}
|
||||
|
||||
void Indexer::Synchronize(bool restart) {
|
||||
void Indexer::Schedule(SyncType type) {
|
||||
this->Schedule(type, nullptr);
|
||||
}
|
||||
|
||||
void Indexer::Schedule(SyncType type, IIndexerSource* source) {
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
this->restart = restart;
|
||||
|
||||
int sourceId = source ? source->SourceId() : 0;
|
||||
for (SyncContext& context : this->syncQueue) {
|
||||
if (context.type == type && context.sourceId == sourceId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SyncContext context;
|
||||
context.type = type;
|
||||
context.sourceId = sourceId;
|
||||
syncQueue.push_back(context);
|
||||
|
||||
this->waitCondition.notify_all();
|
||||
}
|
||||
|
||||
@ -120,10 +148,13 @@ void Indexer::AddPath(const std::string& path) {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
|
||||
if (std::find(this->paths.begin(), this->paths.end(), path) == this->paths.end()) {
|
||||
this->paths.push_back(path);
|
||||
}
|
||||
|
||||
this->addRemoveQueue.push_back(context);
|
||||
}
|
||||
|
||||
this->Synchronize(true);
|
||||
}
|
||||
|
||||
void Indexer::RemovePath(const std::string& path) {
|
||||
@ -133,16 +164,22 @@ void Indexer::RemovePath(const std::string& path) {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
|
||||
auto it = std::find(this->paths.begin(), this->paths.end(), path);
|
||||
if (it != this->paths.end()) {
|
||||
this->paths.erase(it);
|
||||
}
|
||||
|
||||
this->addRemoveQueue.push_back(context);
|
||||
}
|
||||
|
||||
this->Synchronize(true);
|
||||
}
|
||||
|
||||
void Indexer::SynchronizeInternal(boost::asio::io_service* io) {
|
||||
/* load all of the metadata (tag) reader plugins */
|
||||
void Indexer::Synchronize(const SyncContext& context, boost::asio::io_service* io) {
|
||||
/* load plugins required by the indexer (metadata readers, decoder
|
||||
factories, and indexer sources) */
|
||||
typedef PluginFactory::DestroyDeleter<IMetadataReader> MetadataDeleter;
|
||||
typedef PluginFactory::DestroyDeleter<IDecoderFactory> DecoderDeleter;
|
||||
typedef PluginFactory::DestroyDeleter<IIndexerSource> SourceDeleter;
|
||||
|
||||
this->metadataReaders = PluginFactory::Instance()
|
||||
.QueryInterface<IMetadataReader, MetadataDeleter>("GetMetadataReader");
|
||||
@ -150,14 +187,42 @@ void Indexer::SynchronizeInternal(boost::asio::io_service* io) {
|
||||
this->audioDecoders = PluginFactory::Instance()
|
||||
.QueryInterface<IDecoderFactory, DecoderDeleter>("GetDecoderFactory");
|
||||
|
||||
this->sources = PluginFactory::Instance()
|
||||
.QueryInterface<IIndexerSource, SourceDeleter>("GetIndexerSource");
|
||||
|
||||
this->ProcessAddRemoveQueue();
|
||||
|
||||
/* get sync paths and ids from the db */
|
||||
this->filesSaved = 0;
|
||||
|
||||
std::vector<std::string> paths;
|
||||
std::vector<DBID> pathIds;
|
||||
auto type = context.type;
|
||||
auto sourceId = context.sourceId;
|
||||
|
||||
{
|
||||
/* process ALL IIndexerSource plugins, if applicable */
|
||||
|
||||
if (type == SyncType::All || (type == SyncType::Sources && sourceId == 0)) {
|
||||
for (auto it : this->sources) {
|
||||
this->SyncSource(it.get());
|
||||
this->trackTransaction->CommitAndRestart();
|
||||
}
|
||||
}
|
||||
|
||||
/* otherwise, we may have just been asked to index a single one... */
|
||||
|
||||
else if (type == SyncType::Sources && sourceId != 0) {
|
||||
for (auto it : this->sources) {
|
||||
if (it->SourceId() == sourceId) {
|
||||
this->SyncSource(it.get());
|
||||
this->trackTransaction->CommitAndRestart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* process local files */
|
||||
if (type == SyncType::All || type == SyncType::Local) {
|
||||
std::vector<std::string> paths;
|
||||
std::vector<DBID> pathIds;
|
||||
|
||||
/* resolve all the path and path ids (required for local files */
|
||||
db::Statement stmt("SELECT id, path FROM paths", this->dbConnection);
|
||||
|
||||
while (stmt.Step() == db::Row) {
|
||||
@ -174,52 +239,49 @@ void Indexer::SynchronizeInternal(boost::asio::io_service* io) {
|
||||
catch(...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* add new files */
|
||||
this->filesSaved = 0;
|
||||
/* read metadata from the files */
|
||||
|
||||
for(std::size_t i = 0; i < paths.size(); ++i) {
|
||||
this->trackTransaction.reset(new db::ScopedTransaction(this->dbConnection));
|
||||
std::string path = paths[i];
|
||||
this->SyncDirectory(io, path, path, pathIds[i]);
|
||||
}
|
||||
for (std::size_t i = 0; i < paths.size(); ++i) {
|
||||
this->SyncDirectory(io, paths[i], paths[i], pathIds[i]);
|
||||
}
|
||||
|
||||
/* close any pending transaction */
|
||||
|
||||
if (this->trackTransaction) {
|
||||
this->trackTransaction->CommitAndRestart();
|
||||
this->trackTransaction.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Indexer::FinalizeSync() {
|
||||
void Indexer::FinalizeSync(const SyncContext& context) {
|
||||
/* remove undesired entries from db (files themselves will remain) */
|
||||
musik::debug::info(TAG, "cleanup 1/2");
|
||||
|
||||
if (!this->Restarted() && !this->Exited()) {
|
||||
this->SyncDelete();
|
||||
auto type = context.type;
|
||||
|
||||
if (type != SyncType::Sources) {
|
||||
if (!this->Exited()) {
|
||||
this->SyncDelete();
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup -- remove stale artists, albums, genres, etc */
|
||||
musik::debug::info(TAG, "cleanup 2/2");
|
||||
|
||||
if (!this->Restarted() && !this->Exited()) {
|
||||
if (!this->Exited()) {
|
||||
this->SyncCleanup();
|
||||
}
|
||||
|
||||
/* optimize and sort */
|
||||
musik::debug::info(TAG, "optimizing");
|
||||
|
||||
if (!this->Restarted() && !this->Exited()) {
|
||||
if (!this->Exited()) {
|
||||
this->SyncOptimize();
|
||||
}
|
||||
|
||||
/* notify observers */
|
||||
this->Progress(this->filesSaved);
|
||||
|
||||
/* unload reader DLLs*/
|
||||
this->metadataReaders.clear();
|
||||
|
||||
/* run analyzers. */
|
||||
this->RunAnalyzers();
|
||||
|
||||
this->state = StateIdle;
|
||||
@ -276,7 +338,6 @@ void Indexer::ReadMetadataFromFile(
|
||||
if (saveToDb) {
|
||||
track.SetValue("path_id", pathId.c_str());
|
||||
track.Save(this->dbConnection, this->libraryPath);
|
||||
this->filesSaved++;
|
||||
|
||||
#if STRESS_TEST_DB != 0
|
||||
#define INC(track, key, x) \
|
||||
@ -302,7 +363,7 @@ void Indexer::ReadMetadataFromFile(
|
||||
|
||||
++this->filesSaved;
|
||||
|
||||
#ifdef MULTI_THREADED_INDEXER
|
||||
#if MULTI_THREADED_INDEXER
|
||||
this->readSemaphore.post();
|
||||
#endif
|
||||
}
|
||||
@ -313,7 +374,7 @@ void Indexer::SyncDirectory(
|
||||
const std::string ¤tPath,
|
||||
DBID pathId)
|
||||
{
|
||||
if (this->Exited() || this->Restarted()) {
|
||||
if (this->Exited()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -334,12 +395,9 @@ void Indexer::SyncDirectory(
|
||||
std::string pathIdStr = boost::lexical_cast<std::string>(pathId);
|
||||
std::vector<Thread> threads;
|
||||
|
||||
for( ; file != end && !this->Exited() && !this->Restarted(); file++) {
|
||||
for( ; file != end && !this->Exited(); file++) {
|
||||
if (this->filesSaved > TRANSACTION_INTERVAL) {
|
||||
if (this->trackTransaction) {
|
||||
this->trackTransaction->CommitAndRestart();
|
||||
}
|
||||
|
||||
this->trackTransaction->CommitAndRestart();
|
||||
this->Progress(this->filesSaved);
|
||||
this->filesSaved = 0;
|
||||
}
|
||||
@ -370,6 +428,35 @@ void Indexer::SyncDirectory(
|
||||
#undef WAIT_FOR_ACTIVE
|
||||
}
|
||||
|
||||
void Indexer::SyncSource(IIndexerSource* source) {
|
||||
if (source->SourceId() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
source->OnBeforeScan();
|
||||
|
||||
/* first allow the source to update metadata for any tracks that it
|
||||
previously indexed. */
|
||||
{
|
||||
db::Statement tracks(
|
||||
"SELECT id, filename, external_id FROM tracks WHERE source_id=? ORDER BY id",
|
||||
this->dbConnection);
|
||||
|
||||
tracks.BindInt(0, source->SourceId());
|
||||
while (tracks.Step() == db::Row) {
|
||||
TrackPtr track(new IndexerTrack(tracks.ColumnInt(0)));
|
||||
track->SetValue(constants::Track::FILENAME, tracks.ColumnText(1));
|
||||
source->Scan(this, new RetainedTrackWriter(track), tracks.ColumnText(2));
|
||||
}
|
||||
}
|
||||
|
||||
/* now tell it to do a wide-open scan. it can use this opportunity to
|
||||
remove old tracks, or add new ones. */
|
||||
source->Scan(this);
|
||||
|
||||
source->OnAfterScan();
|
||||
}
|
||||
|
||||
void Indexer::ThreadLoop() {
|
||||
boost::filesystem::path thumbPath(this->libraryPath + "thumbs/");
|
||||
|
||||
@ -377,74 +464,67 @@ void Indexer::ThreadLoop() {
|
||||
boost::filesystem::create_directories(thumbPath);
|
||||
}
|
||||
|
||||
bool firstTime = true; /* through the loop */
|
||||
|
||||
while (!this->Exited()) {
|
||||
this->restart = false;
|
||||
|
||||
if(!firstTime || (firstTime && prefs->GetBool(prefs::keys::SyncOnStartup, true))) { /* first time through the loop skips this */
|
||||
this->state = StateIndexing;
|
||||
this->Started();
|
||||
|
||||
this->dbConnection.Open(this->dbFilename.c_str(), 0); /* ensure the db is open */
|
||||
|
||||
#ifdef MULTI_THREADED_INDEXER
|
||||
boost::asio::io_service io;
|
||||
boost::thread_group threadPool;
|
||||
boost::asio::io_service::work work(io);
|
||||
|
||||
/* initialize the thread pool -- we'll use this to index tracks in parallel. */
|
||||
int threadCount = prefs->GetInt(prefs::keys::MaxTagReadThreads, MAX_THREADS);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threadPool.create_thread(boost::bind(&boost::asio::io_service::run, &io));
|
||||
while (true) {
|
||||
/* wait for some work. */
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
while (!this->exit && this->syncQueue.size() == 0) {
|
||||
this->waitCondition.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
this->SynchronizeInternal(&io);
|
||||
if (this->exit) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* done with sync, remove all the threads in the pool to free resources. they'll
|
||||
be re-created later if we index again. */
|
||||
io.stop();
|
||||
threadPool.join_all();
|
||||
SyncContext context = this->syncQueue.front();
|
||||
this->syncQueue.pop_front();
|
||||
|
||||
this->state = StateIndexing;
|
||||
this->Started();
|
||||
|
||||
this->dbConnection.Open(this->dbFilename.c_str(), 0);
|
||||
this->trackTransaction.reset(new db::ScopedTransaction(this->dbConnection));
|
||||
|
||||
#if MULTI_THREADED_INDEXER
|
||||
boost::asio::io_service io;
|
||||
boost::thread_group threadPool;
|
||||
boost::asio::io_service::work work(io);
|
||||
|
||||
/* initialize the thread pool -- we'll use this to index tracks in parallel. */
|
||||
int threadCount = prefs->GetInt(prefs::keys::MaxTagReadThreads, MAX_THREADS);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
threadPool.create_thread(boost::bind(&boost::asio::io_service::run, &io));
|
||||
}
|
||||
|
||||
this->Synchronize(context, &io);
|
||||
|
||||
/* done with sync, remove all the threads in the pool to free resources. they'll
|
||||
be re-created later if we index again. */
|
||||
io.stop();
|
||||
threadPool.join_all();
|
||||
#else
|
||||
this->SynchronizeInternal(nullptr);
|
||||
this->Synchronize(context, nullptr);
|
||||
#endif
|
||||
|
||||
this->FinalizeSync();
|
||||
this->dbConnection.Close(); /* TODO: raii */
|
||||
this->FinalizeSync(context);
|
||||
|
||||
if (!restart) {
|
||||
this->Finished(this->filesSaved);
|
||||
}
|
||||
this->trackTransaction.reset();
|
||||
|
||||
musik::debug::info(TAG, "done!");
|
||||
} /* end skip */
|
||||
this->dbConnection.Close();
|
||||
|
||||
firstTime = false;
|
||||
|
||||
/* sleep before we try again; disabled by default */
|
||||
int waitTime = prefs->GetInt(prefs::keys::AutoSyncIntervalMillis, 0);
|
||||
|
||||
if (waitTime > 0) {
|
||||
boost::xtime waitTimeout;
|
||||
boost::xtime_get(&waitTimeout, boost::TIME_UTC_);
|
||||
waitTimeout.sec += waitTime;
|
||||
|
||||
if (!this->Restarted()) {
|
||||
this->Wait(waitTimeout);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!this->Restarted()) {
|
||||
this->Wait(); /* zzz */
|
||||
}
|
||||
if (!this->Exited()) {
|
||||
this->Finished(this->filesSaved);
|
||||
}
|
||||
|
||||
musik::debug::info(TAG, "done!");
|
||||
}
|
||||
}
|
||||
|
||||
void Indexer::SyncDelete() {
|
||||
/* remove all tracks that no longer reference a valid path entry */
|
||||
|
||||
this->dbConnection.Execute("DELETE FROM tracks WHERE path_id NOT IN (SELECT id FROM paths)");
|
||||
this->dbConnection.Execute("DELETE FROM tracks WHERE source_id == 0 AND path_id NOT IN (SELECT id FROM paths)");
|
||||
|
||||
/* remove files that are no longer on the filesystem. */
|
||||
|
||||
@ -453,9 +533,11 @@ void Indexer::SyncDelete() {
|
||||
|
||||
db::Statement allTracks(
|
||||
"SELECT t.id, t.filename "
|
||||
"FROM tracks t ", this->dbConnection);
|
||||
"FROM tracks t "
|
||||
"WHERE source_id == 0", /* IIndexerSources delete their own tracks */
|
||||
this->dbConnection);
|
||||
|
||||
while (allTracks.Step() == db::Row && !this->Exited() && !this->Restarted()) {
|
||||
while (allTracks.Step() == db::Row && !this->Exited()) {
|
||||
bool remove = false;
|
||||
std::string fn = allTracks.ColumnText(1);
|
||||
|
||||
@ -477,15 +559,6 @@ void Indexer::SyncDelete() {
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Removes information related to removed tracks.
|
||||
///
|
||||
///This should be called after SyncDelete() to clean up the mess :)
|
||||
///
|
||||
///\see
|
||||
///<SyncDelete>
|
||||
//////////////////////////////////////////
|
||||
void Indexer::SyncCleanup() {
|
||||
/* remove old artists */
|
||||
this->dbConnection.Execute("DELETE FROM track_artists WHERE track_id NOT IN (SELECT id FROM tracks)");
|
||||
@ -507,22 +580,12 @@ void Indexer::SyncCleanup() {
|
||||
this->dbConnection.Execute("DELETE FROM playlist_tracks WHERE track_id NOT IN (SELECT id FROM tracks)");
|
||||
|
||||
/* optimize and shrink */
|
||||
this->dbConnection.Execute("ANALYZE");
|
||||
this->dbConnection.Execute("VACUUM");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Get a vector with all sync paths
|
||||
//////////////////////////////////////////
|
||||
void Indexer::GetPaths(std::vector<std::string>& paths) {
|
||||
db::Connection connection;
|
||||
connection.Open(this->dbFilename.c_str());
|
||||
db::Statement stmt("SELECT path FROM paths ORDER BY id", connection);
|
||||
|
||||
while (stmt.Step() == db::Row) {
|
||||
paths.push_back(stmt.ColumnText(0));
|
||||
}
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
std::copy(this->paths.begin(), this->paths.end(), std::back_inserter(paths));
|
||||
}
|
||||
|
||||
static int optimize(
|
||||
@ -554,27 +617,11 @@ static int optimize(
|
||||
}
|
||||
|
||||
void Indexer::SyncOptimize() {
|
||||
DBID count = 0, id = 0;
|
||||
|
||||
{
|
||||
db::ScopedTransaction transaction(this->dbConnection);
|
||||
optimize(this->dbConnection, "genre", "genres");
|
||||
}
|
||||
|
||||
{
|
||||
db::ScopedTransaction transaction(this->dbConnection);
|
||||
optimize(this->dbConnection, "artist", "artists");
|
||||
}
|
||||
|
||||
{
|
||||
db::ScopedTransaction transaction(this->dbConnection);
|
||||
optimize(this->dbConnection, "album", "albums");
|
||||
}
|
||||
|
||||
{
|
||||
db::ScopedTransaction transaction(this->dbConnection);
|
||||
optimize(this->dbConnection, "content", "meta_values");
|
||||
}
|
||||
db::ScopedTransaction transaction(this->dbConnection);
|
||||
optimize(this->dbConnection, "genre", "genres");
|
||||
optimize(this->dbConnection, "artist", "artists");
|
||||
optimize(this->dbConnection, "album", "albums");
|
||||
optimize(this->dbConnection, "content", "meta_values");
|
||||
}
|
||||
|
||||
void Indexer::ProcessAddRemoveQueue() {
|
||||
@ -601,8 +648,6 @@ void Indexer::ProcessAddRemoveQueue() {
|
||||
|
||||
this->addRemoveQueue.pop_front();
|
||||
}
|
||||
|
||||
this->PathsUpdated();
|
||||
}
|
||||
|
||||
void Indexer::RunAnalyzers() {
|
||||
@ -692,7 +737,7 @@ void Indexer::RunAnalyzers() {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->Exited() || this->Restarted()){
|
||||
if (this->Exited()){
|
||||
return;
|
||||
}
|
||||
|
||||
@ -700,27 +745,91 @@ void Indexer::RunAnalyzers() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Indexer::Restarted() {
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
return this->restart;
|
||||
}
|
||||
|
||||
bool Indexer::Exited() {
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
return this->exit;
|
||||
}
|
||||
|
||||
void Indexer::Wait() {
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
if (!this->exit) {
|
||||
this->waitCondition.wait(lock);
|
||||
IRetainedTrackWriter* Indexer::CreateWriter() {
|
||||
std::shared_ptr<Track> track(new IndexerTrack(0));
|
||||
return new RetainedTrackWriter(track);
|
||||
}
|
||||
|
||||
bool Indexer::Save(IIndexerSource* source, IRetainedTrackWriter* track, const char* externalId) {
|
||||
if (source->SourceId() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* two levels of unpacking with dynamic_casts. don't tell anyone,
|
||||
it'll be our little secret. */
|
||||
RetainedTrackWriter* rtw = dynamic_cast<RetainedTrackWriter*>(track);
|
||||
if (rtw) {
|
||||
IndexerTrack* it = rtw->As<IndexerTrack*>();
|
||||
if (it) {
|
||||
if (externalId && strlen(externalId)) {
|
||||
it->SetValue(constants::Track::EXTERNAL_ID, externalId);
|
||||
}
|
||||
|
||||
std::string id = std::to_string(source->SourceId());
|
||||
it->SetValue(constants::Track::SOURCE_ID, id.c_str());
|
||||
|
||||
return it->Save(this->dbConnection, this->libraryPath);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Indexer::RemoveByUri(IIndexerSource* source, const char* uri) {
|
||||
if (source->SourceId() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
db::Statement stmt(
|
||||
"DELETE FROM tracks WHERE source_id=? AND filename=?",
|
||||
this->dbConnection);
|
||||
|
||||
stmt.BindInt(0, source->SourceId());
|
||||
stmt.BindText(1, uri);
|
||||
|
||||
return (stmt.Step() == db::Okay);
|
||||
}
|
||||
|
||||
bool Indexer::RemoveByExternalId(IIndexerSource* source, const char* id) {
|
||||
if (source->SourceId() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
db::Statement stmt(
|
||||
"DELETE FROM tracks WHERE source_id=? AND external_id=?",
|
||||
this->dbConnection);
|
||||
|
||||
stmt.BindInt(0, source->SourceId());
|
||||
stmt.BindText(1, id);
|
||||
|
||||
return (stmt.Step() == db::Okay);
|
||||
}
|
||||
|
||||
int Indexer::RemoveAll(IIndexerSource* source) {
|
||||
if (source->SourceId() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
db::Statement stmt(
|
||||
"DELETE FROM tracks WHERE source_id=?",
|
||||
this->dbConnection);
|
||||
|
||||
stmt.BindInt(0, source->SourceId());
|
||||
|
||||
if (stmt.Step() == db::Okay) {
|
||||
return dbConnection.LastModifiedRowCount();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Indexer::ScheduleRescan(IIndexerSource* source) {
|
||||
if (source->SourceId() != 0) {
|
||||
this->Schedule(SyncType::Sources, source);
|
||||
}
|
||||
}
|
||||
|
||||
void Indexer::Wait(const boost::xtime &time) {
|
||||
boost::mutex::scoped_lock lock(this->stateMutex);
|
||||
if (!this->exit) {
|
||||
this->waitCondition.timed_wait(lock, time);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include <core/db/Connection.h>
|
||||
#include <core/sdk/IMetadataReader.h>
|
||||
#include <core/sdk/IDecoderFactory.h>
|
||||
#include <core/sdk/IIndexerWriter.h>
|
||||
#include <core/sdk/IIndexerNotifier.h>
|
||||
#include <core/library/IIndexer.h>
|
||||
#include <core/support/Preferences.h>
|
||||
|
||||
@ -51,10 +53,16 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
|
||||
namespace musik { namespace core {
|
||||
|
||||
class Indexer : public IIndexer, private boost::noncopyable {
|
||||
class Indexer :
|
||||
public musik::core::IIndexer,
|
||||
public musik::core::sdk::IIndexerWriter,
|
||||
public musik::core::sdk::IIndexerNotifier,
|
||||
private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Indexer(
|
||||
const std::string& libraryPath,
|
||||
@ -62,28 +70,62 @@ namespace musik { namespace core {
|
||||
|
||||
virtual ~Indexer();
|
||||
|
||||
/* IIndexer */
|
||||
virtual void AddPath(const std::string& paths);
|
||||
virtual void RemovePath(const std::string& paths);
|
||||
virtual void GetPaths(std::vector<std::string>& paths);
|
||||
virtual void Synchronize(bool restart = false);
|
||||
virtual void Schedule(SyncType type);
|
||||
virtual State GetState() { return this->state; }
|
||||
|
||||
/* IIndexerWriter */
|
||||
virtual musik::core::sdk::IRetainedTrackWriter* CreateWriter();
|
||||
virtual bool RemoveByUri(musik::core::sdk::IIndexerSource* source, const char* uri);
|
||||
virtual bool RemoveByExternalId(musik::core::sdk::IIndexerSource* source, const char* id);
|
||||
virtual int RemoveAll(musik::core::sdk::IIndexerSource* source);
|
||||
|
||||
virtual bool Save(
|
||||
musik::core::sdk::IIndexerSource* source,
|
||||
musik::core::sdk::IRetainedTrackWriter* track,
|
||||
const char* externalId = "");
|
||||
|
||||
/* IIndexerNotifier */
|
||||
virtual void ScheduleRescan(musik::core::sdk::IIndexerSource* source);
|
||||
|
||||
private:
|
||||
struct AddRemoveContext {
|
||||
bool add;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct SyncContext {
|
||||
SyncType type;
|
||||
int sourceId;
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<
|
||||
musik::core::sdk::IMetadataReader> > MetadataReaderList;
|
||||
|
||||
typedef std::vector<std::shared_ptr<
|
||||
musik::core::sdk::IDecoderFactory> > DecoderList;
|
||||
|
||||
typedef std::vector<std::shared_ptr<
|
||||
musik::core::sdk::IIndexerSource> > IndexerSourceList;
|
||||
|
||||
void ThreadLoop();
|
||||
|
||||
bool Exited();
|
||||
void Wait();
|
||||
void Wait(const boost::xtime &oTime);
|
||||
|
||||
bool Restarted();
|
||||
void Synchronize(const SyncContext& context, boost::asio::io_service* io);
|
||||
|
||||
void FinalizeSync();
|
||||
void FinalizeSync(const SyncContext& context);
|
||||
void SyncDelete();
|
||||
void SyncCleanup();
|
||||
void SyncSource(musik::core::sdk::IIndexerSource* source);
|
||||
void ProcessAddRemoveQueue();
|
||||
void SyncOptimize();
|
||||
void RunAnalyzers();
|
||||
void SynchronizeInternal(boost::asio::io_service* io);
|
||||
|
||||
void Schedule(SyncType type, musik::core::sdk::IIndexerSource *source);
|
||||
|
||||
void SyncDirectory(
|
||||
boost::asio::io_service* io,
|
||||
@ -96,37 +138,22 @@ namespace musik { namespace core {
|
||||
const std::string& pathId);
|
||||
|
||||
db::Connection dbConnection;
|
||||
|
||||
std::string libraryPath;
|
||||
std::string dbFilename;
|
||||
|
||||
bool restart;
|
||||
bool exit;
|
||||
State state;
|
||||
|
||||
boost::mutex stateMutex;
|
||||
boost::condition waitCondition;
|
||||
|
||||
boost::thread *thread;
|
||||
std::atomic<size_t> filesSaved;
|
||||
|
||||
struct AddRemoveContext {
|
||||
bool add;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<
|
||||
musik::core::sdk::IMetadataReader> > MetadataReaderList;
|
||||
|
||||
typedef std::vector<std::shared_ptr<
|
||||
musik::core::sdk::IDecoderFactory> > DecoderList;
|
||||
|
||||
std::deque<AddRemoveContext> addRemoveQueue;
|
||||
|
||||
std::deque<SyncContext> syncQueue;
|
||||
MetadataReaderList metadataReaders;
|
||||
DecoderList audioDecoders;
|
||||
IndexerSourceList sources;
|
||||
std::shared_ptr<musik::core::Preferences> prefs;
|
||||
std::shared_ptr<musik::core::db::ScopedTransaction> trackTransaction;
|
||||
std::vector<std::string> paths;
|
||||
boost::interprocess::interprocess_semaphore readSemaphore;
|
||||
};
|
||||
|
||||
|
@ -82,14 +82,7 @@ LocalLibrary::LocalLibrary(std::string name,int id)
|
||||
, messageQueue(nullptr) {
|
||||
this->identifier = boost::lexical_cast<std::string>(id);
|
||||
|
||||
auto prefs = Preferences::ForComponent("library");
|
||||
|
||||
this->db.Open(
|
||||
this->GetDatabaseFilename().c_str(),
|
||||
0,
|
||||
prefs->GetInt("DatabaseCache",
|
||||
4096));
|
||||
|
||||
this->db.Open(this->GetDatabaseFilename().c_str());
|
||||
LocalLibrary::CreateDatabase(this->db);
|
||||
|
||||
this->indexer = new core::Indexer(
|
||||
@ -257,101 +250,118 @@ musik::core::IIndexer* LocalLibrary::Indexer() {
|
||||
}
|
||||
|
||||
void LocalLibrary::CreateDatabase(db::Connection &db){
|
||||
// Create the tracks-table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS tracks ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"track INTEGER DEFAULT 0,"
|
||||
"disc TEXT DEFAULT '1',"
|
||||
"bpm REAL DEFAULT 0,"
|
||||
"duration INTEGER DEFAULT 0,"
|
||||
"filesize INTEGER DEFAULT 0,"
|
||||
"year INTEGER DEFAULT 0,"
|
||||
"visual_genre_id INTEGER DEFAULT 0,"
|
||||
"visual_artist_id INTEGER DEFAULT 0,"
|
||||
"album_artist_id INTEGER DEFAULT 0,"
|
||||
"path_id INTEGER,"
|
||||
"album_id INTEGER DEFAULT 0,"
|
||||
"title TEXT default '',"
|
||||
"filename TEXT default '',"
|
||||
"filetime INTEGER DEFAULT 0,"
|
||||
"thumbnail_id INTEGER DEFAULT 0)");
|
||||
/* tracks */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS tracks ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"track INTEGER DEFAULT 0,"
|
||||
"disc TEXT DEFAULT '1',"
|
||||
"bpm REAL DEFAULT 0,"
|
||||
"duration INTEGER DEFAULT 0,"
|
||||
"filesize INTEGER DEFAULT 0,"
|
||||
"year INTEGER DEFAULT 0,"
|
||||
"visual_genre_id INTEGER DEFAULT 0,"
|
||||
"visual_artist_id INTEGER DEFAULT 0,"
|
||||
"album_artist_id INTEGER DEFAULT 0,"
|
||||
"path_id INTEGER,"
|
||||
"album_id INTEGER DEFAULT 0,"
|
||||
"title TEXT DEFAULT '',"
|
||||
"filename TEXT DEFAULT '',"
|
||||
"filetime INTEGER DEFAULT 0,"
|
||||
"thumbnail_id INTEGER DEFAULT 0,"
|
||||
"source_id INTEGER DEFAULT 0,"
|
||||
"visible INTEGER DEFAULT 1,"
|
||||
"external_id TEXT DEFAULT null"
|
||||
")");
|
||||
|
||||
// Create the genres-table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS genres ("
|
||||
/* genres tables */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS genres ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT default '',"
|
||||
"aggregated INTEGER DEFAULT 0,"
|
||||
"sort_order INTEGER DEFAULT 0)");
|
||||
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS track_genres ("
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS track_genres ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"track_id INTEGER DEFAULT 0,"
|
||||
"genre_id INTEGER DEFAULT 0)");
|
||||
|
||||
// Create the artists-table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS artists ("
|
||||
/* artist tables */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS artists ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT default '',"
|
||||
"aggregated INTEGER DEFAULT 0,"
|
||||
"sort_order INTEGER DEFAULT 0)");
|
||||
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS track_artists ("
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS track_artists ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"track_id INTEGER DEFAULT 0,"
|
||||
"artist_id INTEGER DEFAULT 0)");
|
||||
|
||||
// Create the meta-tables
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS meta_keys ("
|
||||
/* arbitrary metadata */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS meta_keys ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT)");
|
||||
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS meta_values ("
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS meta_values ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"meta_key_id INTEGER DEFAULT 0,"
|
||||
"sort_order INTEGER DEFAULT 0,"
|
||||
"content TEXT)");
|
||||
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS track_meta ("
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS track_meta ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"track_id INTEGER DEFAULT 0,"
|
||||
"meta_value_id INTEGER DEFAULT 0)");
|
||||
|
||||
// Create the albums-table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS albums ("
|
||||
/* albums */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS albums ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT default '',"
|
||||
"thumbnail_id INTEGER default 0,"
|
||||
"sort_order INTEGER DEFAULT 0)");
|
||||
|
||||
// Create the paths-table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS paths ("
|
||||
/* paths */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS paths ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"path TEXT default ''"
|
||||
")");
|
||||
|
||||
// Create the thumbnails table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS thumbnails ("
|
||||
/* thumbnails */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS thumbnails ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"filename TEXT default '',"
|
||||
"filesize INTEGER DEFAULT 0,"
|
||||
"checksum INTEGER DEFAULT 0"
|
||||
")");
|
||||
|
||||
// Create the playlists
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS playlists ("
|
||||
/* playlists */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS playlists ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT default '',"
|
||||
"user_id INTEGER default 0"
|
||||
")");
|
||||
|
||||
// Create the playlist_tracks table
|
||||
db.Execute("CREATE TABLE IF NOT EXISTS playlist_tracks ("
|
||||
/* playlist tracks */
|
||||
db.Execute(
|
||||
"CREATE TABLE IF NOT EXISTS playlist_tracks ("
|
||||
"track_id INTEGER DEFAULT 0,"
|
||||
"playlist_id INTEGER DEFAULT 0,"
|
||||
"sort_order INTEGER DEFAULT 0"
|
||||
")");
|
||||
|
||||
// INDEXES
|
||||
/* indexes */
|
||||
db.Execute("CREATE UNIQUE INDEX IF NOT EXISTS users_index ON users (login)");
|
||||
db.Execute("CREATE UNIQUE INDEX IF NOT EXISTS paths_index ON paths (path)");
|
||||
db.Execute("CREATE INDEX IF NOT EXISTS genre_index ON genres (sort_order)");
|
||||
@ -369,4 +379,20 @@ void LocalLibrary::CreateDatabase(db::Connection &db){
|
||||
db.Execute("CREATE INDEX IF NOT EXISTS metavalues_index1 ON meta_values (meta_key_id)");
|
||||
|
||||
db.Execute("CREATE INDEX IF NOT EXISTS playlist_index ON playlist_tracks (playlist_id,sort_order)");
|
||||
|
||||
/* upgrade 1: add "source_id", "visible", and "external_id" columns to tracks table */
|
||||
int result = db.Execute("ALTER TABLE tracks ADD COLUMN source_id INTEGER DEFAULT 0");
|
||||
|
||||
if (result == db::Okay) {
|
||||
db.Execute("UPDATE tracks SET source_id=0 WHERE source_id is null");
|
||||
}
|
||||
|
||||
result = db.Execute("ALTER TABLE tracks ADD COLUMN visible INTEGER DEFAULT 1");
|
||||
|
||||
if (result == db::Okay) {
|
||||
db.Execute("UPDATE tracks SET visible=1 WHERE visible is null");
|
||||
}
|
||||
|
||||
result = db.Execute("ALTER TABLE tracks ADD COLUMN external_id TEXT DEFAULT null");
|
||||
db.Execute("CREATE INDEX IF NOT EXISTS tracks_external_id_index ON tracks (external_id)");
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ namespace musik { namespace core { namespace library { namespace constants {
|
||||
static const char* ALBUM_ARTIST_ID = "album_artist_id";
|
||||
static const char* ALBUM_ID = "album_id";
|
||||
static const char* PATH_ID = "path_id";
|
||||
static const char* SOURCE_ID = "source_id";
|
||||
static const char* EXTERNAL_ID = "external_id";
|
||||
|
||||
/* used in Track instances where foreign key IDs have been
|
||||
replaced with actual values... */
|
||||
|
@ -59,8 +59,8 @@ namespace {
|
||||
virtual int GetValue(const char* key, char* dst, int size) { return this->wrapped->GetValue(key, dst, size); }
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue) { return this->wrapped->GetUint64(key, defaultValue); }
|
||||
virtual long long GetInt64(const char* key, long long defaultValue) { return this->wrapped->GetInt64(key, defaultValue); }
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue) { return this->wrapped->GetUint32(key, defaultValue); }
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue) { return this->wrapped->GetInt32(key, defaultValue); }
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue) { return this->wrapped->GetUint32(key, defaultValue); }
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue) { return this->wrapped->GetInt32(key, defaultValue); }
|
||||
virtual double GetDouble(const char* key, double defaultValue) { return this->wrapped->GetDouble(key, defaultValue); }
|
||||
virtual const char* GetDescription() { return this->wrapped->GetDescription(); }
|
||||
virtual const char* GetType() { return this->wrapped->GetType(); }
|
||||
@ -112,27 +112,62 @@ std::string MetadataMap::GetValue(const char* key) {
|
||||
}
|
||||
|
||||
unsigned long long MetadataMap::GetUint64(const char* key, unsigned long long defaultValue) {
|
||||
try { return std::stoull(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoull(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long long MetadataMap::GetInt64(const char* key, long long defaultValue) {
|
||||
try { return std::stoll(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoll(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
unsigned long MetadataMap::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try { return std::stoul(GetValue(key)); } catch (...) { }
|
||||
unsigned int MetadataMap::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoul(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long MetadataMap::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try { return std::stol(GetValue(key)); } catch (...) { }
|
||||
int MetadataMap::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stol(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
double MetadataMap::GetDouble(const char* key, double defaultValue) {
|
||||
try { return std::stod(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stod(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,8 @@ namespace musik { namespace core {
|
||||
virtual int GetValue(const char* key, char* dst, int size);
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL);
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL);
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f);
|
||||
|
||||
/* implementation specific */
|
||||
|
@ -69,6 +69,9 @@ static const std::string COLUMNS =
|
||||
static const std::string TABLES =
|
||||
"albums, tracks, artists ";
|
||||
|
||||
static const std::string VISIBLE_PREDICATE =
|
||||
" tracks.visible == 1 AND ";
|
||||
|
||||
static const std::string FILTER_PREDICATE =
|
||||
" (LOWER(album) like ? OR LOWER(album_artist) like ?) AND ";
|
||||
|
||||
@ -117,6 +120,7 @@ bool AlbumListQuery::OnRun(Connection& db) {
|
||||
bool category = fieldIdName.size() && fieldIdValue != -1;
|
||||
|
||||
std::string query = "SELECT DISTINCT " + COLUMNS + " FROM " + TABLES + " WHERE ";
|
||||
query += VISIBLE_PREDICATE;
|
||||
query += filtered ? FILTER_PREDICATE : "";
|
||||
|
||||
if (category) {
|
||||
|
@ -53,49 +53,49 @@ using namespace musik::core::db::local;
|
||||
static const std::string REGULAR_ALBUM_QUERY =
|
||||
"SELECT DISTINCT albums.id, albums.name "
|
||||
"FROM albums, tracks "
|
||||
"WHERE albums.id = tracks.album_id "
|
||||
"WHERE albums.id = tracks.album_id AND tracks.visible = 1 "
|
||||
"ORDER BY albums.sort_order;";
|
||||
|
||||
static const std::string FILTERED_ALBUM_QUERY =
|
||||
"SELECT DISTINCT albums.id, albums.name "
|
||||
"FROM albums, tracks "
|
||||
"WHERE albums.id = tracks.album_id AND LOWER(albums.name) LIKE ? "
|
||||
"WHERE albums.id = tracks.album_id AND LOWER(albums.name) LIKE ? AND tracks.visible = 1 "
|
||||
"ORDER BY albums.sort_order;";
|
||||
|
||||
static const std::string REGULAR_ARTIST_QUERY =
|
||||
"SELECT DISTINCT artists.id, artists.name "
|
||||
"FROM artists, tracks "
|
||||
"WHERE artists.id = tracks.visual_artist_id "
|
||||
"WHERE artists.id = tracks.visual_artist_id AND tracks.visible = 1 "
|
||||
"ORDER BY artists.sort_order;";
|
||||
|
||||
static const std::string FILTERED_ARTIST_QUERY =
|
||||
"SELECT DISTINCT artists.id, artists.name "
|
||||
"FROM artists, tracks "
|
||||
"WHERE artists.id = tracks.visual_artist_id AND LOWER(artists.name) LIKE ? "
|
||||
"WHERE artists.id = tracks.visual_artist_id AND LOWER(artists.name) LIKE ? AND tracks.visible = 1 "
|
||||
"ORDER BY artists.sort_order;";
|
||||
|
||||
static const std::string REGULAR_ALBUM_ARTIST_QUERY =
|
||||
"SELECT DISTINCT artists.id, artists.name "
|
||||
"FROM artists, tracks "
|
||||
"WHERE artists.id = tracks.album_artist_id "
|
||||
"WHERE artists.id = tracks.album_artist_id AND tracks.visible = 1 "
|
||||
"ORDER BY artists.sort_order;";
|
||||
|
||||
static const std::string FILTERED_ALBUM_ARTIST_QUERY =
|
||||
"SELECT DISTINCT artists.id, artists.name "
|
||||
"FROM artists, tracks "
|
||||
"WHERE artists.id = tracks.album_artist_id AND LOWER(artists.name) LIKE ? "
|
||||
"WHERE artists.id = tracks.album_artist_id AND LOWER(artists.name) LIKE ? AND tracks.visible = 1 "
|
||||
"ORDER BY artists.sort_order;";
|
||||
|
||||
static const std::string REGULAR_GENRE_QUERY =
|
||||
"SELECT DISTINCT genres.id, genres.name "
|
||||
"FROM genres, tracks "
|
||||
"WHERE genres.id = tracks.visual_genre_id "
|
||||
"WHERE genres.id = tracks.visual_genre_id AND tracks.visible = 1 "
|
||||
"ORDER BY genres.sort_order;";
|
||||
|
||||
static const std::string FILTERED_GENRE_QUERY =
|
||||
"SELECT DISTINCT genres.id, genres.name "
|
||||
"FROM genres, tracks "
|
||||
"WHERE genres.id = tracks.visual_genre_id AND LOWER(genres.name) LIKE ? "
|
||||
"WHERE genres.id = tracks.visual_genre_id AND LOWER(genres.name) LIKE ? AND tracks.visible = 1 "
|
||||
"ORDER BY genres.sort_order;";
|
||||
|
||||
static const std::string REGULAR_PLAYLISTS_QUERY =
|
||||
|
@ -121,7 +121,7 @@ bool CategoryTrackListQuery::OnRun(Connection& db) {
|
||||
std::string query =
|
||||
"SELECT DISTINCT t.id, al.name "
|
||||
"FROM tracks t, albums al, artists ar, genres gn "
|
||||
"WHERE t.%s=? AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
|
||||
"WHERE t.visible=1 AND t.%s=? AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
|
||||
|
||||
if (this->filter.size()) {
|
||||
query += " AND (t.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) ";
|
||||
|
@ -81,8 +81,8 @@ bool GetPlaylistQuery::OnRun(Connection& db) {
|
||||
|
||||
std::string query =
|
||||
"SELECT DISTINCT track_id "
|
||||
"FROM playlist_tracks "
|
||||
"WHERE playlist_id=? "
|
||||
"FROM tracks, playlist_tracks "
|
||||
"WHERE tracks.id=track_id AND tracks.visible=1 AND playlist_id=? "
|
||||
"ORDER BY sort_order " +
|
||||
this->GetLimitAndOffset();
|
||||
|
||||
|
@ -99,6 +99,7 @@ bool SearchTrackListQuery::OnRun(Connection& db) {
|
||||
"SELECT DISTINCT t.id, al.name "
|
||||
"FROM tracks t, albums al, artists ar, genres gn "
|
||||
"WHERE "
|
||||
" t.visible=1 AND "
|
||||
"(t.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) "
|
||||
" AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
|
||||
"ORDER BY al.name, disc, track, ar.name ";
|
||||
@ -107,7 +108,7 @@ bool SearchTrackListQuery::OnRun(Connection& db) {
|
||||
query =
|
||||
"SELECT DISTINCT t.id, al.name "
|
||||
"FROM tracks t, albums al, artists ar, genres gn "
|
||||
"WHERE t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
|
||||
"WHERE t.visible=1 AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
|
||||
"ORDER BY al.name, disc, track, ar.name ";
|
||||
}
|
||||
|
||||
|
98
src/core/library/query/local/TrackMetadataQuery.cpp
Normal file
98
src/core/library/query/local/TrackMetadataQuery.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pch.hpp"
|
||||
#include "TrackMetadataQuery.h"
|
||||
#include <core/library/LocalLibraryConstants.h>
|
||||
|
||||
using namespace musik::core::db;
|
||||
using namespace musik::core::db::local;
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::library;
|
||||
|
||||
static const std::string ALL_METADATA_QUERY =
|
||||
"SELECT DISTINCT t.track, t.disc, t.bpm, t.duration, t.filesize, t.year, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id "
|
||||
"FROM tracks t, albums al, artists alar, artists ar, genres gn "
|
||||
"WHERE t.id=? AND t.album_id=al.id AND t.album_artist_id=alar.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
|
||||
|
||||
static const std::string URI_ONLY_QUERY =
|
||||
"SELECT DISTINCT filename "
|
||||
"FROM tracks "
|
||||
"WHERE t.id=? ";
|
||||
|
||||
TrackMetadataQuery::TrackMetadataQuery(TrackPtr target, ILibraryPtr library, Type type) {
|
||||
this->result = target;
|
||||
this->library = library;
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
bool TrackMetadataQuery::OnRun(Connection& db) {
|
||||
if (this->type == Type::AllMetadata) {
|
||||
Statement trackQuery(ALL_METADATA_QUERY.c_str(), db);
|
||||
trackQuery.BindInt(0, (uint64) this->result->GetId());
|
||||
|
||||
if (trackQuery.Step() == Row) {
|
||||
result->SetValue(constants::Track::TRACK_NUM, trackQuery.ColumnText(0));
|
||||
result->SetValue(constants::Track::DISC_NUM, trackQuery.ColumnText(1));
|
||||
result->SetValue(constants::Track::BPM, trackQuery.ColumnText(2));
|
||||
result->SetValue(constants::Track::DURATION, trackQuery.ColumnText(3));
|
||||
result->SetValue(constants::Track::FILESIZE, trackQuery.ColumnText(4));
|
||||
result->SetValue(constants::Track::YEAR, trackQuery.ColumnText(5));
|
||||
result->SetValue(constants::Track::TITLE, trackQuery.ColumnText(6));
|
||||
result->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(7));
|
||||
result->SetValue(constants::Track::THUMBNAIL_ID, trackQuery.ColumnText(8));
|
||||
result->SetValue(constants::Track::ALBUM, trackQuery.ColumnText(9));
|
||||
result->SetValue(constants::Track::ALBUM_ARTIST, trackQuery.ColumnText(10));
|
||||
result->SetValue(constants::Track::GENRE, trackQuery.ColumnText(11));
|
||||
result->SetValue(constants::Track::ARTIST, trackQuery.ColumnText(12));
|
||||
result->SetValue(constants::Track::FILETIME, trackQuery.ColumnText(13));
|
||||
result->SetValue(constants::Track::GENRE_ID, trackQuery.ColumnText(14));
|
||||
result->SetValue(constants::Track::ARTIST_ID, trackQuery.ColumnText(15));
|
||||
result->SetValue(constants::Track::ALBUM_ARTIST_ID, trackQuery.ColumnText(16));
|
||||
result->SetValue(constants::Track::ALBUM_ID, trackQuery.ColumnText(17));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Statement trackQuery(URI_ONLY_QUERY.c_str(), db);
|
||||
trackQuery.BindInt(0, (uint64) this->result->GetId());
|
||||
|
||||
if (trackQuery.Step() == Row) {
|
||||
result->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(0));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
70
src/core/library/query/local/TrackMetadataQuery.h
Normal file
70
src/core/library/query/local/TrackMetadataQuery.h
Normal file
@ -0,0 +1,70 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "LocalQueryBase.h"
|
||||
|
||||
#include <core/library/ILibrary.h>
|
||||
#include <core/library/track/Track.h>
|
||||
|
||||
namespace musik { namespace core { namespace db { namespace local {
|
||||
|
||||
class TrackMetadataQuery : public LocalQueryBase {
|
||||
public:
|
||||
enum class Type : int {
|
||||
AllMetadata,
|
||||
UriOnly
|
||||
};
|
||||
|
||||
TrackMetadataQuery(
|
||||
musik::core::TrackPtr target,
|
||||
musik::core::ILibraryPtr library,
|
||||
Type type = Type::AllMetadata);
|
||||
|
||||
virtual ~TrackMetadataQuery() { }
|
||||
|
||||
TrackPtr Result() {
|
||||
return this->result;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool OnRun(musik::core::db::Connection& db);
|
||||
virtual std::string Name() { return "TrackMetadataQuery"; }
|
||||
|
||||
private:
|
||||
ILibraryPtr library;
|
||||
TrackPtr result;
|
||||
Type type;
|
||||
};
|
||||
|
||||
} } } }
|
@ -88,27 +88,58 @@ std::string IndexerTrack::GetValue(const char* metakey) {
|
||||
}
|
||||
|
||||
unsigned long long IndexerTrack::GetUint64(const char* key, unsigned long long defaultValue) {
|
||||
try { return std::stoull(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoull(GetValue(key));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long long IndexerTrack::GetInt64(const char* key, long long defaultValue) {
|
||||
try { return std::stoll(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoll(GetValue(key));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
unsigned long IndexerTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try { return std::stoul(GetValue(key)); } catch (...) { }
|
||||
unsigned int IndexerTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoul(GetValue(key));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long IndexerTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try { return std::stol(GetValue(key)); } catch (...) { }
|
||||
int IndexerTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stol(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
double IndexerTrack::GetDouble(const char* key, double defaultValue) {
|
||||
try { return std::stod(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stod(GetValue(key));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@ -120,12 +151,12 @@ void IndexerTrack::SetValue(const char* metakey, const char* value) {
|
||||
}
|
||||
|
||||
void IndexerTrack::ClearValue(const char* metakey) {
|
||||
if (this->internalMetadata) {
|
||||
this->internalMetadata->metadata.erase(metakey);
|
||||
}
|
||||
if (this->internalMetadata) {
|
||||
this->internalMetadata->metadata.erase(metakey);
|
||||
}
|
||||
}
|
||||
|
||||
void IndexerTrack::SetThumbnail(const char *data,long size) {
|
||||
void IndexerTrack::SetThumbnail(const char *data, long size) {
|
||||
if (this->internalMetadata->thumbnailData) {
|
||||
delete this->internalMetadata->thumbnailData;
|
||||
}
|
||||
@ -179,12 +210,12 @@ bool IndexerTrack::NeedsToBeIndexed(
|
||||
this->SetValue("filename", file.string().c_str());
|
||||
|
||||
size_t lastDot = file.leaf().string().find_last_of(".");
|
||||
if (lastDot != std::string::npos){
|
||||
if (lastDot != std::string::npos) {
|
||||
this->SetValue("extension", file.leaf().string().substr(lastDot + 1).c_str());
|
||||
}
|
||||
|
||||
DBID fileSize = (DBID) boost::filesystem::file_size(file);
|
||||
DBTIME fileTime = (DBTIME) boost::filesystem::last_write_time(file);
|
||||
DBID fileSize = (DBID)boost::filesystem::file_size(file);
|
||||
DBTIME fileTime = (DBTIME)boost::filesystem::last_write_time(file);
|
||||
|
||||
this->SetValue("filesize", boost::lexical_cast<std::string>(fileSize).c_str());
|
||||
this->SetValue("filetime", boost::lexical_cast<std::string>(fileTime).c_str());
|
||||
@ -208,7 +239,7 @@ bool IndexerTrack::NeedsToBeIndexed(
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
catch (...) {
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -218,6 +249,23 @@ static DBID writeToTracksTable(
|
||||
db::Connection &dbConnection,
|
||||
IndexerTrack& track)
|
||||
{
|
||||
/* if there's no ID specified, but we have an external ID, let's
|
||||
see if we can find the corresponding ID. this can happen when
|
||||
IInputSource plugins are reading/writing track data. */
|
||||
if (track.GetId() == 0) {
|
||||
std::string externalId = track.GetValue("external_id");
|
||||
int sourceId = track.GetInt32("source_id", 0);
|
||||
|
||||
if (externalId.size() && sourceId != 0) {
|
||||
db::Statement stmt("SELECT id FROM tracks WHERE source_id=? AND external_id=?", dbConnection);
|
||||
stmt.BindInt(0, sourceId);
|
||||
stmt.BindText(1, externalId);
|
||||
if (stmt.Step() == db::Row) {
|
||||
track.SetId(stmt.ColumnInt64(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db::Statement stmt("INSERT OR REPLACE INTO tracks " \
|
||||
"(id, track, disc, bpm, duration, filesize, year, title, filename, filetime, path_id) " \
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", dbConnection);
|
||||
@ -225,13 +273,13 @@ static DBID writeToTracksTable(
|
||||
stmt.BindText(1, track.GetValue("track"));
|
||||
stmt.BindText(2, track.GetValue("disc"));
|
||||
stmt.BindText(3, track.GetValue("bpm"));
|
||||
stmt.BindText(4, track.GetValue("duration"));
|
||||
stmt.BindText(5, track.GetValue("filesize"));
|
||||
stmt.BindInt(4, track.GetInt32("duration"));
|
||||
stmt.BindInt(5, track.GetInt32("filesize"));
|
||||
stmt.BindText(6, track.GetValue("year"));
|
||||
stmt.BindText(7, track.GetValue("title"));
|
||||
stmt.BindText(8, track.GetValue("filename"));
|
||||
stmt.BindText(9, track.GetValue("filetime"));
|
||||
stmt.BindText(10, track.GetValue("path_id"));
|
||||
stmt.BindInt(9, track.GetInt32("filetime"));
|
||||
stmt.BindInt(10, track.GetInt32("path_id"));
|
||||
|
||||
if (track.GetId() != 0) {
|
||||
stmt.BindInt(0, (uint64) track.GetId());
|
||||
@ -272,7 +320,11 @@ static void removeKnownFields(Track::MetadataMap& metadata) {
|
||||
metadata.erase("extension");
|
||||
metadata.erase("genre");
|
||||
metadata.erase("artist");
|
||||
metadata.erase("album_artist");
|
||||
metadata.erase("album");
|
||||
metadata.erase("source_id");
|
||||
metadata.erase("external_id");
|
||||
metadata.erase("visible");
|
||||
}
|
||||
|
||||
DBID IndexerTrack::SaveThumbnail(db::Connection& connection, const std::string& libraryDirectory) {
|
||||
@ -532,12 +584,26 @@ bool IndexerTrack::Save(db::Connection &dbConnection, std::string libraryDirecto
|
||||
DBID albumArtistId = this->SaveSingleValueField(dbConnection, "album_artist", "artists");
|
||||
DBID thumbnailId = this->SaveThumbnail(dbConnection, libraryDirectory);
|
||||
|
||||
/* ensure we have a correct source id */
|
||||
std::string externalId = this->GetValue("external_id");
|
||||
int sourceId = 0;
|
||||
|
||||
try {
|
||||
std::string source = this->GetValue("source_id");
|
||||
if (source.size()) {
|
||||
sourceId = std::stoi(source.c_str());
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
/* shouldn't happen... */
|
||||
}
|
||||
|
||||
/* update all of the track foreign keys */
|
||||
|
||||
{
|
||||
db::Statement stmt(
|
||||
"UPDATE tracks " \
|
||||
"SET album_id=?, visual_genre_id=?, visual_artist_id=?, album_artist_id=?, thumbnail_id=? " \
|
||||
"SET album_id=?, visual_genre_id=?, visual_artist_id=?, album_artist_id=?, thumbnail_id=?, source_id=?, external_id=? " \
|
||||
"WHERE id=?", dbConnection);
|
||||
|
||||
stmt.BindInt(0, albumId);
|
||||
@ -545,7 +611,16 @@ bool IndexerTrack::Save(db::Connection &dbConnection, std::string libraryDirecto
|
||||
stmt.BindInt(2, artistId);
|
||||
stmt.BindInt(3, albumArtistId);
|
||||
stmt.BindInt(4, thumbnailId);
|
||||
stmt.BindInt(5, this->id);
|
||||
stmt.BindInt(5, sourceId);
|
||||
|
||||
if (externalId.size()) {
|
||||
stmt.BindText(6, externalId);
|
||||
}
|
||||
else {
|
||||
stmt.BindNull(6);
|
||||
}
|
||||
|
||||
stmt.BindInt(7, this->id);
|
||||
stmt.Step();
|
||||
}
|
||||
|
||||
|
@ -45,16 +45,18 @@ namespace musik { namespace core {
|
||||
IndexerTrack(DBID id);
|
||||
virtual ~IndexerTrack(void);
|
||||
|
||||
/* IWritableTrack */
|
||||
virtual void SetValue(const char* metakey, const char* value);
|
||||
virtual void ClearValue(const char* metakey);
|
||||
virtual void SetThumbnail(const char *data, long size);
|
||||
|
||||
/* ITrack */
|
||||
virtual std::string GetValue(const char* metakey);
|
||||
virtual int GetValue(const char* key, char* dst, int size);
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL);
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL);
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f);
|
||||
|
||||
virtual std::string Uri();
|
||||
|
@ -72,27 +72,62 @@ std::string LibraryTrack::GetValue(const char* metakey) {
|
||||
}
|
||||
|
||||
unsigned long long LibraryTrack::GetUint64(const char* key, unsigned long long defaultValue) {
|
||||
try { return std::stoull(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoull(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long long LibraryTrack::GetInt64(const char* key, long long defaultValue) {
|
||||
try { return std::stoll(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoll(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
unsigned long LibraryTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try { return std::stoul(GetValue(key)); } catch (...) { }
|
||||
unsigned int LibraryTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stoul(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long LibraryTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try { return std::stol(GetValue(key)); } catch (...) { }
|
||||
int LibraryTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stol(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
double LibraryTrack::GetDouble(const char* key, double defaultValue) {
|
||||
try { return std::stod(GetValue(key)); } catch (...) { }
|
||||
try {
|
||||
std::string value = GetValue(key);
|
||||
if (value.size()) {
|
||||
return std::stod(GetValue(key));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
@ -54,19 +54,20 @@ namespace musik { namespace core {
|
||||
virtual unsigned long long GetId();
|
||||
virtual void SetId(DBID id) { this->id = id; }
|
||||
|
||||
virtual std::string GetValue(const char* metakey);
|
||||
virtual std::string Uri();
|
||||
|
||||
/* IWritableTrack */
|
||||
virtual void SetValue(const char* metakey, const char* value);
|
||||
virtual void ClearValue(const char* metakey);
|
||||
virtual void SetThumbnail(const char *data, long size);
|
||||
|
||||
virtual std::string GetValue(const char* metakey);
|
||||
/* ITrack */
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL);
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL);
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f);
|
||||
|
||||
virtual std::string Uri();
|
||||
|
||||
virtual int GetValue(const char* key, char* dst, int size);
|
||||
virtual int Uri(char* dst, int size);
|
||||
|
||||
|
@ -37,6 +37,8 @@
|
||||
|
||||
using namespace musik::core;
|
||||
|
||||
/* * * * RetainedTrack * * * */
|
||||
|
||||
RetainedTrack::RetainedTrack(TrackPtr track) {
|
||||
this->count = 1;
|
||||
this->track = track;
|
||||
@ -46,13 +48,18 @@ RetainedTrack::~RetainedTrack() {
|
||||
}
|
||||
|
||||
void RetainedTrack::Release() {
|
||||
if (this->count > 0) {
|
||||
int c = this->count.fetch_sub(1);
|
||||
if (c > 0) {
|
||||
this->count = 0;
|
||||
this->track.reset();
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void RetainedTrack::Retain() {
|
||||
++this->count;
|
||||
}
|
||||
|
||||
int RetainedTrack::GetValue(const char* key, char* dst, int size) {
|
||||
return track->GetValue(key, dst, size);
|
||||
}
|
||||
@ -69,11 +76,11 @@ long long RetainedTrack::GetInt64(const char* key, long long defaultValue) {
|
||||
return track->GetInt64(key, defaultValue);
|
||||
}
|
||||
|
||||
unsigned long RetainedTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
unsigned int RetainedTrack::GetUint32(const char* key, unsigned long defaultValue) {
|
||||
return track->GetUint32(key, defaultValue);
|
||||
}
|
||||
|
||||
long RetainedTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
int RetainedTrack::GetInt32(const char* key, unsigned int defaultValue) {
|
||||
return track->GetInt32(key, defaultValue);
|
||||
}
|
||||
|
||||
@ -84,3 +91,38 @@ double RetainedTrack::GetDouble(const char* key, double defaultValue) {
|
||||
unsigned long long RetainedTrack::GetId() {
|
||||
return track->GetId();
|
||||
}
|
||||
|
||||
/* * * * RetainedTrackWriter * * * */
|
||||
|
||||
RetainedTrackWriter::RetainedTrackWriter(TrackPtr track) {
|
||||
this->count = 1;
|
||||
this->track = track;
|
||||
}
|
||||
|
||||
RetainedTrackWriter::~RetainedTrackWriter() {
|
||||
}
|
||||
|
||||
void RetainedTrackWriter::Release() {
|
||||
int c = this->count.fetch_sub(1);
|
||||
if (c > 0) {
|
||||
this->count = 0;
|
||||
this->track.reset();
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void RetainedTrackWriter::Retain() {
|
||||
++this->count;
|
||||
}
|
||||
|
||||
void RetainedTrackWriter::SetValue(const char* metakey, const char* value) {
|
||||
this->track->SetValue(metakey, value);
|
||||
}
|
||||
|
||||
void RetainedTrackWriter::ClearValue(const char* metakey) {
|
||||
this->track->ClearValue(metakey);
|
||||
}
|
||||
|
||||
void RetainedTrackWriter::SetThumbnail(const char *data, long size) {
|
||||
this->track->SetThumbnail(data, size);
|
||||
}
|
@ -33,6 +33,7 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <core/sdk/IRetainedTrack.h>
|
||||
#include <core/sdk/IRetainedTrackWriter.h>
|
||||
#include "Track.h"
|
||||
#include <atomic>
|
||||
|
||||
@ -43,16 +44,18 @@ namespace musik { namespace core {
|
||||
RetainedTrack(TrackPtr track);
|
||||
virtual ~RetainedTrack();
|
||||
|
||||
virtual unsigned long long GetId();
|
||||
/* IRetainedTrack */
|
||||
virtual void Release();
|
||||
virtual void Retain();
|
||||
|
||||
/* ITrack */
|
||||
virtual unsigned long long GetId();
|
||||
virtual int GetValue(const char* key, char* dst, int size);
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL);
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL);
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0);
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0);
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f);
|
||||
|
||||
virtual int Uri(char* dst, int size);
|
||||
|
||||
private:
|
||||
@ -60,5 +63,28 @@ namespace musik { namespace core {
|
||||
TrackPtr track;
|
||||
};
|
||||
|
||||
class RetainedTrackWriter : public musik::core::sdk::IRetainedTrackWriter {
|
||||
public:
|
||||
RetainedTrackWriter(TrackPtr track);
|
||||
virtual ~RetainedTrackWriter();
|
||||
|
||||
template <typename T> T As() {
|
||||
return dynamic_cast<T>(track.get());
|
||||
}
|
||||
|
||||
/* IRetainedTrackWriter */
|
||||
virtual void Release();
|
||||
virtual void Retain();
|
||||
|
||||
/* ITrackWriter */
|
||||
virtual void SetValue(const char* metakey, const char* value);
|
||||
virtual void ClearValue(const char* metakey);
|
||||
virtual void SetThumbnail(const char *data, long size);
|
||||
|
||||
private:
|
||||
std::atomic<int> count;
|
||||
TrackPtr track;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
|
@ -66,13 +66,18 @@ namespace musik { namespace core {
|
||||
virtual std::string GetValue(const char* metakey) = 0;
|
||||
virtual std::string Uri() = 0;
|
||||
|
||||
/* IWritableTrack */
|
||||
virtual void SetValue(const char* metakey, const char* value) = 0;
|
||||
virtual void ClearValue(const char* metakey) = 0;
|
||||
virtual void SetThumbnail(const char *data, long size) = 0;
|
||||
|
||||
/* ITrack */
|
||||
virtual int GetValue(const char* key, char* dst, int size) = 0;
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL) = 0;
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL) = 0;
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f) = 0;
|
||||
|
||||
virtual int Uri(char* dst, int size) = 0;
|
||||
|
||||
virtual MetadataIteratorRange GetValues(const char* metakey) = 0;
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <core/library/track/LibraryTrack.h>
|
||||
#include <core/library/LocalLibraryConstants.h>
|
||||
#include <core/library/track/RetainedTrack.h>
|
||||
#include <core/library/query/local/TrackMetadataQuery.h>
|
||||
#include <core/db/Connection.h>
|
||||
#include <core/db/Statement.h>
|
||||
|
||||
@ -49,26 +50,9 @@
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::db;
|
||||
using namespace musik::core::library;
|
||||
|
||||
using namespace musik::core::db::local;
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
class TrackMetadataQuery : public LocalQueryBase {
|
||||
public:
|
||||
TrackMetadataQuery(DBID trackId, ILibraryPtr library);
|
||||
virtual ~TrackMetadataQuery() { }
|
||||
|
||||
TrackPtr Result() { return this->result; }
|
||||
|
||||
protected:
|
||||
virtual bool OnRun(Connection& db);
|
||||
virtual std::string Name() { return "TrackMetadataQuery"; }
|
||||
|
||||
private:
|
||||
DBID trackId;
|
||||
ILibraryPtr library;
|
||||
TrackPtr result;
|
||||
};
|
||||
|
||||
TrackList::TrackList(ILibraryPtr library) {
|
||||
this->library = library;
|
||||
}
|
||||
@ -133,11 +117,14 @@ TrackPtr TrackList::Get(size_t index) const {
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto target = TrackPtr(new LibraryTrack(id, this->library));
|
||||
|
||||
std::shared_ptr<TrackMetadataQuery> query(
|
||||
new TrackMetadataQuery(id, this->library));
|
||||
new TrackMetadataQuery(target, this->library));
|
||||
|
||||
this->library->Enqueue(query, ILibrary::QuerySynchronous);
|
||||
this->AddToCache(id, query->Result());
|
||||
|
||||
return query->Result();
|
||||
}
|
||||
catch (...) {
|
||||
@ -221,45 +208,3 @@ void TrackList::AddToCache(DBID key, TrackPtr value) const {
|
||||
}
|
||||
}
|
||||
|
||||
TrackMetadataQuery::TrackMetadataQuery(DBID trackId, ILibraryPtr library) {
|
||||
this->trackId = trackId;
|
||||
this->library = library;
|
||||
}
|
||||
|
||||
bool TrackMetadataQuery::OnRun(Connection& db) {
|
||||
static const std::string query =
|
||||
"SELECT DISTINCT t.id, t.track, t.disc, t.bpm, t.duration, t.filesize, t.year, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id "
|
||||
"FROM tracks t, paths p, albums al, artists alar, artists ar, genres gn "
|
||||
"WHERE t.id=? AND t.album_id=al.id AND t.album_artist_id=alar.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
|
||||
|
||||
Statement trackQuery(query.c_str(), db);
|
||||
trackQuery.BindInt(0, this->trackId);
|
||||
|
||||
if (trackQuery.Step() == Row) {
|
||||
DBID id = trackQuery.ColumnInt64(0);
|
||||
TrackPtr track = TrackPtr(new LibraryTrack(id, this->library));
|
||||
track->SetValue(constants::Track::TRACK_NUM, trackQuery.ColumnText(1));
|
||||
track->SetValue(constants::Track::DISC_NUM, trackQuery.ColumnText(2));
|
||||
track->SetValue(constants::Track::BPM, trackQuery.ColumnText(3));
|
||||
track->SetValue(constants::Track::DURATION, trackQuery.ColumnText(4));
|
||||
track->SetValue(constants::Track::FILESIZE, trackQuery.ColumnText(5));
|
||||
track->SetValue(constants::Track::YEAR, trackQuery.ColumnText(6));
|
||||
track->SetValue(constants::Track::TITLE, trackQuery.ColumnText(7));
|
||||
track->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(8));
|
||||
track->SetValue(constants::Track::THUMBNAIL_ID, trackQuery.ColumnText(9));
|
||||
track->SetValue(constants::Track::ALBUM, trackQuery.ColumnText(10));
|
||||
track->SetValue(constants::Track::ALBUM_ARTIST, trackQuery.ColumnText(11));
|
||||
track->SetValue(constants::Track::GENRE, trackQuery.ColumnText(12));
|
||||
track->SetValue(constants::Track::ARTIST, trackQuery.ColumnText(13));
|
||||
track->SetValue(constants::Track::FILETIME, trackQuery.ColumnText(14));
|
||||
track->SetValue(constants::Track::GENRE_ID, trackQuery.ColumnText(15));
|
||||
track->SetValue(constants::Track::ARTIST_ID, trackQuery.ColumnText(16));
|
||||
track->SetValue(constants::Track::ALBUM_ARTIST_ID, trackQuery.ColumnText(17));
|
||||
track->SetValue(constants::Track::ALBUM_ID, trackQuery.ColumnText(18));
|
||||
|
||||
this->result = track;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -38,16 +38,20 @@
|
||||
|
||||
#include <core/support/Preferences.h>
|
||||
#include <core/library/LocalSimpleDataProvider.h>
|
||||
#include <core/sdk/IIndexerNotifier.h>
|
||||
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::db::local;
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
typedef void(*SetSimpleDataProvider)(musik::core::sdk::ISimpleDataProvider*);
|
||||
typedef void(*SetSimpleDataProvider)(ISimpleDataProvider*);
|
||||
LocalSimpleDataProvider* dataProvider = nullptr;
|
||||
|
||||
typedef void(*SetIndexerNotifier)(IIndexerNotifier*);
|
||||
|
||||
namespace musik { namespace core { namespace plugin {
|
||||
|
||||
void InstallDependencies(musik::core::ILibraryPtr library) {
|
||||
void InstallDependencies(ILibraryPtr library) {
|
||||
/* preferences */
|
||||
Preferences::LoadPluginPreferences();
|
||||
|
||||
@ -60,11 +64,23 @@ namespace musik { namespace core { namespace plugin {
|
||||
[](musik::core::sdk::IPlugin* plugin, SetSimpleDataProvider func) {
|
||||
func(dataProvider);
|
||||
});
|
||||
|
||||
/* indexer */
|
||||
IIndexerNotifier* indexerNotifier =
|
||||
dynamic_cast<IIndexerNotifier*>(library->Indexer());
|
||||
|
||||
PluginFactory::Instance().QueryFunction<SetIndexerNotifier>(
|
||||
"SetIndexerNotifier",
|
||||
[indexerNotifier](musik::core::sdk::IPlugin* plugin, SetIndexerNotifier func) {
|
||||
func(indexerNotifier);
|
||||
});
|
||||
}
|
||||
|
||||
void UninstallDependencies() {
|
||||
/* preferences */
|
||||
Preferences::SavePluginPreferences();
|
||||
|
||||
/* data providers */
|
||||
PluginFactory::Instance().QueryFunction<SetSimpleDataProvider>(
|
||||
"SetSimpleDataProvider",
|
||||
[](musik::core::sdk::IPlugin* plugin, SetSimpleDataProvider func) {
|
||||
@ -73,6 +89,13 @@ namespace musik { namespace core { namespace plugin {
|
||||
|
||||
delete dataProvider;
|
||||
dataProvider = nullptr;
|
||||
|
||||
/* indexer */
|
||||
PluginFactory::Instance().QueryFunction<SetIndexerNotifier>(
|
||||
"SetIndexerNotifier",
|
||||
[](musik::core::sdk::IPlugin* plugin, SetIndexerNotifier func) {
|
||||
func(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
} } }
|
||||
|
46
src/core/sdk/IIndexerNotifier.h
Normal file
46
src/core/sdk/IIndexerNotifier.h
Normal file
@ -0,0 +1,46 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IIndexerSource.h"
|
||||
|
||||
namespace musik { namespace core { namespace sdk {
|
||||
|
||||
class IIndexerNotifier {
|
||||
public:
|
||||
virtual void ScheduleRescan(IIndexerSource* source) = 0;
|
||||
};
|
||||
|
||||
} } }
|
60
src/core/sdk/IIndexerSource.h
Normal file
60
src/core/sdk/IIndexerSource.h
Normal file
@ -0,0 +1,60 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IIndexerWriter.h"
|
||||
#include "ITrackWriter.h"
|
||||
|
||||
namespace musik { namespace core { namespace sdk {
|
||||
|
||||
class IIndexerSource {
|
||||
public:
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
virtual void OnBeforeScan() = 0;
|
||||
|
||||
virtual void OnAfterScan() = 0;
|
||||
|
||||
virtual void Scan(IIndexerWriter* indexer) = 0;
|
||||
|
||||
virtual void Scan(
|
||||
IIndexerWriter* indexer,
|
||||
IRetainedTrackWriter* track,
|
||||
const char* externalId) = 0;
|
||||
|
||||
virtual int SourceId() = 0;
|
||||
};
|
||||
|
||||
} } }
|
57
src/core/sdk/IIndexerWriter.h
Normal file
57
src/core/sdk/IIndexerWriter.h
Normal file
@ -0,0 +1,57 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IRetainedTrackWriter.h"
|
||||
|
||||
namespace musik { namespace core { namespace sdk {
|
||||
|
||||
class IIndexerSource;
|
||||
|
||||
class IIndexerWriter {
|
||||
public:
|
||||
virtual IRetainedTrackWriter* CreateWriter() = 0;
|
||||
|
||||
virtual bool Save(
|
||||
IIndexerSource* source,
|
||||
IRetainedTrackWriter* track,
|
||||
const char* externalId = "") = 0;
|
||||
|
||||
virtual bool RemoveByUri(IIndexerSource* source, const char* uri) = 0;
|
||||
virtual bool RemoveByExternalId(IIndexerSource* source, const char* id) = 0;
|
||||
virtual int RemoveAll(IIndexerSource* source) = 0;
|
||||
};
|
||||
|
||||
} } }
|
@ -43,8 +43,8 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual int GetValue(const char* key, char* dst, int size) = 0;
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL) = 0;
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL) = 0;
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f) = 0;
|
||||
virtual const char* GetDescription() = 0;
|
||||
virtual const char* GetType() = 0;
|
||||
|
@ -43,6 +43,7 @@ namespace musik {
|
||||
class IRetainedTrack : public ITrack {
|
||||
public:
|
||||
virtual void Release() = 0;
|
||||
virtual void Retain() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
52
src/core/sdk/IRetainedTrackWriter.h
Normal file
52
src/core/sdk/IRetainedTrackWriter.h
Normal file
@ -0,0 +1,52 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2016 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ITrackWriter.h"
|
||||
|
||||
namespace musik {
|
||||
namespace core {
|
||||
namespace sdk {
|
||||
|
||||
class IRetainedTrackWriter : public ITrackWriter {
|
||||
public:
|
||||
virtual void Release() = 0;
|
||||
virtual void Retain() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual int GetValue(const char* key, char* dst, int size) = 0;
|
||||
virtual unsigned long long GetUint64(const char* key, unsigned long long defaultValue = 0ULL) = 0;
|
||||
virtual long long GetInt64(const char* key, long long defaultValue = 0LL) = 0;
|
||||
virtual unsigned long GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual long GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual unsigned int GetUint32(const char* key, unsigned long defaultValue = 0) = 0;
|
||||
virtual int GetInt32(const char* key, unsigned int defaultValue = 0) = 0;
|
||||
virtual double GetDouble(const char* key, double defaultValue = 0.0f) = 0;
|
||||
virtual int Uri(char* dst, int size) = 0;
|
||||
};
|
||||
|
@ -108,5 +108,5 @@ namespace musik {
|
||||
static const char* AlbumId = "album_id";
|
||||
}
|
||||
|
||||
static const int SdkVersion = 4;
|
||||
static const int SdkVersion = 5;
|
||||
} } }
|
@ -125,6 +125,9 @@ int main(int argc, char* argv[])
|
||||
ILibraryPtr library = LibraryFactory::Libraries().at(0);
|
||||
library->SetMessageQueue(Window::MessageQueue());
|
||||
|
||||
auto prefs = Preferences::ForComponent(
|
||||
musik::core::prefs::components::Settings);
|
||||
|
||||
MasterTransport transport;
|
||||
PlaybackService playback(Window::MessageQueue(), library, transport);
|
||||
|
||||
@ -138,9 +141,10 @@ int main(int argc, char* argv[])
|
||||
#ifdef WIN32
|
||||
app.SetIcon(IDI_ICON1);
|
||||
#endif
|
||||
|
||||
auto prefs = Preferences::ForComponent(
|
||||
musik::core::prefs::components::Settings);
|
||||
/* fire up the indexer if configured to run on startup */
|
||||
if (prefs->GetBool(musik::core::prefs::keys::SyncOnStartup, true)) {
|
||||
library->Indexer()->Schedule(IIndexer::SyncType::Sources);
|
||||
}
|
||||
|
||||
/* set color mode (basic, palette, rgb) */
|
||||
Colors::Mode colorMode = Colors::RGB;
|
||||
|
@ -208,8 +208,11 @@ bool BrowseLayout::KeyPress(const std::string& key) {
|
||||
/* if the tracklist is NOT focused (i.e. the focus is on a
|
||||
category window), start playback from the top. */
|
||||
if (this->GetFocus() != this->trackList) {
|
||||
playback.Play(*trackList->GetTrackList(), 0);
|
||||
return true;
|
||||
auto tracks = trackList->GetTrackList();
|
||||
if (tracks) {
|
||||
playback.Play(*tracks.get(), 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Hotkeys::Is(Hotkeys::ViewRefresh, key)) {
|
||||
|
@ -243,7 +243,7 @@ bool ConsoleLayout::ProcessCommand(const std::string& cmd) {
|
||||
this->WriteOutput("");
|
||||
}
|
||||
else if (name == "rescan" || name == "scan" || name == "index") {
|
||||
library->Indexer()->Synchronize();
|
||||
library->Indexer()->Schedule(IIndexer::SyncType::All);
|
||||
}
|
||||
else if (name == "h" || name == "help") {
|
||||
this->Help();
|
||||
|
@ -96,9 +96,9 @@ SettingsLayout::SettingsLayout(
|
||||
: LayoutBase()
|
||||
, library(library)
|
||||
, indexer(library->Indexer())
|
||||
, transport(transport) {
|
||||
, transport(transport)
|
||||
, pathsUpdated(false) {
|
||||
this->libraryPrefs = Preferences::ForComponent(core::prefs::components::Settings);
|
||||
this->indexer->PathsUpdated.connect(this, &SettingsLayout::RefreshAddedPaths);
|
||||
this->browseAdapter.reset(new DirectoryAdapter());
|
||||
this->addedPathsAdapter.reset(new SimpleScrollAdapter());
|
||||
this->InitializeWindows();
|
||||
@ -361,6 +361,12 @@ void SettingsLayout::OnVisibilityChanged(bool visible) {
|
||||
this->LoadPreferences();
|
||||
this->CheckShowFirstRunDialog();
|
||||
}
|
||||
else {
|
||||
if (this->pathsUpdated) {
|
||||
this->library->Indexer()->Schedule(IIndexer::SyncType::Local);
|
||||
this->pathsUpdated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsLayout::CheckShowFirstRunDialog() {
|
||||
@ -444,6 +450,8 @@ void SettingsLayout::AddSelectedDirectory() {
|
||||
|
||||
if (path.size()) {
|
||||
this->indexer->AddPath(path);
|
||||
this->RefreshAddedPaths();
|
||||
this->pathsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -455,6 +463,8 @@ void SettingsLayout::RemoveSelectedDirectory() {
|
||||
size_t index = this->addedPathsList->GetSelectedIndex();
|
||||
if (index != ListWindow::NO_SELECTION) {
|
||||
this->indexer->RemovePath(paths.at(index));
|
||||
this->RefreshAddedPaths();
|
||||
this->pathsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +136,8 @@ namespace musik {
|
||||
|
||||
std::shared_ptr<cursespp::SimpleScrollAdapter> addedPathsAdapter;
|
||||
std::shared_ptr<DirectoryAdapter> browseAdapter;
|
||||
|
||||
bool pathsUpdated = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,7 @@
|
||||
|
||||
#include <glue/util/Playback.h>
|
||||
|
||||
using musik::core::ILibraryPtr;
|
||||
using musik::core::audio::ITransport;
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::audio;
|
||||
using namespace musik::core::sdk;
|
||||
using namespace musik::box;
|
||||
@ -105,7 +104,7 @@ bool GlobalHotkeys::Handle(const std::string& kn) {
|
||||
return true;
|
||||
}
|
||||
else if (Hotkeys::Is(Hotkeys::RescanMetadata, kn)) {
|
||||
library->Indexer()->Synchronize(true);
|
||||
library->Indexer()->Schedule(IIndexer::SyncType::All);
|
||||
return true;
|
||||
}
|
||||
else if (Hotkeys::Is(Hotkeys::ToggleVisualizer, kn)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user