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:
casey langen 2017-03-31 00:48:36 -07:00
parent c5e57094d5
commit 2dac91c429
45 changed files with 1210 additions and 402 deletions

View File

@ -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

View File

@ -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" />

View File

@ -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>

View File

@ -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)

View File

@ -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();

View File

@ -78,7 +78,7 @@ void ScopedTransaction::End() {
}
else {
this->connection->Execute("COMMIT TRANSACTION");
this->connection->Checkpoint();
//this->connection->Checkpoint();
}
}

View File

@ -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);
}

View File

@ -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;
};
} } }

View File

@ -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;
};
} }

View File

@ -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 &currentPath,
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);
}
}

View File

@ -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;
};

View File

@ -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)");
}

View File

@ -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... */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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) {

View File

@ -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 =

View File

@ -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 ?) ";

View File

@ -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();

View File

@ -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 ";
}

View 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;
}

View 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;
};
} } } }

View File

@ -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();
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
};
} }

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
});
}
} } }

View 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;
};
} } }

View 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;
};
} } }

View 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;
};
} } }

View File

@ -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;

View File

@ -43,6 +43,7 @@ namespace musik {
class IRetainedTrack : public ITrack {
public:
virtual void Release() = 0;
virtual void Retain() = 0;
};
}

View 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;
};
}
}
}

View File

@ -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;
};

View File

@ -108,5 +108,5 @@ namespace musik {
static const char* AlbumId = "album_id";
}
static const int SdkVersion = 4;
static const int SdkVersion = 5;
} } }

View File

@ -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;

View File

@ -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)) {

View File

@ -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();

View File

@ -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;
}
}

View File

@ -136,6 +136,8 @@ namespace musik {
std::shared_ptr<cursespp::SimpleScrollAdapter> addedPathsAdapter;
std::shared_ptr<DirectoryAdapter> browseAdapter;
bool pathsUpdated = false;
};
}
}

View File

@ -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)) {