diff --git a/build/depends.py b/build/depends.py index 6e0dee6ecc72..b8818beb8674 100644 --- a/build/depends.py +++ b/build/depends.py @@ -885,6 +885,10 @@ def sources(self, build): "widget/wlibrarytableview.cpp", "widget/wanalysislibrarytableview.cpp", "widget/wlibrarytextbrowser.cpp", + + "database/mixxxdb.cpp", + "database/schemamanager.cpp", + "library/trackcollection.cpp", "library/basesqltablemodel.cpp", "library/basetrackcache.cpp", @@ -969,7 +973,6 @@ def sources(self, build): "library/dao/autodjcratesdao.cpp", "library/librarycontrol.cpp", - "library/schemamanager.cpp", "library/songdownloader.cpp", "library/starrating.cpp", "library/stardelegate.cpp", @@ -1117,6 +1120,9 @@ def sources(self, build): "util/movinginterquartilemean.cpp", "util/console.cpp", "util/db/dbconnection.cpp", + "util/db/dbconnectionpool.cpp", + "util/db/dbconnectionpooler.cpp", + "util/db/dbconnectionpooled.cpp", "util/db/dbid.cpp", "util/db/fwdsqlquery.cpp", "util/db/fwdsqlqueryselectresult.cpp", diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index 3d55479d6c59..6ef487513afe 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -1,10 +1,5 @@ #include "analyzer/analyzerqueue.h" -#include - -#include -#include - #ifdef __VAMP__ #include "analyzer/analyzerbeats.h" #include "analyzer/analyzerkey.h" @@ -12,14 +7,15 @@ #include "analyzer/analyzergain.h" #include "analyzer/analyzerebur128.h" #include "analyzer/analyzerwaveform.h" -#include "library/trackcollection.h" #include "mixer/playerinfo.h" #include "sources/soundsourceproxy.h" #include "track/track.h" #include "util/compatibility.h" +#include "util/db/dbconnectionpooler.h" #include "util/event.h" #include "util/timer.h" #include "util/trace.h" +#include "util/logger.h" // Measured in 0.1%, // 0 for no progress during finalize @@ -28,46 +24,52 @@ #define FINALIZE_PROMILLE 1 namespace { - // Analysis is done in blocks. - // We need to use a smaller block size, because on Linux the AnalyzerQueue - // can starve the CPU of its resources, resulting in xruns. A block size - // of 4096 frames per block seems to do fine. - const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; - const SINT kAnalysisFramesPerBlock = 4096; - const SINT kAnalysisSamplesPerBlock = - kAnalysisFramesPerBlock * kAnalysisChannels; + +mixxx::Logger kLogger("AnalyzerQueue"); + +// Analysis is done in blocks. +// We need to use a smaller block size, because on Linux the AnalyzerQueue +// can starve the CPU of its resources, resulting in xruns. A block size +// of 4096 frames per block seems to do fine. +const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; +const SINT kAnalysisFramesPerBlock = 4096; +const SINT kAnalysisSamplesPerBlock = + kAnalysisFramesPerBlock * kAnalysisChannels; + +QAtomicInt s_instanceCounter(0); + } // anonymous namespace -AnalyzerQueue::AnalyzerQueue(TrackCollection* pTrackCollection) - : m_aq(), +AnalyzerQueue::AnalyzerQueue( + mixxx::DbConnectionPoolPtr pDbConnectionPool, + const UserSettingsPointer& pConfig, + Mode mode) + : m_pDbConnectionPool(std::move(pDbConnectionPool)), m_exit(false), m_aiCheckPriorities(false), m_sampleBuffer(kAnalysisSamplesPerBlock), - m_tioq(), - m_qm(), - m_qwait(), m_queue_size(0) { - Q_UNUSED(pTrackCollection); + + if (mode != Mode::WithoutWaveform) { + m_pAnalyzers.push_back(std::make_unique(pConfig)); + } + m_pAnalyzers.push_back(std::make_unique(pConfig)); + m_pAnalyzers.push_back(std::make_unique(pConfig)); +#ifdef __VAMP__ + m_pAnalyzers.push_back(std::make_unique(pConfig)); + m_pAnalyzers.push_back(std::make_unique(pConfig)); +#endif + connect(this, SIGNAL(updateProgress()), this, SLOT(slotUpdateProgress())); + + start(QThread::LowPriority); } AnalyzerQueue::~AnalyzerQueue() { stop(); m_progressInfo.sema.release(); wait(); //Wait until thread has actually stopped before proceeding. - - QListIterator it(m_aq); - while (it.hasNext()) { - Analyzer* an = it.next(); - //qDebug() << "AnalyzerQueue: deleting " << typeid(an).name(); - delete an; - } - //qDebug() << "AnalyzerQueue::~AnalyzerQueue()"; -} - -void AnalyzerQueue::addAnalyzer(Analyzer* an) { - m_aq.push_back(an); } // This is called from the AnalyzerQueue thread @@ -95,10 +97,9 @@ bool AnalyzerQueue::isLoadedTrackWaiting(TrackPointer analysingTrack) { int progress = pTrack->getAnalyzerProgress(); if (progress < 0) { // Load stored analysis - QListIterator ita(m_aq); bool processTrack = false; - while (ita.hasNext()) { - if (!ita.next()->isDisabledOrLoadStoredSuccess(pTrack)) { + for (auto const& pAnalyzer: m_pAnalyzers) { + if (!pAnalyzer->isDisabledOrLoadStoredSuccess(pTrack)) { processTrack = true; } } @@ -154,7 +155,7 @@ TrackPointer AnalyzerQueue::dequeueNextBlocking() { } // Prioritize tracks that are loaded. if (info.isTrackLoaded(pTrack)) { - qDebug() << "Prioritizing" << pTrack->getTitle() << pTrack->getLocation(); + kLogger.debug() << "Prioritizing" << pTrack->getTitle() << pTrack->getLocation(); pLoadTrack = pTrack; it.remove(); break; @@ -169,7 +170,7 @@ TrackPointer AnalyzerQueue::dequeueNextBlocking() { m_qm.unlock(); if (pLoadTrack) { - qDebug() << "Analyzing" << pLoadTrack->getTitle() << pLoadTrack->getLocation(); + kLogger.debug() << "Analyzing" << pLoadTrack->getTitle() << pLoadTrack->getLocation(); } // pTrack might be NULL, up to the caller to check. return pLoadTrack; @@ -206,12 +207,8 @@ bool AnalyzerQueue::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudi // the full block size. if (kAnalysisFramesPerBlock == framesRead) { // Complete analysis block of audio samples has been read. - QListIterator it(m_aq); - while (it.hasNext()) { - Analyzer* an = it.next(); - //qDebug() << typeid(*an).name() << ".process()"; - an->process(m_sampleBuffer.data(), m_sampleBuffer.size()); - //qDebug() << "Done " << typeid(*an).name() << ".process()"; + for (auto const& pAnalyzer: m_pAnalyzers) { + pAnalyzer->process(m_sampleBuffer.data(), m_sampleBuffer.size()); } } else { // Partial analysis block of audio samples has been read. @@ -219,7 +216,7 @@ bool AnalyzerQueue::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudi // otherwise a decoding error must have occurred. if (frameIndex < pAudioSource->getMaxFrameIndex()) { // EOF not reached -> Maybe a corrupt file? - qWarning() << "Failed to read sample data from file:" + kLogger.warning() << "Failed to read sample data from file:" << tio->getLocation() << "@" << frameIndex; if (0 >= framesRead) { @@ -258,7 +255,7 @@ bool AnalyzerQueue::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudi // has something new entered the queue? if (m_aiCheckPriorities.fetchAndStoreAcquire(false)) { if (isLoadedTrackWaiting(tio)) { - qDebug() << "Interrupting analysis to give preference to a loaded track."; + kLogger.debug() << "Interrupting analysis to give preference to a loaded track."; dieflag = true; cancelled = true; } @@ -286,12 +283,29 @@ void AnalyzerQueue::stop() { } void AnalyzerQueue::run() { - unsigned static id = 0; // the id of this thread, for debugging purposes - QThread::currentThread()->setObjectName(QString("AnalyzerQueue %1").arg(++id)); - // If there are no analyzers, don't waste time running. - if (m_aq.size() == 0) + if (m_pAnalyzers.empty()) { + return; + } + + const int instanceId = s_instanceCounter.fetchAndAddAcquire(1) + 1; + QThread::currentThread()->setObjectName(QString("AnalyzerQueue %1").arg(instanceId)); + + kLogger.debug() << "Entering thread"; + + execThread(); + + kLogger.debug() << "Exiting thread"; +} + +void AnalyzerQueue::execThread() { + const mixxx::DbConnectionPooler dbConnection(m_pDbConnectionPool); + if (!dbConnection) { + kLogger.warning() + << "Failed to open database connection for analyzer queue"; + kLogger.debug() << "Exiting thread"; return; + } m_progressInfo.current_track.reset(); m_progressInfo.track_progress = 0; @@ -323,16 +337,15 @@ void AnalyzerQueue::run() { audioSrcCfg.setChannelCount(kAnalysisChannels); mixxx::AudioSourcePointer pAudioSource(soundSourceProxy.openAudioSource(audioSrcCfg)); if (!pAudioSource) { - qWarning() << "Failed to open file for analyzing:" << nextTrack->getLocation(); + kLogger.warning() << "Failed to open file for analyzing:" << nextTrack->getLocation(); emptyCheck(); continue; } - QListIterator it(m_aq); bool processTrack = false; - while (it.hasNext()) { + for (auto const& pAnalyzer: m_pAnalyzers) { // Make sure not to short-circuit initialize(...) - if (it.next()->initialize(nextTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { + if (pAnalyzer->initialize(nextTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { processTrack = true; } } @@ -346,9 +359,8 @@ void AnalyzerQueue::run() { bool completed = doAnalysis(nextTrack, pAudioSource); if (!completed) { // This track was cancelled - QListIterator itf(m_aq); - while (itf.hasNext()) { - itf.next()->cleanup(nextTrack); + for (auto const& pAnalyzer: m_pAnalyzers) { + pAnalyzer->cleanup(nextTrack); } queueAnalyseTrack(nextTrack); emitUpdateProgress(nextTrack, 0); @@ -356,16 +368,15 @@ void AnalyzerQueue::run() { // 100% - FINALIZE_PERCENT finished emitUpdateProgress(nextTrack, 1000 - FINALIZE_PROMILLE); // This takes around 3 sec on a Atom Netbook - QListIterator itf(m_aq); - while (itf.hasNext()) { - itf.next()->finalize(nextTrack); + for (auto const& pAnalyzer: m_pAnalyzers) { + pAnalyzer->finalize(nextTrack); } emit(trackDone(nextTrack)); emitUpdateProgress(nextTrack, 1000); // 100% } } else { emitUpdateProgress(nextTrack, 1000); // 100% - qDebug() << "Skipping track analysis because no analyzer initialized."; + kLogger.debug() << "Skipping track analysis because no analyzer initialized."; } emptyCheck(); } @@ -431,39 +442,3 @@ void AnalyzerQueue::queueAnalyseTrack(TrackPointer tio) { } m_qm.unlock(); } - -// static -AnalyzerQueue* AnalyzerQueue::createDefaultAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { - AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - - ret->addAnalyzer(new AnalyzerWaveform(pConfig)); - ret->addAnalyzer(new AnalyzerGain(pConfig)); - ret->addAnalyzer(new AnalyzerEbur128(pConfig)); -#ifdef __VAMP__ - ret->addAnalyzer(new AnalyzerBeats(pConfig)); - ret->addAnalyzer(new AnalyzerKey(pConfig)); -#endif - - ret->start(QThread::LowPriority); - return ret; -} - -// static -AnalyzerQueue* AnalyzerQueue::createAnalysisFeatureAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { - AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - - if (pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { - ret->addAnalyzer(new AnalyzerWaveform(pConfig)); - } - ret->addAnalyzer(new AnalyzerGain(pConfig)); - ret->addAnalyzer(new AnalyzerEbur128(pConfig)); -#ifdef __VAMP__ - ret->addAnalyzer(new AnalyzerBeats(pConfig)); - ret->addAnalyzer(new AnalyzerKey(pConfig)); -#endif - - ret->start(QThread::LowPriority); - return ret; -} diff --git a/src/analyzer/analyzerqueue.h b/src/analyzer/analyzerqueue.h index 642eca0d9c73..b37341a61db2 100644 --- a/src/analyzer/analyzerqueue.h +++ b/src/analyzer/analyzerqueue.h @@ -1,7 +1,6 @@ #ifndef ANALYZER_ANALYZERQUEUE_H #define ANALYZER_ANALYZERQUEUE_H -#include #include #include #include @@ -9,29 +8,33 @@ #include -#include "analyzer/analyzer.h" #include "preferences/usersettings.h" #include "sources/audiosource.h" #include "track/track.h" +#include "util/db/dbconnectionpool.h" #include "util/samplebuffer.h" +#include "util/memory.h" -class TrackCollection; +class Analyzer; class AnalyzerQueue : public QThread { Q_OBJECT public: - AnalyzerQueue(TrackCollection* pTrackCollection); - virtual ~AnalyzerQueue(); + enum class Mode { + Default, + WithoutWaveform, + }; + + AnalyzerQueue( + mixxx::DbConnectionPoolPtr pDbConnectionPool, + const UserSettingsPointer& pConfig, + Mode mode = Mode::Default); + ~AnalyzerQueue() override; void stop(); void queueAnalyseTrack(TrackPointer tio); - static AnalyzerQueue* createDefaultAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection); - static AnalyzerQueue* createAnalysisFeatureAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection); - public slots: void slotAnalyseTrack(TrackPointer tio); void slotUpdateProgress(); @@ -55,9 +58,12 @@ class AnalyzerQueue : public QThread { QSemaphore sema; }; - void addAnalyzer(Analyzer* an); + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; + + typedef std::unique_ptr AnalyzerPtr; + std::vector m_pAnalyzers; - QList m_aq; + void execThread(); bool isLoadedTrackWaiting(TrackPointer analysingTrack); TrackPointer dequeueNextBlocking(); diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index 7940a14e1f43..19fb2a8663e0 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -1,7 +1,5 @@ #include "analyzer/analyzerwaveform.h" -#include - #include "engine/engineobject.h" #include "engine/enginefilterbutterworth8.h" #include "engine/enginefilterbessel4.h" @@ -9,49 +7,31 @@ #include "library/dao/analysisdao.h" #include "track/track.h" #include "waveform/waveformfactory.h" +#include "util/logger.h" -namespace -{ - QAtomicInt dbIndex(0); -} +namespace { + +mixxx::Logger kLogger("AnalyzerWaveform"); + +} // anonymous -AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig) : +AnalyzerWaveform::AnalyzerWaveform( + const UserSettingsPointer& pConfig) : + m_analysisDao(pConfig), m_skipProcessing(false), m_waveformData(nullptr), m_waveformSummaryData(nullptr), m_stride(0, 0), m_currentStride(0), m_currentSummaryStride(0) { - const int idx = ::dbIndex.fetchAndAddAcquire(1); - qDebug() << "AnalyzerWaveform::AnalyzerWaveform() :" << idx; - m_filter[0] = 0; m_filter[1] = 0; m_filter[2] = 0; - //A new connection different for each thread is needed http://doc.qt.io/qt-4.8/threads-modules.html#threads-and-the-sql-module - m_database = QSqlDatabase::addDatabase("QSQLITE", "WAVEFORM_ANALYSIS" + QString::number(idx)); - if (!m_database.isOpen()) { - m_database.setHostName("localhost"); - m_database.setDatabaseName(QDir(pConfig->getSettingsPath()).filePath("mixxxdb.sqlite")); - m_database.setUserName("mixxx"); - m_database.setPassword("mixxx"); - - //Open the database connection in this thread. - if (!m_database.open()) { - qDebug() << "Failed to open database from analyzer thread." - << m_database.lastError(); - } - } - - m_pAnalysisDao = std::make_unique(m_database, pConfig); } AnalyzerWaveform::~AnalyzerWaveform() { - QString conname = m_database.connectionName(); - qDebug() << "AnalyzerWaveform::~AnalyzerWaveform():" << conname; + kLogger.debug() << "~AnalyzerWaveform():"; destroyFilters(); - m_database.close(); - QSqlDatabase::removeDatabase(conname); } bool AnalyzerWaveform::initialize(TrackPointer tio, int sampleRate, int totalSamples) { @@ -122,7 +102,7 @@ bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { if (trackId.isValid() && (missingWaveform || missingWavesummary)) { QList analyses = - m_pAnalysisDao->getAnalysesForTrack(trackId); + m_analysisDao.getAnalysesForTrack(trackId); QListIterator it(analyses); while (it.hasNext()) { @@ -137,7 +117,7 @@ bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { missingWaveform = false; } else if (vc != WaveformFactory::VC_KEEP) { // remove all other Analysis except that one we should keep - m_pAnalysisDao->deleteAnalysis(analysis.analysisId); + m_analysisDao.deleteAnalysis(analysis.analysisId); } } if (analysis.type == AnalysisDao::TYPE_WAVESUMMARY) { vc = WaveformFactory::waveformSummaryVersionToVersionClass(analysis.version); @@ -147,7 +127,7 @@ bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { missingWavesummary = false; } else if (vc != WaveformFactory::VC_KEEP) { // remove all other Analysis except that one we should keep - m_pAnalysisDao->deleteAnalysis(analysis.analysisId); + m_analysisDao.deleteAnalysis(analysis.analysisId); } } } @@ -155,7 +135,7 @@ bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { // If we don't need to calculate the waveform/wavesummary, skip. if (!missingWaveform && !missingWavesummary) { - qDebug() << "AnalyzerWaveform::loadStored - Stored waveform loaded"; + kLogger.debug() << "loadStored - Stored waveform loaded"; if (pLoadedTrackWaveform) { tio->setWaveform(pLoadedTrackWaveform); } @@ -269,8 +249,8 @@ void AnalyzerWaveform::process(const CSAMPLE* buffer, const int bufferLength) { } } - //qDebug() << "AnalyzerWaveform::process - m_waveform->getCompletion()" << m_waveform->getCompletion() << "off" << m_waveform->getDataSize(); - //qDebug() << "AnalyzerWaveform::process - m_waveformSummary->getCompletion()" << m_waveformSummary->getCompletion() << "off" << m_waveformSummary->getDataSize(); + //kLogger.debug() << "process - m_waveform->getCompletion()" << m_waveform->getCompletion() << "off" << m_waveform->getDataSize(); + //kLogger.debug() << "process - m_waveformSummary->getCompletion()" << m_waveformSummary->getCompletion() << "off" << m_waveformSummary->getDataSize(); } void AnalyzerWaveform::cleanup(TrackPointer tio) { @@ -329,9 +309,9 @@ void AnalyzerWaveform::finalize(TrackPointer tio) { // waveforms (i.e. if the config setting was disabled in a previous scan) // and then it is not called. The other analyzers have signals which control // the update of their data. - m_pAnalysisDao->saveTrackAnalyses(*tio); + m_analysisDao.saveTrackAnalyses(*tio); - qDebug() << "Waveform generation for track" << tio->getId() << "done" + kLogger.debug() << "Waveform generation for track" << tio->getId() << "done" << m_timer.elapsed().debugSecondsWithUnit(); } diff --git a/src/analyzer/analyzerwaveform.h b/src/analyzer/analyzerwaveform.h index 912436c3297d..ccc4bdf7780e 100644 --- a/src/analyzer/analyzerwaveform.h +++ b/src/analyzer/analyzerwaveform.h @@ -2,23 +2,19 @@ #define ANALYZER_ANALYZERWAVEFORM_H #include -#include #include #include "analyzer/analyzer.h" -#include "preferences/usersettings.h" -#include "util/math.h" -#include "util/memory.h" #include "waveform/waveform.h" +#include "library/dao/analysisdao.h" +#include "util/math.h" #include "util/performancetimer.h" //NOTS vrince some test to segment sound, to apply color in the waveform //#define TEST_HEAT_MAP class EngineFilterIIRBase; -class Waveform; -class AnalysisDao; inline CSAMPLE scaleSignal(CSAMPLE invalue, FilterIndex index = FilterCount) { if (invalue == 0.0) { @@ -140,8 +136,9 @@ struct WaveformStride { class AnalyzerWaveform : public Analyzer { public: - AnalyzerWaveform(UserSettingsPointer pConfig); - virtual ~AnalyzerWaveform(); + explicit AnalyzerWaveform( + const UserSettingsPointer& pConfig); + ~AnalyzerWaveform() override; bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override; bool isDisabledOrLoadStoredSuccess(TrackPointer tio) const override; @@ -157,6 +154,8 @@ class AnalyzerWaveform : public Analyzer { void destroyFilters(); void storeIfGreater(float* pDest, float source); + mutable AnalysisDao m_analysisDao; + bool m_skipProcessing; WaveformPointer m_waveform; @@ -173,8 +172,6 @@ class AnalyzerWaveform : public Analyzer { std::vector m_buffers[FilterCount]; PerformanceTimer m_timer; - QSqlDatabase m_database; - std::unique_ptr m_pAnalysisDao; #ifdef TEST_HEAT_MAP QImage* test_heatMap; diff --git a/src/analyzer/vamp/vamppluginloader.cpp b/src/analyzer/vamp/vamppluginloader.cpp index a6522856acfb..771b2fe0936c 100644 --- a/src/analyzer/vamp/vamppluginloader.cpp +++ b/src/analyzer/vamp/vamppluginloader.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "util/logger.h" diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 4e40783098f3..54029398d483 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -476,7 +476,7 @@ bool ControllerManager::importScript(const QString& scriptPath, << scriptPath; } - // The name we will save this file as in our local script repository. The + // The name we will save this file as in our local script mixxxdb. The // conflict resolution logic below will mutate this variable if the name is // already taken. QString scriptFileName = script.fileName(); diff --git a/src/database/mixxxdb.cpp b/src/database/mixxxdb.cpp new file mode 100644 index 000000000000..2046129b445b --- /dev/null +++ b/src/database/mixxxdb.cpp @@ -0,0 +1,86 @@ +#include "database/mixxxdb.h" + +#include "database/schemamanager.h" + +#include "util/assert.h" +#include "util/logger.h" + + +// The schema XML is baked into the binary via Qt resources. +//static +const QString MixxxDb::kDefaultSchemaFile(":/schema.xml"); + +//static +const int MixxxDb::kRequiredSchemaVersion = 27; + +namespace { + +const mixxx::Logger kLogger("MixxxDb"); + +// The connection parameters for the main Mixxx DB +mixxx::DbConnection::Params dbConnectionParams( + const UserSettingsPointer& pConfig) { + mixxx::DbConnection::Params params; + params.type = "QSQLITE"; + params.hostName = "localhost"; + params.filePath = QDir(pConfig->getSettingsPath()).filePath("mixxxdb.sqlite"); + params.userName = "mixxx"; + params.password = "mixxx"; + return params; +} + +} // anonymous namespace + +MixxxDb::MixxxDb( + const UserSettingsPointer& pConfig) + : m_pDbConnectionPool(std::make_shared(dbConnectionParams(pConfig), "MIXXX")) { +} + +bool MixxxDb::initDatabaseSchema( + const QSqlDatabase& database, + const QString& schemaFile, + int schemaVersion) { + QString okToExit = tr("Click OK to exit."); + QString upgradeFailed = tr("Cannot upgrade database schema"); + QString upgradeToVersionFailed = + tr("Unable to upgrade your database schema to version %1") + .arg(QString::number(schemaVersion)); + QString helpEmail = tr("For help with database issues contact:") + "\n" + + "mixxx-devel@lists.sourceforge.net"; + + switch (SchemaManager(database).upgradeToSchemaVersion(schemaFile, schemaVersion)) { + case SchemaManager::Result::CurrentVersion: + case SchemaManager::Result::UpgradeSucceeded: + case SchemaManager::Result::NewerVersionBackwardsCompatible: + return true; // done + case SchemaManager::Result::UpgradeFailed: + QMessageBox::warning( + 0, upgradeFailed, + upgradeToVersionFailed + "\n" + + tr("Your mixxxdb.sqlite file may be corrupt.") + "\n" + + tr("Try renaming it and restarting Mixxx.") + "\n" + + helpEmail + "\n\n" + okToExit, + QMessageBox::Ok); + return false; // abort + case SchemaManager::Result::NewerVersionIncompatible: + QMessageBox::warning( + 0, upgradeFailed, + upgradeToVersionFailed + "\n" + + tr("Your mixxxdb.sqlite file was created by a newer " + "version of Mixxx and is incompatible.") + + "\n\n" + okToExit, + QMessageBox::Ok); + return false; // abort + case SchemaManager::Result::SchemaError: + QMessageBox::warning( + 0, upgradeFailed, + upgradeToVersionFailed + "\n" + + tr("The database schema file is invalid.") + "\n" + + helpEmail + "\n\n" + okToExit, + QMessageBox::Ok); + return false; // abort + } + // Suppress compiler warning + DEBUG_ASSERT(!"unhandled switch/case"); + return false; +} diff --git a/src/database/mixxxdb.h b/src/database/mixxxdb.h new file mode 100644 index 000000000000..4e5fe77182cb --- /dev/null +++ b/src/database/mixxxdb.h @@ -0,0 +1,37 @@ +#ifndef MIXXXDB_H +#define MIXXXDB_H + + +#include + +#include "preferences/usersettings.h" + +#include "util/db/dbconnectionpool.h" + + +class MixxxDb : public QObject { + Q_OBJECT + + public: + static const QString kDefaultSchemaFile; + + static const int kRequiredSchemaVersion; + + static bool initDatabaseSchema( + const QSqlDatabase& database, + const QString& schemaFile = kDefaultSchemaFile, + int schemaVersion = kRequiredSchemaVersion); + + explicit MixxxDb( + const UserSettingsPointer& pConfig); + + mixxx::DbConnectionPoolPtr connectionPool() const { + return m_pDbConnectionPool; + } + + private: + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; +}; + + +#endif // MIXXXDB_H diff --git a/src/database/schemamanager.cpp b/src/database/schemamanager.cpp new file mode 100644 index 000000000000..2e678fc8e1a8 --- /dev/null +++ b/src/database/schemamanager.cpp @@ -0,0 +1,209 @@ +#include "database/schemamanager.h" + +#include "util/db/fwdsqlquery.h" +#include "util/db/sqltransaction.h" +#include "util/xml.h" +#include "util/logger.h" +#include "util/assert.h" + + +const QString SchemaManager::SETTINGS_VERSION_STRING = "mixxx.schema.version"; +const QString SchemaManager::SETTINGS_MINCOMPATIBLE_STRING = "mixxx.schema.min_compatible_version"; + +namespace { + mixxx::Logger kLogger("SchemaManager"); + + int readCurrentSchemaVersion(SettingsDAO& settings) { + QString settingsValue = settings.getValue(SchemaManager::SETTINGS_VERSION_STRING); + // May be a null string if the schema has not been created. We default the + // startVersion to 0 so that we automatically try to upgrade to revision 1. + if (settingsValue.isNull()) { + return 0; // initial version + } else { + bool ok = false; + int schemaVersion = settingsValue.toInt(&ok); + VERIFY_OR_DEBUG_ASSERT(ok && (schemaVersion >= 0)) { + kLogger.critical() + << "Invalid database schema version" << settingsValue; + } + return schemaVersion; + } + } +} + +SchemaManager::SchemaManager(const QSqlDatabase& database) + : m_database(database), + m_settingsDao(database), + m_currentVersion(readCurrentSchemaVersion(m_settingsDao)) { +} + +bool SchemaManager::isBackwardsCompatibleWithVersion(int targetVersion) const { + QString backwardsCompatibleVersion = + m_settingsDao.getValue(SETTINGS_MINCOMPATIBLE_STRING); + bool ok = false; + int iBackwardsCompatibleVersion = backwardsCompatibleVersion.toInt(&ok); + + // If the current backwards compatible schema version is not stored in the + // settings table, assume the current schema version is only backwards + // compatible with itself. + if (backwardsCompatibleVersion.isNull() || !ok) { + // rryan 11/2010 We just added the backwards compatible flags, and some + // people using the Mixxx trunk are already on schema version 7. This + // special case is for them. Schema version 7 is backwards compatible + // with schema version 3. + if (m_currentVersion == 7) { + iBackwardsCompatibleVersion = 3; + } else { + iBackwardsCompatibleVersion = m_currentVersion; + } + } + + // If the target version is greater than the minimum compatible version of + // the current schema, then the current schema is backwards compatible with + // targetVersion. + return iBackwardsCompatibleVersion <= targetVersion; +} + +SchemaManager::Result SchemaManager::upgradeToSchemaVersion( + const QString& schemaFilename, + int targetVersion) { + VERIFY_OR_DEBUG_ASSERT(m_currentVersion >= 0) { + return Result::UpgradeFailed; + } + + if (m_currentVersion == targetVersion) { + kLogger.info() + << "Database schema is up-to-date" + << "at version" << m_currentVersion; + return Result::CurrentVersion; + } else if (m_currentVersion < targetVersion) { + kLogger.info() + << "Upgrading database schema" + << "from version" << m_currentVersion + << "to version" << targetVersion; + } else { + if (isBackwardsCompatibleWithVersion(targetVersion)) { + kLogger.info() + << "Current database schema is newer" + << "at version" << m_currentVersion + << "and backwards compatible" + << "with version" << targetVersion; + return Result::NewerVersionBackwardsCompatible; + } else { + kLogger.warning() + << "Current database schema is newer" + << "at version" << m_currentVersion + << "and incompatible" + << "with version" << targetVersion; + return Result::NewerVersionIncompatible; + } + } + + kLogger.debug() + << "Loading database schema migrations from" + << schemaFilename; + QDomElement schemaRoot = XmlParse::openXMLFile(schemaFilename, "schema"); + if (schemaRoot.isNull()) { + kLogger.critical() + << "Failed to load database schema migrations from" + << schemaFilename; + return Result::SchemaError; + } + + QDomNodeList revisions = schemaRoot.childNodes(); + + QMap revisionMap; + + for (int i = 0; i < revisions.count(); i++) { + QDomElement revision = revisions.at(i).toElement(); + QString version = revision.attribute("version"); + VERIFY_OR_DEBUG_ASSERT(!version.isNull()) { + kLogger.critical() + << "Failed to parse database schema migrations from" + << schemaFilename; + return Result::SchemaError; + } + int iVersion = version.toInt(); + revisionMap[iVersion] = revision; + } + + // The checks above guarantee that currentVersion < targetVersion when we + // get here. + while (m_currentVersion < targetVersion) { + int nextVersion = m_currentVersion + 1; + + // Now that we bake the schema.xml into the binary it is a programming + // error if we include a schema.xml that does not have information on + // how to get all the way to targetVersion. + VERIFY_OR_DEBUG_ASSERT(revisionMap.contains(nextVersion)) { + kLogger.critical() + << "Migration path for upgrading database schema" + << "from version" << m_currentVersion + << "to version" << nextVersion + << "is missing"; + return Result::SchemaError; + } + + QDomElement revision = revisionMap[nextVersion]; + QDomElement eDescription = revision.firstChildElement("description"); + QDomElement eSql = revision.firstChildElement("sql"); + QString minCompatibleVersion = revision.attribute("min_compatible"); + + // Default the min-compatible version to the current version string if + // it's not in the schema.xml + if (minCompatibleVersion.isNull()) { + minCompatibleVersion = QString::number(nextVersion); + } + + VERIFY_OR_DEBUG_ASSERT(!eSql.isNull()) { + kLogger.critical() + << "Failed to parse database schema migrations from" + << schemaFilename; + return Result::SchemaError; + } + + QString description = eDescription.text(); + QString sql = eSql.text(); + + kLogger.info() + << "Upgrading to database schema to version" + << nextVersion << ":" + << description.trimmed(); + + SqlTransaction transaction(m_database); + + // TODO(XXX) We can't have semicolons in schema.xml for anything other + // than statement separators. + QStringList sqlStatements = sql.split(";"); + + QStringListIterator it(sqlStatements); + + bool result = true; + while (result && it.hasNext()) { + QString statement = it.next().trimmed(); + if (statement.isEmpty()) { + // skip blank lines + continue; + } + FwdSqlQuery query(m_database, statement); + result = query.isPrepared() && query.execPrepared(); + } + + if (result) { + m_settingsDao.setValue(SETTINGS_VERSION_STRING, nextVersion); + m_settingsDao.setValue(SETTINGS_MINCOMPATIBLE_STRING, minCompatibleVersion); + transaction.commit(); + m_currentVersion = nextVersion; + kLogger.info() + << "Upgraded database schema" + << "to version" << m_currentVersion; + } else { + kLogger.critical() + << "Failed to upgrade database schema from version" + << m_currentVersion << "to version" << nextVersion; + transaction.rollback(); + return Result::UpgradeFailed; + } + } + return Result::UpgradeSucceeded; +} diff --git a/src/database/schemamanager.h b/src/database/schemamanager.h new file mode 100644 index 000000000000..a43cbd61e21c --- /dev/null +++ b/src/database/schemamanager.h @@ -0,0 +1,42 @@ +#ifndef SCHEMAMANAGER_H +#define SCHEMAMANAGER_H + +#include + +#include "preferences/usersettings.h" +#include "library/dao/settingsdao.h" + +class SchemaManager { + public: + static const QString SETTINGS_VERSION_STRING; + static const QString SETTINGS_MINCOMPATIBLE_STRING; + + enum class Result { + CurrentVersion, + NewerVersionBackwardsCompatible, + NewerVersionIncompatible, + UpgradeSucceeded, + UpgradeFailed, + SchemaError + }; + + explicit SchemaManager(const QSqlDatabase& database); + + int getCurrentVersion() const { + return m_currentVersion; + } + + bool isBackwardsCompatibleWithVersion(int targetVersion) const; + + Result upgradeToSchemaVersion( + const QString& schemaFilename, + int targetVersion); + + private: + QSqlDatabase m_database; + SettingsDAO m_settingsDao; + + int m_currentVersion; +}; + +#endif /* SCHEMAMANAGER_H */ diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index ab925a9a2b7a..8d47b3ad2a05 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -4,7 +4,10 @@ #include +#include "library/library.h" #include "library/analysisfeature.h" + +#include "library/library.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/dlganalysis.h" @@ -17,16 +20,17 @@ const QString AnalysisFeature::m_sAnalysisViewName = QString("Analysis"); -AnalysisFeature::AnalysisFeature(QObject* parent, +AnalysisFeature::AnalysisFeature(Library* parent, UserSettingsPointer pConfig, TrackCollection* pTrackCollection) : LibraryFeature(parent), m_pConfig(pConfig), + m_pDbConnectionPool(parent->dbConnectionPool()), m_pTrackCollection(pTrackCollection), - m_pAnalyzerQueue(NULL), + m_pAnalyzerQueue(nullptr), m_iOldBpmEnabled(0), m_analysisTitleName(tr("Analyze")), - m_pAnalysisView(NULL) { + m_pAnalysisView(nullptr) { setTitleDefault(); } @@ -108,6 +112,18 @@ void AnalysisFeature::activate() { emit(enableCoverArtDisplay(true)); } +namespace { + inline + AnalyzerQueue::Mode getAnalyzerQueueMode( + const UserSettingsPointer& pConfig) { + if (pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) { + return AnalyzerQueue::Mode::Default; + } else { + return AnalyzerQueue::Mode::WithoutWaveform; + } + } +} // anonymous namespace + void AnalysisFeature::analyzeTracks(QList trackIds) { if (m_pAnalyzerQueue == NULL) { // Save the old BPM detection prefs setting (on or off) @@ -116,7 +132,10 @@ void AnalysisFeature::analyzeTracks(QList trackIds) { m_pConfig->set(ConfigKey("[BPM]","BPMDetectionEnabled"), ConfigValue(1)); // Note: this sucks... we should refactor the prefs/analyzer to fix this hacky bit ^^^^. - m_pAnalyzerQueue = AnalyzerQueue::createAnalysisFeatureAnalyzerQueue(m_pConfig, m_pTrackCollection); + m_pAnalyzerQueue = new AnalyzerQueue( + m_pDbConnectionPool, + m_pConfig, + getAnalyzerQueueMode(m_pConfig)); connect(m_pAnalyzerQueue, SIGNAL(trackProgress(int)), m_pAnalysisView, SLOT(trackAnalysisProgress(int))); diff --git a/src/library/analysisfeature.h b/src/library/analysisfeature.h index bbdd853fc45c..b6b4211f99c3 100644 --- a/src/library/analysisfeature.h +++ b/src/library/analysisfeature.h @@ -13,17 +13,19 @@ #include #include "library/libraryfeature.h" -#include "preferences/usersettings.h" -#include "treeitemmodel.h" #include "library/dlganalysis.h" +#include "library/treeitemmodel.h" +#include "preferences/usersettings.h" +#include "util/db/dbconnectionpool.h" -class AnalyzerQueue; +class Library; class TrackCollection; +class AnalyzerQueue; class AnalysisFeature : public LibraryFeature { Q_OBJECT public: - AnalysisFeature(QObject* parent, + AnalysisFeature(Library* parent, UserSettingsPointer pConfig, TrackCollection* pTrackCollection); virtual ~AnalysisFeature(); @@ -63,6 +65,7 @@ class AnalysisFeature : public LibraryFeature { void setTitleProgress(int trackNum, int totalNum); UserSettingsPointer m_pConfig; + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; TrackCollection* m_pTrackCollection; AnalyzerQueue* m_pAnalyzerQueue; // Used to temporarily enable BPM detection in the prefs before we analyse diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 98bcabfed53f..60f8456e7929 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -25,6 +25,19 @@ const QString AutoDJFeature::m_sAutoDJViewName = QString("Auto DJ"); namespace { const int kMaxRetrieveAttempts = 3; + + int findOrCrateAutoDjPlaylistId(PlaylistDAO& playlistDAO) { + int playlistId = playlistDAO.getPlaylistIdFromName(AUTODJ_TABLE); + // If the AutoDJ playlist does not exist yet then create it. + if (playlistId < 0) { + playlistId = playlistDAO.createPlaylist( + AUTODJ_TABLE, PlaylistDAO::PLHT_AUTO_DJ); + VERIFY_OR_DEBUG_ASSERT(playlistId >= 0) { + qWarning() << "Failed to create Auto DJ playlist!"; + } + } + return playlistId; + } } // anonymous namespace AutoDJFeature::AutoDJFeature(Library* pLibrary, @@ -36,22 +49,10 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, m_pLibrary(pLibrary), m_pTrackCollection(pTrackCollection), m_playlistDao(pTrackCollection->getPlaylistDAO()), - m_iAutoDJPlaylistId(-1), + m_iAutoDJPlaylistId(findOrCrateAutoDjPlaylistId(m_playlistDao)), m_pAutoDJProcessor(NULL), m_pAutoDJView(NULL), - m_autoDjCratesDao(pTrackCollection, pConfig) { - m_iAutoDJPlaylistId = m_playlistDao.getPlaylistIdFromName(AUTODJ_TABLE); - // If the AutoDJ playlist does not exist yet then create it. - if (m_iAutoDJPlaylistId < 0) { - m_iAutoDJPlaylistId = m_playlistDao.createPlaylist( - AUTODJ_TABLE, PlaylistDAO::PLHT_AUTO_DJ); - VERIFY_OR_DEBUG_ASSERT(m_iAutoDJPlaylistId >= 0) { - qWarning() << "Failed to create Auto DJ playlist!"; - } - } - // The AutoDJCratesDAO expects that the dedicated AutoDJ playlist - // has already been created. - m_autoDjCratesDao.initialize(); + m_autoDjCratesDao(m_iAutoDJPlaylistId, pTrackCollection, pConfig) { qRegisterMetaType("AutoDJState"); m_pAutoDJProcessor = new AutoDJProcessor( diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index bc19eb336489..91038762f95c 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -16,6 +16,7 @@ #include "mixer/playerinfo.h" #include "track/keyutils.h" #include "track/trackmetadata.h" +#include "util/db/dbconnection.h" #include "util/duration.h" #include "util/dnd.h" #include "util/assert.h" @@ -510,7 +511,7 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { m_tableOrderBy = "ORDER BY "; QString field = m_tableColumns[column]; QString sort_field = QString("%1.%2").arg(m_tableName, field); - m_tableOrderBy.append(DbConnection::collateLexicographically(sort_field)); + m_tableOrderBy.append(mixxx::DbConnection::collateLexicographically(sort_field)); m_tableOrderBy.append((order == Qt::AscendingOrder) ? " ASC" : " DESC"); } m_sortColumns.clear(); @@ -540,7 +541,7 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { } m_trackSourceOrderBy.append(first ? "ORDER BY ": ", "); - m_trackSourceOrderBy.append(DbConnection::collateLexicographically(sort_field)); + m_trackSourceOrderBy.append(mixxx::DbConnection::collateLexicographically(sort_field)); m_trackSourceOrderBy.append((sc.m_order == Qt::AscendingOrder) ? " ASC" : " DESC"); //qDebug() << m_trackSourceOrderBy; diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index 61437cc8b9fc..a5e1343a3324 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -3,16 +3,18 @@ #include "library/crate/crateschema.h" #include "library/dao/trackschema.h" -#include "util/db/sqllikewildcards.h" #include "util/db/dbconnection.h" -#include "util/db/sqltransaction.h" + +#include "util/db/sqllikewildcards.h" #include "util/db/fwdsqlquery.h" -#include +#include "util/logger.h" namespace { +const mixxx::Logger kLogger("CrateStorage"); + const QString CRATETABLE_LOCKED = "locked"; const QString CRATE_SUMMARY_VIEW = "crate_summary"; @@ -82,6 +84,7 @@ class CrateQueryBinder { } // anonymous namespace + CrateQueryFields::CrateQueryFields(const FwdSqlQuery& query) : m_iId(query.fieldIndex(CRATETABLE_ID)), m_iName(query.fieldIndex(CRATETABLE_NAME)), @@ -122,8 +125,6 @@ void CrateSummaryQueryFields::populateFromQuery( void CrateStorage::repairDatabase(QSqlDatabase database) { - DEBUG_ASSERT(!m_database.isOpen()); - // NOTE(uklotzde): No transactions // All queries are independent so there is no need to enclose some // or all of them in a transaction. Grouping into transactions would @@ -145,7 +146,8 @@ void CrateStorage::repairDatabase(QSqlDatabase database) { CRATE_TABLE, CRATETABLE_NAME)); if (query.execPrepared() && (query.numRowsAffected() > 0)) { - qWarning() << "Deleted" << query.numRowsAffected() + kLogger.warning() + << "Deleted" << query.numRowsAffected() << "crates with empty names"; } } @@ -156,8 +158,8 @@ void CrateStorage::repairDatabase(QSqlDatabase database) { CRATE_TABLE, CRATETABLE_LOCKED)); if (query.execPrepared() && (query.numRowsAffected() > 0)) { - qWarning() << "Fixed boolean values in" - "table" << CRATE_TABLE + kLogger.warning() + << "Fixed boolean values in table" << CRATE_TABLE << "column" << CRATETABLE_LOCKED << "for" << query.numRowsAffected() << "crates"; } @@ -169,8 +171,8 @@ void CrateStorage::repairDatabase(QSqlDatabase database) { CRATE_TABLE, CRATETABLE_AUTODJ_SOURCE)); if (query.execPrepared() && (query.numRowsAffected() > 0)) { - qWarning() << "Fixed boolean values in" - "table" << CRATE_TABLE + kLogger.warning() + << "Fixed boolean values in table" << CRATE_TABLE << "column" << CRATETABLE_AUTODJ_SOURCE << "for" << query.numRowsAffected() << "crates"; } @@ -186,7 +188,8 @@ void CrateStorage::repairDatabase(QSqlDatabase database) { CRATETABLE_ID, CRATE_TABLE)); if (query.execPrepared() && (query.numRowsAffected() > 0)) { - qWarning() << "Removed" << query.numRowsAffected() + kLogger.warning() + << "Removed" << query.numRowsAffected() << "crate tracks from non-existent crates"; } } @@ -199,26 +202,31 @@ void CrateStorage::repairDatabase(QSqlDatabase database) { LIBRARYTABLE_ID, LIBRARY_TABLE)); if (query.execPrepared() && (query.numRowsAffected() > 0)) { - qWarning() << "Removed" << query.numRowsAffected() + kLogger.warning() + << "Removed" << query.numRowsAffected() << "library purged tracks from crates"; } } } -void CrateStorage::attachDatabase(QSqlDatabase database) { +void CrateStorage::connectDatabase(QSqlDatabase database) { m_database = database; createViews(); } -void CrateStorage::detachDatabase() { +void CrateStorage::disconnectDatabase() { + // Ensure that we don't use the current database connection + // any longer. + m_database = QSqlDatabase(); } void CrateStorage::createViews() { VERIFY_OR_DEBUG_ASSERT(FwdSqlQuery(m_database, kCrateSummaryViewQuery).execPrepared()) { - qCritical() << "Failed to create database view for crate summaries!"; + kLogger.critical() + << "Failed to create database view for crate summaries!"; } } @@ -247,11 +255,13 @@ bool CrateStorage::readCrateById(CrateId id, Crate* pCrate) const { CrateSelectResult crates(std::move(query)); if ((pCrate != nullptr) ? crates.populateNext(pCrate) : crates.next()) { VERIFY_OR_DEBUG_ASSERT(!crates.next()) { - qWarning() << "Ambiguous crate id:" << id; + kLogger.warning() + << "Ambiguous crate id:" << id; } return true; } else { - qWarning() << "Crate not found by id:" << id; + kLogger.warning() + << "Crate not found by id:" << id; } } return false; @@ -268,11 +278,13 @@ bool CrateStorage::readCrateByName(const QString& name, Crate* pCrate) const { CrateSelectResult crates(std::move(query)); if ((pCrate != nullptr) ? crates.populateNext(pCrate) : crates.next()) { VERIFY_OR_DEBUG_ASSERT(!crates.next()) { - qWarning() << "Ambiguous crate name:" << name; + kLogger.warning() + << "Ambiguous crate name:" << name; } return true; } else { - qDebug() << "Crate not found by name:" << name; + kLogger.debug() + << "Crate not found by name:" << name; } } return false; @@ -281,7 +293,7 @@ bool CrateStorage::readCrateByName(const QString& name, Crate* pCrate) const { CrateSelectResult CrateStorage::selectCrates() const { FwdSqlQuery query(m_database, - DbConnection::collateLexicographically(QString( + mixxx::DbConnection::collateLexicographically(QString( "SELECT * FROM %1 ORDER BY %2").arg( CRATE_TABLE, CRATETABLE_NAME))); @@ -318,7 +330,7 @@ CrateSelectResult CrateStorage::selectCratesByIds( DEBUG_ASSERT(!subselectForCrateIds.isEmpty()); FwdSqlQuery query(m_database, - DbConnection::collateLexicographically(QString( + mixxx::DbConnection::collateLexicographically(QString( "SELECT * FROM %1 WHERE %2 %3 (%4) ORDER BY %5").arg( CRATE_TABLE, CRATETABLE_ID, @@ -336,7 +348,7 @@ CrateSelectResult CrateStorage::selectCratesByIds( CrateSelectResult CrateStorage::selectAutoDjCrates(bool autoDjSource) const { FwdSqlQuery query(m_database, - DbConnection::collateLexicographically(QString( + mixxx::DbConnection::collateLexicographically(QString( "SELECT * FROM %1 WHERE %2=:autoDjSource ORDER BY %3").arg( CRATE_TABLE, CRATETABLE_AUTODJ_SOURCE, @@ -352,7 +364,7 @@ CrateSelectResult CrateStorage::selectAutoDjCrates(bool autoDjSource) const { CrateSummarySelectResult CrateStorage::selectCrateSummaries() const { FwdSqlQuery query(m_database, - DbConnection::collateLexicographically(QString( + mixxx::DbConnection::collateLexicographically(QString( "SELECT * FROM %1 ORDER BY %2").arg( CRATE_SUMMARY_VIEW, CRATETABLE_NAME))); @@ -373,11 +385,13 @@ bool CrateStorage::readCrateSummaryById(CrateId id, CrateSummary* pCrateSummary) CrateSummarySelectResult crateSummaries(std::move(query)); if ((pCrateSummary != nullptr) ? crateSummaries.populateNext(pCrateSummary) : crateSummaries.next()) { VERIFY_OR_DEBUG_ASSERT(!crateSummaries.next()) { - qWarning() << "Ambiguous crate id:" << id; + kLogger.warning() + << "Ambiguous crate id:" << id; } return true; } else { - qWarning() << "Crate summary not found by id:" << id; + kLogger.warning() + << "Crate summary not found by id:" << id; } } return false; @@ -495,12 +509,11 @@ QSet CrateStorage::collectCrateIdsOfTracks(const QList& trackI bool CrateStorage::onInsertingCrate( - SqlTransaction& transaction, const Crate& crate, CrateId* pCrateId) { - DEBUG_ASSERT(transaction); VERIFY_OR_DEBUG_ASSERT(!crate.getId().isValid()) { - qWarning() << "Cannot insert crate with a valid id:" << crate.getId(); + kLogger.warning() + << "Cannot insert crate with a valid id:" << crate.getId(); return false; } FwdSqlQuery query(m_database, QString( @@ -533,11 +546,10 @@ bool CrateStorage::onInsertingCrate( bool CrateStorage::onUpdatingCrate( - SqlTransaction& transaction, const Crate& crate) { - DEBUG_ASSERT(transaction); VERIFY_OR_DEBUG_ASSERT(crate.getId().isValid()) { - qWarning() << "Cannot update crate without a valid id"; + kLogger.warning() + << "Cannot update crate without a valid id"; return false; } FwdSqlQuery query(m_database, QString( @@ -560,24 +572,23 @@ bool CrateStorage::onUpdatingCrate( } if (query.numRowsAffected() > 0) { VERIFY_OR_DEBUG_ASSERT(query.numRowsAffected() <= 1) { - qWarning() << "Updated multiple crates with the same id" << crate.getId(); + kLogger.warning() + << "Updated multiple crates with the same id" << crate.getId(); } return true; } else { - qWarning() << "Cannot update non-existent crate with id" << crate.getId(); + kLogger.warning() + << "Cannot update non-existent crate with id" << crate.getId(); return false; } } bool CrateStorage::onDeletingCrate( - SqlTransaction& transaction, CrateId crateId) { - VERIFY_OR_DEBUG_ASSERT(transaction) { - return false; - } VERIFY_OR_DEBUG_ASSERT(crateId.isValid()) { - qWarning() << "Cannot delete crate without a valid id"; + kLogger.warning() + << "Cannot delete crate without a valid id"; return false; } { @@ -593,7 +604,8 @@ bool CrateStorage::onDeletingCrate( return false; } if (query.numRowsAffected() <= 0) { - qDebug() << "Deleting empty crate with id" << crateId; + kLogger.debug() + << "Deleting empty crate with id" << crateId; } } { @@ -610,11 +622,13 @@ bool CrateStorage::onDeletingCrate( } if (query.numRowsAffected() > 0) { VERIFY_OR_DEBUG_ASSERT(query.numRowsAffected() <= 1) { - qWarning() << "Deleted multiple crates with the same id" << crateId; + kLogger.warning() + << "Deleted multiple crates with the same id" << crateId; } return true; } else { - qWarning() << "Cannot delete non-existent crate with id" << crateId; + kLogger.warning() + << "Cannot delete non-existent crate with id" << crateId; return false; } } @@ -622,12 +636,8 @@ bool CrateStorage::onDeletingCrate( bool CrateStorage::onAddingCrateTracks( - SqlTransaction& transaction, CrateId crateId, const QList& trackIds) { - VERIFY_OR_DEBUG_ASSERT(transaction) { - return false; - } FwdSqlQuery query(m_database, QString( "INSERT OR IGNORE INTO %1 (%2, %3) VALUES (:crateId,:trackId)").arg( CRATE_TRACKS_TABLE, @@ -644,7 +654,9 @@ bool CrateStorage::onAddingCrateTracks( } if (query.numRowsAffected() == 0) { // track is already in crate - qDebug() << "Track" << trackId << "not added to crate" << crateId; + kLogger.debug() + << "Track" << trackId + << "not added to crate" << crateId; } else { DEBUG_ASSERT(query.numRowsAffected() == 1); } @@ -654,14 +666,10 @@ bool CrateStorage::onAddingCrateTracks( bool CrateStorage::onRemovingCrateTracks( - SqlTransaction& transaction, CrateId crateId, const QList& trackIds) { - // NOTE(uklotzde): We remove tracks in a transaction and loop + // NOTE(uklotzde): We remove tracks in a loop // analogously to adding tracks (see above). - VERIFY_OR_DEBUG_ASSERT(transaction) { - return false; - } FwdSqlQuery query(m_database, QString( "DELETE FROM %1 WHERE %2=:crateId AND %3=:trackId").arg( CRATE_TRACKS_TABLE, @@ -678,7 +686,9 @@ bool CrateStorage::onRemovingCrateTracks( } if (query.numRowsAffected() == 0) { // track not found in crate - qDebug() << "Track" << trackId << "not removed from crate" << crateId; + kLogger.debug() + << "Track" << trackId + << "not removed from crate" << crateId; } else { DEBUG_ASSERT(query.numRowsAffected() == 1); } @@ -688,10 +698,7 @@ bool CrateStorage::onRemovingCrateTracks( bool CrateStorage::onPurgingTracks( - SqlTransaction& transaction, const QList& trackIds) { - DEBUG_ASSERT(transaction); - // NOTE(uklotzde): Remove tracks from crates one-by-one. // This might be optimized by deleting multiple track ids // at once in chunks with a maximum size. diff --git a/src/library/crate/cratestorage.h b/src/library/crate/cratestorage.h index dea61bc99ef8..1dfa16ce3b8e 100644 --- a/src/library/crate/cratestorage.h +++ b/src/library/crate/cratestorage.h @@ -9,14 +9,11 @@ #include "library/crate/cratesummary.h" #include "track/trackid.h" -#include "util/db/sqlstorage.h" #include "util/db/fwdsqlqueryselectresult.h" #include "util/db/sqlsubselectmode.h" +#include "util/db/sqlstorage.h" -// forward declaration(s) -class SqlTransaction; - class CrateQueryFields { public: CrateQueryFields() {} @@ -179,21 +176,17 @@ class CrateTrackSelectResult: public FwdSqlQuerySelectResult { CrateTrackQueryFields m_queryFields; }; -class CrateStorage: public SqlStorage { +class CrateStorage: public virtual /*implements*/ SqlStorage { public: - ~CrateStorage() final = default; - - - ///////////////////////////////////////////////////////////////////////// - // Inherited operations (non-const) - // Only invoked by TrackCollection! - ///////////////////////////////////////////////////////////////////////// - - void repairDatabase(QSqlDatabase database) final; + CrateStorage() = default; + ~CrateStorage() override = default; - void attachDatabase(QSqlDatabase database) final; - void detachDatabase() final; + void repairDatabase( + QSqlDatabase database) override; + void connectDatabase( + QSqlDatabase database) override; + void disconnectDatabase() override; ///////////////////////////////////////////////////////////////////////// // Crate write operations (transactional, non-const) @@ -213,30 +206,24 @@ class CrateStorage: public SqlStorage { ///////////////////////////////////////////////////////////////////////// bool onInsertingCrate( - SqlTransaction& transaction, const Crate& crate, CrateId* pCrateId = nullptr); bool onUpdatingCrate( - SqlTransaction& transaction, const Crate& crate); bool onDeletingCrate( - SqlTransaction& transaction, CrateId crateId); bool onAddingCrateTracks( - SqlTransaction& transaction, CrateId crateId, const QList& trackIds); bool onRemovingCrateTracks( - SqlTransaction& transaction, CrateId crateId, const QList& trackIds); bool onPurgingTracks( - SqlTransaction& transaction, const QList& trackIds); diff --git a/src/library/dao/analysisdao.cpp b/src/library/dao/analysisdao.cpp index da200545c0ec..59394d10759e 100644 --- a/src/library/dao/analysisdao.cpp +++ b/src/library/dao/analysisdao.cpp @@ -17,25 +17,14 @@ const QString AnalysisDao::s_analysisTableName = "track_analysis"; // CPU time so I think we should stick with the default. rryan 4/3/2012 const int kCompressionLevel = -1; -AnalysisDao::AnalysisDao(QSqlDatabase& database, UserSettingsPointer pConfig) - : m_pConfig(pConfig), - m_db(database) { +AnalysisDao::AnalysisDao(UserSettingsPointer pConfig) + : m_pConfig(pConfig) { QDir storagePath = getAnalysisStoragePath(); if (!QDir().mkpath(storagePath.absolutePath())) { qDebug() << "WARNING: Could not create analysis storage path. Mixxx will be unable to store analyses."; } } -AnalysisDao::~AnalysisDao() { -} - -void AnalysisDao::initialize() { -} - -void AnalysisDao::setDatabase(QSqlDatabase& database) { - m_db = database; -} - QList AnalysisDao::getAnalysesForTrack(TrackId trackId) { if (!m_db.isOpen() || !trackId.isValid()) { return QList(); @@ -369,10 +358,12 @@ void AnalysisDao::saveTrackAnalyses(const Track& track) { << "analysisId" << analysis.analysisId; } -size_t AnalysisDao::getDiskUsageInBytes(AnalysisType type) { +size_t AnalysisDao::getDiskUsageInBytes( + const QSqlDatabase& database, + AnalysisType type) const { QDir analysisPath(getAnalysisStoragePath()); - QSqlQuery query(m_db); + QSqlQuery query(database); query.prepare(QString("SELECT id FROM %1 WHERE type=:type").arg(s_analysisTableName)); query.bindValue(":type", type); @@ -390,10 +381,12 @@ size_t AnalysisDao::getDiskUsageInBytes(AnalysisType type) { return total; } -bool AnalysisDao::deleteAnalysesByType(AnalysisType type) { +bool AnalysisDao::deleteAnalysesByType( + const QSqlDatabase& database, + AnalysisType type) const { QDir analysisPath(getAnalysisStoragePath()); - QSqlQuery query(m_db); + QSqlQuery query(database); query.prepare(QString("SELECT id FROM %1 WHERE type=:type").arg(s_analysisTableName)); query.bindValue(":type", type); diff --git a/src/library/dao/analysisdao.h b/src/library/dao/analysisdao.h index 24e11e957c54..2e5cb15f361c 100644 --- a/src/library/dao/analysisdao.h +++ b/src/library/dao/analysisdao.h @@ -31,15 +31,22 @@ class AnalysisDao : public DAO { QByteArray data; }; - AnalysisDao(QSqlDatabase& database, UserSettingsPointer pConfig); - virtual ~AnalysisDao(); + explicit AnalysisDao(UserSettingsPointer pConfig); + ~AnalysisDao() override {} - virtual void initialize(); - void setDatabase(QSqlDatabase& database); + // The following functions can be used with a custom database + // connection and independent of whether the DAO has been + // initialized or not. + bool deleteAnalysesByType( + const QSqlDatabase& database, + AnalysisType type) const; + size_t getDiskUsageInBytes( + const QSqlDatabase& database, + AnalysisType type) const; - bool getWaveform(Track& tio); - bool saveWaveform(const Track& tio); - bool removeWaveform(const Track& tio); + void initialize(const QSqlDatabase& database) override { + m_db = database; + } QList getAnalysesForTrackByType(TrackId trackId, AnalysisType type); QList getAnalysesForTrack(TrackId trackId); @@ -47,12 +54,9 @@ class AnalysisDao : public DAO { bool deleteAnalysis(const int analysisId); void deleteAnalyses(const QList& trackIds); bool deleteAnalysesForTrack(TrackId trackId); - bool deleteAnalysesByType(AnalysisType type); void saveTrackAnalyses(const Track& track); - size_t getDiskUsageInBytes(AnalysisType type); - private: bool saveWaveform(const Track& tio, const Waveform& waveform, diff --git a/src/library/dao/autodjcratesdao.cpp b/src/library/dao/autodjcratesdao.cpp index 53a72f3bb179..99d3826d2806 100644 --- a/src/library/dao/autodjcratesdao.cpp +++ b/src/library/dao/autodjcratesdao.cpp @@ -39,14 +39,15 @@ const int kLeastPreferredPercentMax = 50; } // anonymous namespace AutoDJCratesDAO::AutoDJCratesDAO( + int iAutoDjPlaylistId, TrackCollection* pTrackCollection, - UserSettingsPointer a_pConfig) - : m_pTrackCollection(pTrackCollection), + UserSettingsPointer pConfig) + : m_iAutoDjPlaylistId(iAutoDjPlaylistId), + m_pTrackCollection(pTrackCollection), m_database(pTrackCollection->database()), - m_pConfig (a_pConfig), + m_pConfig(pConfig), // The database has not been created yet. m_bAutoDjCratesDbCreated(false), - m_iAutoDjPlaylistId(-1), // By default, active tracks are not tracks that haven't been played in // a while. m_bUseIgnoreTime(false) { @@ -55,15 +56,6 @@ AutoDJCratesDAO::AutoDJCratesDAO( AutoDJCratesDAO::~AutoDJCratesDAO() { } -void AutoDJCratesDAO::initialize() { - // Save the ID of the auto-DJ playlist. - m_iAutoDjPlaylistId = - m_pTrackCollection->getPlaylistDAO().getPlaylistIdFromName(AUTODJ_TABLE); - VERIFY_OR_DEBUG_ASSERT(m_iAutoDjPlaylistId >= 0) { - qWarning() << "Auto DJ playlist not found!"; - } -} - // Create the temporary auto-DJ-crates table. // Done the first time it's used, since the user might not even make // use of this feature. diff --git a/src/library/dao/autodjcratesdao.h b/src/library/dao/autodjcratesdao.h index cd99ca073ad3..0d4882940dd9 100644 --- a/src/library/dao/autodjcratesdao.h +++ b/src/library/dao/autodjcratesdao.h @@ -5,27 +5,21 @@ #include #include "preferences/usersettings.h" -#include "library/dao/dao.h" #include "library/crate/crateid.h" #include "track/track.h" #include "util/class.h" class TrackCollection; -class AutoDJCratesDAO : public QObject, public virtual DAO { +class AutoDJCratesDAO : public QObject { Q_OBJECT public: - AutoDJCratesDAO(TrackCollection* pTrackCollection, - UserSettingsPointer a_pConfig); + AutoDJCratesDAO( + int iAutoDjPlaylistId, + TrackCollection* pTrackCollection, + UserSettingsPointer a_pConfig); ~AutoDJCratesDAO() override; - void initialize() override; - - int getPlaylistId() const { - DEBUG_ASSERT(m_iAutoDjPlaylistId >= 0); - return m_iAutoDjPlaylistId; - } - // Get the ID of a random track. TrackId getRandomTrackId(); @@ -105,6 +99,9 @@ class AutoDJCratesDAO : public QObject, public virtual DAO { void updateAutoDjCrate(CrateId crateId); void deleteAutoDjCrate(CrateId crateId); + // The auto-DJ playlist's ID. + const int m_iAutoDjPlaylistId; + TrackCollection* m_pTrackCollection; // The SQL database we interact with. @@ -116,9 +113,6 @@ class AutoDJCratesDAO : public QObject, public virtual DAO { // True if the auto-DJ-crates database has been created. bool m_bAutoDjCratesDbCreated; - // The auto-DJ playlist's ID. - int m_iAutoDjPlaylistId; - // True if active tracks can be tracks that haven't been played in // a while. bool m_bUseIgnoreTime; diff --git a/src/library/dao/cuedao.cpp b/src/library/dao/cuedao.cpp index d45f28cd32c7..66a4e440c900 100644 --- a/src/library/dao/cuedao.cpp +++ b/src/library/dao/cuedao.cpp @@ -12,17 +12,6 @@ #include "util/assert.h" #include "util/performancetimer.h" -CueDAO::CueDAO(QSqlDatabase& database) - : m_database(database) { -} - -CueDAO::~CueDAO() { -} - -void CueDAO::initialize() { - qDebug() << "CueDAO::initialize" << QThread::currentThread() << m_database.connectionName(); -} - int CueDAO::cueCount() { qDebug() << "CueDAO::cueCount" << QThread::currentThread() << m_database.connectionName(); QSqlQuery query(m_database); diff --git a/src/library/dao/cuedao.h b/src/library/dao/cuedao.h index e9326185e5bc..3f0a9cb16319 100644 --- a/src/library/dao/cuedao.h +++ b/src/library/dao/cuedao.h @@ -16,11 +16,12 @@ class Cue; class CueDAO : public DAO { public: - CueDAO(QSqlDatabase& database); - virtual ~CueDAO(); - void setDatabase(QSqlDatabase& database) { m_database = database; } + ~CueDAO() override {} + + void initialize(const QSqlDatabase& database) override { + m_database = database; + } - void initialize(); int cueCount(); int numCuesForTrack(TrackId trackId); QList getCuesForTrack(TrackId trackId) const; @@ -34,7 +35,7 @@ class CueDAO : public DAO { private: CuePointer cueFromRow(const QSqlQuery& query) const; - QSqlDatabase& m_database; + QSqlDatabase m_database; mutable QMap m_cues; }; diff --git a/src/library/dao/dao.h b/src/library/dao/dao.h index 12bbcf195132..94bb02db4a87 100644 --- a/src/library/dao/dao.h +++ b/src/library/dao/dao.h @@ -1,11 +1,13 @@ -// dao.h -// Created 10/22/2009 by RJ Ryan (rryan@mit.edu) - #ifndef DAO_H #define DAO_H +#include + class DAO { - virtual void initialize() = 0; + public: + virtual ~DAO() {} + + virtual void initialize(const QSqlDatabase& database) = 0; }; #endif /* DAO_H */ diff --git a/src/library/dao/directorydao.cpp b/src/library/dao/directorydao.cpp index 11d59652478b..d309ac807f92 100644 --- a/src/library/dao/directorydao.cpp +++ b/src/library/dao/directorydao.cpp @@ -11,18 +11,6 @@ #include "util/db/sqllikewildcardescaper.h" -DirectoryDAO::DirectoryDAO(QSqlDatabase& database) - : m_database(database) { -} - -DirectoryDAO::~DirectoryDAO() { -} - -void DirectoryDAO::initialize() { - qDebug() << "DirectoryDAO::initialize" << QThread::currentThread() - << m_database.connectionName(); -} - int DirectoryDAO::addDirectory(const QString& newDir) { // Do nothing if the dir to add is a child of a directory that is already in // the db. diff --git a/src/library/dao/directorydao.h b/src/library/dao/directorydao.h index fc9b49c32510..827bd4a7037c 100644 --- a/src/library/dao/directorydao.h +++ b/src/library/dao/directorydao.h @@ -15,12 +15,10 @@ enum ReturnCodes { class DirectoryDAO : public DAO { public: + void initialize(const QSqlDatabase& database) override { + m_database = database; + } - DirectoryDAO(QSqlDatabase& database); - virtual ~DirectoryDAO(); - - void initialize(); - void setDatabase(QSqlDatabase& database) { m_database = database; } int addDirectory(const QString& dir); int removeDirectory(const QString& dir); QSet relocateDirectory(const QString& oldFolder, const QString& newFolder); @@ -28,7 +26,7 @@ class DirectoryDAO : public DAO { private: bool isChildDir(QString testDir, QString dirStr); - QSqlDatabase& m_database; + QSqlDatabase m_database; }; #endif //DIRECTORYDAO_H diff --git a/src/library/dao/libraryhashdao.cpp b/src/library/dao/libraryhashdao.cpp index 63ff28476db0..637f1f49f4b2 100644 --- a/src/library/dao/libraryhashdao.cpp +++ b/src/library/dao/libraryhashdao.cpp @@ -8,20 +8,6 @@ #include "libraryhashdao.h" #include "library/queryutil.h" -LibraryHashDAO::LibraryHashDAO(QSqlDatabase& database) - : m_database(database) { - -} - -LibraryHashDAO::~LibraryHashDAO() -{ -} - -void LibraryHashDAO::initialize() { - qDebug() << "LibraryHashDAO::initialize" << QThread::currentThread() - << m_database.connectionName(); -} - QHash LibraryHashDAO::getDirectoryHashes() { QSqlQuery query(m_database); query.prepare("SELECT hash, directory_path FROM LibraryHashes"); diff --git a/src/library/dao/libraryhashdao.h b/src/library/dao/libraryhashdao.h index 148ee22f4c11..5c8b3fbd6684 100644 --- a/src/library/dao/libraryhashdao.h +++ b/src/library/dao/libraryhashdao.h @@ -10,11 +10,11 @@ class LibraryHashDAO : public DAO { public: - LibraryHashDAO(QSqlDatabase& database); - virtual ~LibraryHashDAO(); + ~LibraryHashDAO() override {} - void setDatabase(QSqlDatabase& database) { m_database = database; }; - void initialize(); + void initialize(const QSqlDatabase& database) { + m_database = database; + }; QHash getDirectoryHashes(); int getDirectoryHash(const QString& dirPath); @@ -30,7 +30,7 @@ class LibraryHashDAO : public DAO { QStringList getDeletedDirectories(); private: - QSqlDatabase& m_database; + QSqlDatabase m_database; }; #endif //LIBRARYHASHDAO_H diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index 7a5476adb217..23cd96d7ed79 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -8,15 +8,12 @@ #include "library/autodj/autodjprocessor.h" #include "util/math.h" -PlaylistDAO::PlaylistDAO(QSqlDatabase& database) - : m_database(database), - m_pAutoDJProcessor(nullptr) { +PlaylistDAO::PlaylistDAO() + : m_pAutoDJProcessor(nullptr) { } -PlaylistDAO::~PlaylistDAO() { -} - -void PlaylistDAO::initialize() { +void PlaylistDAO::initialize(const QSqlDatabase& database) { + m_database = database; populatePlaylistMembershipCache(); } diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h index 578a0ebc7884..5f9af679a7d2 100644 --- a/src/library/dao/playlistdao.h +++ b/src/library/dao/playlistdao.h @@ -28,6 +28,8 @@ const QString PLAYLISTTRACKSTABLE_ARTIST = "artist"; const QString PLAYLISTTRACKSTABLE_TITLE = "title"; const QString PLAYLISTTRACKSTABLE_DATETIMEADDED = "pl_datetime_added"; +#define AUTODJ_TABLE "Auto DJ" + class AutoDJProcessor; class PlaylistDAO : public QObject, public virtual DAO { @@ -46,11 +48,11 @@ class PlaylistDAO : public QObject, public virtual DAO { REPLACE, }; - PlaylistDAO(QSqlDatabase& database); - virtual ~PlaylistDAO(); + PlaylistDAO(); + ~PlaylistDAO() override {} + + void initialize(const QSqlDatabase& database); - void initialize(); - void setDatabase(QSqlDatabase& database) { m_database = database; } // Create a playlist, fails with -1 if already exists int createPlaylist(const QString& name, const HiddenType type = PLHT_NOT_HIDDEN); // Create a playlist, appends "(n)" if already exists, name becomes the new name @@ -140,7 +142,7 @@ class PlaylistDAO : public QObject, public virtual DAO { int* pTrackDistance); void populatePlaylistMembershipCache(); - QSqlDatabase& m_database; + QSqlDatabase m_database; QMultiHash m_playlistsTrackIsIn; AutoDJProcessor* m_pAutoDJProcessor; DISALLOW_COPY_AND_ASSIGN(PlaylistDAO); diff --git a/src/library/dao/settingsdao.cpp b/src/library/dao/settingsdao.cpp index 08c549672155..2e7f8cb7d1c7 100644 --- a/src/library/dao/settingsdao.cpp +++ b/src/library/dao/settingsdao.cpp @@ -3,7 +3,7 @@ #include "library/dao/settingsdao.h" -SettingsDAO::SettingsDAO(QSqlDatabase& db) +SettingsDAO::SettingsDAO(const QSqlDatabase& db) : m_db(db) { } @@ -14,7 +14,7 @@ SettingsDAO::~SettingsDAO() { void SettingsDAO::initialize() { } -QString SettingsDAO::getValue(const QString& name, QString defaultValue) { +QString SettingsDAO::getValue(const QString& name, QString defaultValue) const { QSqlQuery query(m_db); query.prepare("SELECT value FROM settings WHERE name = :name"); diff --git a/src/library/dao/settingsdao.h b/src/library/dao/settingsdao.h index 9cf5bad12b53..cd0d66bf3f26 100644 --- a/src/library/dao/settingsdao.h +++ b/src/library/dao/settingsdao.h @@ -14,12 +14,12 @@ // All library-specific preferences go in the library settings table class SettingsDAO : public QObject { public: - SettingsDAO(QSqlDatabase &db); + SettingsDAO(const QSqlDatabase& db); virtual ~SettingsDAO(); virtual void initialize(); - QString getValue(const QString& name, QString defaultValue = QString()); + QString getValue(const QString& name, QString defaultValue = QString()) const; bool setValue(const QString& name, const QVariant& value); private: diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 5bc3473c7be6..ef72864d2d60 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -68,14 +68,12 @@ RecentTrackCacheItem::~RecentTrackCacheItem() { // expensive. const int kRecentTracksCacheSize = 5; -TrackDAO::TrackDAO(QSqlDatabase& database, - CueDAO& cueDao, +TrackDAO::TrackDAO(CueDAO& cueDao, PlaylistDAO& playlistDao, AnalysisDao& analysisDao, LibraryHashDAO& libraryHashDao, UserSettingsPointer pConfig) - : m_database(database), - m_cueDao(cueDao), + : m_cueDao(cueDao), m_playlistDao(playlistDao), m_analysisDao(analysisDao), m_libraryHashDao(libraryHashDao), @@ -152,10 +150,6 @@ void TrackDAO::finish() { transaction.commit(); } -void TrackDAO::initialize() { - qDebug() << "TrackDAO::initialize" << QThread::currentThread() << m_database.connectionName(); -} - /** Retrieve the track id for the track that's located at "location" on disk. @return the track id for the track located at location, or -1 if the track is not in the database. @@ -840,9 +834,7 @@ QList TrackDAO::addMultipleTracks( } bool TrackDAO::onHidingTracks( - SqlTransaction& transaction, const QList& trackIds) { - DEBUG_ASSERT(transaction); QStringList idList; for (const auto& trackId: trackIds) { idList.append(trackId.toString()); @@ -866,9 +858,7 @@ void TrackDAO::afterHidingTracks( // This function should get called if you drag-and-drop a file that's been // "hidden" from Mixxx back into the library view. bool TrackDAO::onUnhidingTracks( - SqlTransaction& transaction, const QList& trackIds) { - DEBUG_ASSERT(transaction); QStringList idList; for (const auto& trackId: trackIds) { idList.append(trackId.toString()); @@ -911,9 +901,7 @@ QList TrackDAO::getTrackIds(const QDir& dir) { } bool TrackDAO::onPurgingTracks( - SqlTransaction& transaction, const QList& trackIds) { - DEBUG_ASSERT(transaction); if (trackIds.empty()) { return true; // nothing to do } diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index 7fb097ff95e0..6cc4e5996ab1 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -54,17 +54,18 @@ class TrackDAO : public QObject, public virtual DAO { public: // The 'config object' is necessary because users decide ID3 tags get // synchronized on track metadata change - TrackDAO(QSqlDatabase& database, CueDAO& cueDao, - PlaylistDAO& playlistDao, - AnalysisDao& analysisDao, - LibraryHashDAO& libraryHashDao, - UserSettingsPointer pConfig); - virtual ~TrackDAO(); - + TrackDAO( + CueDAO& cueDao, + PlaylistDAO& playlistDao, + AnalysisDao& analysisDao, + LibraryHashDAO& libraryHashDao, + UserSettingsPointer pConfig); + ~TrackDAO() override; + + void initialize(const QSqlDatabase& database) override { + m_database = database; + } void finish(); - void setDatabase(QSqlDatabase& database) { m_database = database; } - - void initialize(); TrackId getTrackId(const QString& absoluteFilePath); QList getTrackIds(const QList& files); @@ -88,19 +89,16 @@ class TrackDAO : public QObject, public virtual DAO { void addTracksFinish(bool rollback = false); bool onHidingTracks( - SqlTransaction& transaction, const QList& trackIds); void afterHidingTracks( const QList& trackIds); bool onUnhidingTracks( - SqlTransaction& transaction, const QList& trackIds); void afterUnhidingTracks( const QList& trackIds); bool onPurgingTracks( - SqlTransaction& transaction, const QList& trackIds); void afterPurgingTracks( const QList& trackIds); diff --git a/src/library/library.cpp b/src/library/library.cpp index efa9f1db3b06..cc5e934e48f0 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -6,6 +6,8 @@ #include #include +#include "database/mixxxdb.h" + #include "mixer/playermanager.h" #include "library/library.h" #include "library/library_preferences.h" @@ -26,7 +28,9 @@ #include "library/traktor/traktorfeature.h" #include "library/librarycontrol.h" #include "library/setlogfeature.h" +#include "util/db/dbconnectionpooled.h" #include "util/sandbox.h" +#include "util/logger.h" #include "util/assert.h" #include "widget/wtracktableview.h" @@ -35,6 +39,19 @@ #include "controllers/keyboard/keyboardeventfilter.h" + +namespace { + +const mixxx::Logger kLogger("Library"); + +} // anonymous namespace + +//static +const QString Library::kConfigGroup("[Library]"); + +//static +const ConfigKey Library::kConfigKeyRepairDatabaseOnNextRestart(kConfigGroup, "RepairDatabaseOnNextRestart"); + // This is is the name which we use to register the WTrackTableView with the // WLibrary const QString Library::m_sTrackViewName = QString("WTrackTableView"); @@ -46,14 +63,50 @@ Library::Library(QObject* parent, UserSettingsPointer pConfig, PlayerManagerInterface* pPlayerManager, RecordingManager* pRecordingManager) : m_pConfig(pConfig), + m_mixxxDb(pConfig), + m_dbConnectionPooler(m_mixxxDb.connectionPool()), m_pSidebarModel(new SidebarModel(parent)), m_pTrackCollection(new TrackCollection(pConfig)), m_pLibraryControl(new LibraryControl(this)), m_pRecordingManager(pRecordingManager), - m_scanner(m_pTrackCollection, pConfig) { + m_scanner(m_mixxxDb.connectionPool(), m_pTrackCollection, pConfig) { + kLogger.info() << "Opening datbase connection"; + + const mixxx::DbConnectionPooled dbConnectionPooled(m_mixxxDb.connectionPool()); + if (!dbConnectionPooled) { + QMessageBox::critical(0, tr("Cannot open database"), + tr("Unable to establish a database connection.\n" + "Mixxx requires QT with SQLite support. Please read " + "the Qt SQL driver documentation for information on how " + "to build it.\n\n" + "Click OK to exit."), QMessageBox::Ok); + // TODO(XXX) something a little more elegant + exit(-1); + } + QSqlDatabase dbConnection(dbConnectionPooled); + DEBUG_ASSERT(dbConnection.isOpen()); + + kLogger.info() << "Initializing or upgrading database schema"; + if (!MixxxDb::initDatabaseSchema(dbConnection)) { + // TODO(XXX) something a little more elegant + exit(-1); + } + + // TODO(XXX): Add a checkbox in the library preferences for checking + // and repairing the database on the next restart of the application. + if (pConfig->getValue(kConfigKeyRepairDatabaseOnNextRestart, false)) { + kLogger.info() << "Checking and repairing database (if necessary)"; + m_pTrackCollection->repairDatabase(dbConnection); + // Reset config value + pConfig->setValue(kConfigKeyRepairDatabaseOnNextRestart, false); + } + + kLogger.info() << "Connecting database"; + m_pTrackCollection->connectDatabase(dbConnection); + qRegisterMetaType("Library::RemovalType"); - m_pKeyNotation.reset(new ControlObject(ConfigKey("[Library]", "key_notation"))); + m_pKeyNotation.reset(new ControlObject(ConfigKey(kConfigGroup, "key_notation"))); connect(&m_scanner, SIGNAL(scanStarted()), this, SIGNAL(scanStarted())); @@ -95,21 +148,21 @@ Library::Library(QObject* parent, UserSettingsPointer pConfig, //messagebox popup when you select them. (This forces you to reach for your //mouse or keyboard if you're using MIDI control and you scroll through them...) if (RhythmboxFeature::isSupported() && - pConfig->getValue(ConfigKey("[Library]","ShowRhythmboxLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup,"ShowRhythmboxLibrary"), true)) { addFeature(new RhythmboxFeature(this, m_pTrackCollection)); } - if (pConfig->getValue(ConfigKey("[Library]","ShowBansheeLibrary"), true)) { + if (pConfig->getValue(ConfigKey(kConfigGroup,"ShowBansheeLibrary"), true)) { BansheeFeature::prepareDbPath(pConfig); if (BansheeFeature::isSupported()) { addFeature(new BansheeFeature(this, m_pTrackCollection, pConfig)); } } if (ITunesFeature::isSupported() && - pConfig->getValue(ConfigKey("[Library]","ShowITunesLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup,"ShowITunesLibrary"), true)) { addFeature(new ITunesFeature(this, m_pTrackCollection)); } if (TraktorFeature::isSupported() && - pConfig->getValue(ConfigKey("[Library]","ShowTraktorLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup,"ShowTraktorLibrary"), true)) { addFeature(new TraktorFeature(this, m_pTrackCollection)); } @@ -126,8 +179,8 @@ Library::Library(QObject* parent, UserSettingsPointer pConfig, } m_iTrackTableRowHeight = m_pConfig->getValue( - ConfigKey("[Library]", "RowHeight"), kDefaultRowHeightPx); - QString fontStr = m_pConfig->getValueString(ConfigKey("[Library]", "Font")); + ConfigKey(kConfigGroup, "RowHeight"), kDefaultRowHeightPx); + QString fontStr = m_pConfig->getValueString(ConfigKey(kConfigGroup, "Font")); if (!fontStr.isEmpty()) { m_trackTableFont.fromString(fontStr); } else { @@ -147,6 +200,10 @@ Library::~Library() { } delete m_pLibraryControl; + + kLogger.info() << "Disconnecting database"; + m_pTrackCollection->disconnectDatabase(); + //IMPORTANT: m_pTrackCollection gets destroyed via the QObject hierarchy somehow. // Qt does it for us due to the way RJ wrote all this stuff. //Update: - OR NOT! As of Dec 8, 2009, this pointer must be destroyed manually otherwise diff --git a/src/library/library.h b/src/library/library.h index 7a0b8b7a5183..55fb5cbe1e1d 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -13,12 +13,14 @@ #include #include "preferences/usersettings.h" +#include "database/mixxxdb.h" #include "track/track.h" #include "recording/recordingmanager.h" #include "analysisfeature.h" #include "library/coverartcache.h" #include "library/setlogfeature.h" #include "library/scanner/libraryscanner.h" +#include "util/db/dbconnectionpooler.h" class TrackModel; class TrackCollection; @@ -37,13 +39,22 @@ class PlayerManagerInterface; class Library : public QObject { Q_OBJECT -public: + + public: + static const QString kConfigGroup; + + static const ConfigKey kConfigKeyRepairDatabaseOnNextRestart; + Library(QObject* parent, UserSettingsPointer pConfig, PlayerManagerInterface* pPlayerManager, RecordingManager* pRecordingManager); virtual ~Library(); + mixxx::DbConnectionPoolPtr dbConnectionPool() const { + return m_mixxxDb.connectionPool(); + } + void bindWidget(WLibrary* libraryWidget, KeyboardEventFilter* pKeyboard); void bindSidebarWidget(WLibrarySidebar* sidebarWidget); @@ -51,14 +62,6 @@ class Library : public QObject { void addFeature(LibraryFeature* feature); QStringList getDirs(); - // TODO(rryan) Transitionary only -- the only reason this is here is so the - // waveform widgets can signal to a player to load a track. This can be - // fixed by moving the waveform renderers inside player and connecting the - // signals directly. - TrackCollection* getTrackCollection() { - return m_pTrackCollection; - } - inline int getTrackTableRowHeight() const { return m_iTrackTableRowHeight; } @@ -119,7 +122,17 @@ class Library : public QObject { void scanFinished(); private: - UserSettingsPointer m_pConfig; + const UserSettingsPointer m_pConfig; + + // The Mixxx SQLite3 database + const MixxxDb m_mixxxDb; + + // The Mixxx database connection for the thread that creates + // and owns this library instance. TODO(XXX): Move database + // related code out of the GUI into multiple, dedicated + // worker threads. + const mixxx::DbConnectionPooler m_dbConnectionPooler; + SidebarModel* m_pSidebarModel; TrackCollection* m_pTrackCollection; QList m_features; diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index d3d13e3472a4..484ae2e36f98 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -13,7 +13,7 @@ #include "widget/wlibrarysidebar.h" #include "library/library.h" #include "library/libraryview.h" -#include "util/container.h" + LoadToGroupController::LoadToGroupController(QObject* pParent, const QString& group) : QObject(pParent), diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h index a0dba41f7463..7a9b606354b7 100644 --- a/src/library/libraryfeature.h +++ b/src/library/libraryfeature.h @@ -16,7 +16,7 @@ #include #include "track/track.h" -#include "treeitemmodel.h" +#include "library/treeitemmodel.h" #include "library/coverartcache.h" #include "library/dao/trackdao.h" diff --git a/src/library/mixxxlibraryfeature.h b/src/library/mixxxlibraryfeature.h index 54d0a14f51e8..db79b7eb2660 100644 --- a/src/library/mixxxlibraryfeature.h +++ b/src/library/mixxxlibraryfeature.h @@ -16,7 +16,7 @@ #include "library/libraryfeature.h" #include "library/dao/trackdao.h" -#include "treeitemmodel.h" +#include "library/treeitemmodel.h" #include "preferences/usersettings.h" class DlgHidden; diff --git a/src/library/playlistfeature.cpp b/src/library/playlistfeature.cpp index ddfdbe87455c..7b22c3b6b6cc 100644 --- a/src/library/playlistfeature.cpp +++ b/src/library/playlistfeature.cpp @@ -15,6 +15,7 @@ #include "library/parser.h" #include "controllers/keyboard/keyboardeventfilter.h" #include "sources/soundsourceproxy.h" +#include "util/db/dbconnection.h" #include "util/dnd.h" #include "util/duration.h" @@ -140,7 +141,7 @@ void PlaylistFeature::buildPlaylistList() { "LEFT JOIN library ON PlaylistTracks.track_id = library.id " "WHERE Playlists.hidden = 0 " "GROUP BY Playlists.id"); - queryString.append(DbConnection::collateLexicographically( + queryString.append(mixxx::DbConnection::collateLexicographically( " ORDER BY sort_name")); QSqlQuery query(m_pTrackCollection->database()); if (!query.exec(queryString)) { diff --git a/src/library/queryutil.h b/src/library/queryutil.h index a0742ac83b2d..e7ab94cdfea4 100644 --- a/src/library/queryutil.h +++ b/src/library/queryutil.h @@ -10,7 +10,7 @@ class ScopedTransaction { public: - explicit ScopedTransaction(QSqlDatabase& database) : + explicit ScopedTransaction(const QSqlDatabase& database) : m_database(database), m_active(false) { if (!transaction()) { @@ -62,7 +62,7 @@ class ScopedTransaction { return result; } private: - QSqlDatabase& m_database; + QSqlDatabase m_database; bool m_active; }; @@ -94,7 +94,7 @@ class FieldEscaper final { } } - const QSqlDatabase& m_database; + QSqlDatabase m_database; mutable QSqlField m_stringField; }; diff --git a/src/library/scanner/libraryscanner.cpp b/src/library/scanner/libraryscanner.cpp index 843eb31d0560..aa4c9b4785cb 100644 --- a/src/library/scanner/libraryscanner.cpp +++ b/src/library/scanner/libraryscanner.cpp @@ -1,63 +1,51 @@ -/*************************************************************************** - libraryscanner.cpp - scans library in a thread - ------------------- - begin : 11/27/2007 - copyright : (C) 2007 Albert Santoni - email : gamegod \a\t users.sf.net -***************************************************************************/ - -/*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -***************************************************************************/ - -#include - #include "library/scanner/libraryscanner.h" #include "sources/soundsourceproxy.h" #include "library/scanner/recursivescandirectorytask.h" #include "library/scanner/libraryscannerdlg.h" +#include "library/scanner/scannertask.h" #include "library/queryutil.h" #include "library/coverartutils.h" #include "library/trackcollection.h" +#include "util/logger.h" #include "util/trace.h" #include "util/file.h" #include "util/timer.h" #include "library/scanner/scannerutil.h" +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" + +namespace { // TODO(rryan) make configurable const int kScannerThreadPoolSize = 1; -LibraryScanner::LibraryScanner(TrackCollection* collection, - UserSettingsPointer pConfig) - : m_pCollection(collection), - m_libraryHashDao(m_database), - m_cueDao(m_database), - m_playlistDao(m_database), - m_directoryDao(m_database), - m_analysisDao(m_database, pConfig), - m_trackDao(m_database, - m_cueDao, m_playlistDao, - m_analysisDao, m_libraryHashDao, - pConfig), - m_stateSema(1), // only one transaction is possible at a time - m_state(IDLE) { - // Don't initialize m_database here, we need to do it in run() so the DB - // conn is in the right thread. - qDebug() << "Starting LibraryScanner thread."; +mixxx::Logger kLogger("LibraryScanner"); + +QAtomicInt s_instanceCounter(0); + +} // anonymous namespace +LibraryScanner::LibraryScanner( + mixxx::DbConnectionPoolPtr pDbConnectionPool, + TrackCollection* pTrackCollection, + const UserSettingsPointer& pConfig) + : m_pDbConnectionPool(std::move(pDbConnectionPool)), + m_pTrackCollection(pTrackCollection), + m_analysisDao(pConfig), + m_trackDao(m_cueDao, m_playlistDao, + m_analysisDao, m_libraryHashDao, + pConfig), + m_stateSema(1), // only one transaction is possible at a time + m_state(IDLE) { // Move LibraryScanner to its own thread so that our signals/slots will // queue to our event loop. + kLogger.debug() << "Starting thread"; moveToThread(this); m_pool.moveToThread(this); - unsigned static id = 0; // the id of this LibraryScanner, for debugging purposes - setObjectName(QString("LibraryScanner %1").arg(++id)); + const int instanceId = s_instanceCounter.fetchAndAddAcquire(1) + 1; + setObjectName(QString("LibraryScanner %1").arg(instanceId)); m_pool.setMaxThreadCount(kScannerThreadPoolSize); @@ -70,16 +58,14 @@ LibraryScanner::LibraryScanner(TrackCollection* collection, // scan is finished, because we might have modified the database directly // when we detected moved files, and the TIOs corresponding to the moved // files would then have the wrong track location. - if (collection != NULL) { // false only during test - TrackDAO* dao = &(collection->getTrackDAO()); - connect(this, SIGNAL(scanFinished()), dao, SLOT(clearCache())); - connect(this, SIGNAL(trackAdded(TrackPointer)), - dao, SLOT(databaseTrackAdded(TrackPointer))); - connect(this, SIGNAL(tracksMoved(QSet, QSet)), - dao, SLOT(databaseTracksMoved(QSet, QSet))); - connect(this, SIGNAL(tracksChanged(QSet)), - dao, SLOT(databaseTracksChanged(QSet))); - } + TrackDAO* dao = &(m_pTrackCollection->getTrackDAO()); + connect(this, SIGNAL(scanFinished()), dao, SLOT(clearCache())); + connect(this, SIGNAL(trackAdded(TrackPointer)), + dao, SLOT(databaseTrackAdded(TrackPointer))); + connect(this, SIGNAL(tracksMoved(QSet, QSet)), + dao, SLOT(databaseTracksMoved(QSet, QSet))); + connect(this, SIGNAL(tracksChanged(QSet)), + dao, SLOT(databaseTracksChanged(QSet))); m_pProgressDlg.reset(new LibraryScannerDlg()); connect(this, SIGNAL(progressLoading(QString)), @@ -102,62 +88,41 @@ LibraryScanner::LibraryScanner(TrackCollection* collection, LibraryScanner::~LibraryScanner() { cancelAndQuit(); - - // There should never be an outstanding transaction when this code is - // called. If there is, it means we probably aren't committing a transaction - // somewhere that should be. - if (m_database.isOpen()) { - qDebug() << "Closing database" << m_database.connectionName(); - - // Rollback any uncommitted transaction - if (m_database.rollback()) { - qDebug() << "ERROR: There was a transaction in progress while closing the library scanner connection." - << "There is a logic error somewhere."; - } - // Close our database connection - m_database.close(); - } - qDebug() << "LibraryScanner destroyed"; } void LibraryScanner::run() { - Trace trace("LibraryScanner"); - if (m_pCollection != NULL) { // false only during tests - if (!m_database.isValid()) { - m_database = QSqlDatabase::cloneDatabase(m_pCollection->database(), "LIBRARY_SCANNER"); - } - - if (!m_database.isOpen()) { - // Open the database connection in this thread. - if (!m_database.open()) { - qDebug() << "Failed to open database from library scanner thread." << m_database.lastError(); - return; - } + kLogger.debug() << "Entering thread"; + { + Trace trace("LibraryScanner"); + + const mixxx::DbConnectionPooler dbConnectionPooler(m_pDbConnectionPool); + const mixxx::DbConnectionPooled dbConnectionPooled(m_pDbConnectionPool); + if (!dbConnectionPooled) { + kLogger.warning() + << "Failed to open database connection for library scanner"; + kLogger.debug() << "Exiting thread"; + return; } - - m_libraryHashDao.setDatabase(m_database); - m_cueDao.setDatabase(m_database); - m_trackDao.setDatabase(m_database); - m_playlistDao.setDatabase(m_database); - m_analysisDao.setDatabase(m_database); - m_directoryDao.setDatabase(m_database); - - m_libraryHashDao.initialize(); - m_cueDao.initialize(); - m_trackDao.initialize(); - m_playlistDao.initialize(); - m_analysisDao.initialize(); - m_directoryDao.initialize(); + QSqlDatabase dbConnection(dbConnectionPooled); + DEBUG_ASSERT(dbConnection.isOpen()); + + m_libraryHashDao.initialize(dbConnection); + m_cueDao.initialize(dbConnection); + m_trackDao.initialize(dbConnection); + m_playlistDao.initialize(dbConnection); + m_analysisDao.initialize(dbConnection); + m_directoryDao.initialize(dbConnection); + + // Start the event loop. + kLogger.debug() << "Event loop starting"; + exec(); + kLogger.debug() << "Event loop stopped"; } - - // Start the event loop. - qDebug() << "LibraryScanner event loop starting."; - exec(); - qDebug() << "LibraryScanner event loop stopped."; + kLogger.debug() << "Exiting thread"; } void LibraryScanner::slotStartScan() { - qDebug() << "LibraryScanner::slotStartScan"; + kLogger.debug() << "slotStartScan()"; DEBUG_ASSERT(m_state == STARTING); // Recursively scan each directory in the directories table. @@ -197,7 +162,7 @@ void LibraryScanner::slotStartScan() { // we think they are) m_trackDao.invalidateTrackLocationsInLibrary(); - qDebug() << "Recursively scanning library."; + kLogger.debug() << "Recursively scanning library."; // Start scanning the library. This prepares insertion queries in TrackDAO // (must be called before calling addTracksAdd) and begins a transaction. @@ -233,9 +198,9 @@ void LibraryScanner::slotStartScan() { // is called when all tasks of the first stage are done (threads are finished) void LibraryScanner::slotFinishHashedScan() { - qDebug() << "LibraryScanner::slotFinishHashedScan"; + kLogger.debug() << "slotFinishHashedScan"; VERIFY_OR_DEBUG_ASSERT(!m_scannerGlobal.isNull()) { - qWarning() << "No scanner global state exists in LibraryScanner::slotFinishHashedScan"; + kLogger.critical() << "No scanner global state exists in slotFinishHashedScan"; return; } @@ -277,12 +242,14 @@ void LibraryScanner::cleanUpScan() { // Start a transaction for all the library hashing (moved file // detection) stuff. - ScopedTransaction transaction(m_database); + const mixxx::DbConnectionPooled dbConnectionPooled(m_pDbConnectionPool); + QSqlDatabase dbConnection(dbConnectionPooled); + ScopedTransaction transaction(dbConnection); - qDebug() << "Marking tracks in changed directories as verified"; + kLogger.debug() << "Marking tracks in changed directories as verified"; m_trackDao.markTrackLocationsAsVerified(m_scannerGlobal->verifiedTracks()); - qDebug() << "Marking unchanged directories and tracks as verified"; + kLogger.debug() << "Marking unchanged directories and tracks as verified"; m_libraryHashDao.updateDirectoryStatuses( m_scannerGlobal->verifiedDirectories(), false, @@ -295,7 +262,7 @@ void LibraryScanner::cleanUpScan() { // outside of the library directories, files that have been // moved/deleted/renamed and are in duplicate directories by symlinks or // non normalized paths. - qDebug() << "Checking remaining unverified tracks."; + kLogger.debug() << "Checking remaining unverified tracks"; if (!m_trackDao.verifyRemainingTracks( m_libraryRootDirs, m_scannerGlobal->shouldCancelPointer())) { @@ -303,19 +270,15 @@ void LibraryScanner::cleanUpScan() { return; } - qDebug() << "Marking unverified tracks as deleted."; + kLogger.debug() << "Marking unverified tracks as deleted"; m_trackDao.markUnverifiedTracksAsDeleted(); - qDebug() << "Marking unverified directories as deleted."; + kLogger.debug() << "Marking unverified directories as deleted"; m_libraryHashDao.markUnverifiedDirectoriesAsDeleted(); - // For making the scanner slow, during debugging - //qDebug() << "Burn CPU"; - //for (int i = 0;i < 1000000000; i++) asm("nop"); - // Check to see if the "deleted" tracks showed up in another location, // and if so, do some magic to update all our tables. - qDebug() << "Detecting moved files."; + kLogger.debug() << "Detecting moved files"; QSet tracksMovedSetOld; QSet tracksMovedSetNew; if (!m_trackDao.detectMovedTracks(&tracksMovedSetOld, @@ -334,7 +297,7 @@ void LibraryScanner::cleanUpScan() { transaction.commit(); - qDebug() << "Detecting cover art for unscanned files."; + kLogger.debug() << "Detecting cover art for unscanned files"; QSet coverArtTracksChanged; m_trackDao.detectCoverArtForTracksWithoutCover( m_scannerGlobal->shouldCancelPointer(), &coverArtTracksChanged); @@ -347,18 +310,18 @@ void LibraryScanner::cleanUpScan() { // is called when all tasks of the second stage are done (threads are finished) void LibraryScanner::slotFinishUnhashedScan() { - qDebug() << "LibraryScanner::slotFinishUnhashedScan"; + kLogger.debug() << "slotFinishUnhashedScan"; VERIFY_OR_DEBUG_ASSERT(!m_scannerGlobal.isNull()) { - qWarning() << "No scanner global state exists in LibraryScanner::slotFinishUnhashedScan"; + kLogger.critical() << "No scanner global state exists in slotFinishUnhashedScan"; return; } bool bScanFinishedCleanly = m_scannerGlobal->scanFinishedCleanly(); if (bScanFinishedCleanly) { - qDebug() << "Recursive scanning finished cleanly."; + kLogger.debug() << "Recursive scanning finished cleanly"; } else { - qDebug() << "Recursive scanning interrupted by the user."; + kLogger.debug() << "Recursive scanning interrupted by the user"; } // Finish adding the tracks -- rollback the transaction if the scan did not @@ -371,9 +334,9 @@ void LibraryScanner::slotFinishUnhashedScan() { } if (!m_scannerGlobal->shouldCancel() && bScanFinishedCleanly) { - qDebug() << "Scan finished cleanly"; + kLogger.debug() << "Scan finished cleanly"; } else { - qDebug() << "Scan cancelled"; + kLogger.debug() << "Scan cancelled"; } // TODO(XXX) doesn't take into account verifyRemainingTracks. @@ -442,7 +405,7 @@ void LibraryScanner::cancel() { } void LibraryScanner::queueTask(ScannerTask* pTask) { - //qDebug() << "LibraryScanner::queueTask" << pTask; + //kLogger.debug() << "queueTask" << pTask; ScopedTimer timer("LibraryScanner::queueTask"); if (m_scannerGlobal.isNull() || m_scannerGlobal->shouldCancel()) { return; @@ -472,7 +435,7 @@ void LibraryScanner::queueTask(ScannerTask* pTask) { void LibraryScanner::slotDirectoryHashedAndScanned(const QString& directoryPath, bool newDirectory, int hash) { ScopedTimer timer("LibraryScanner::slotDirectoryHashedAndScanned"); - //qDebug() << "LibraryScanner::sloDirectoryHashedAndScanned" << directoryPath + //kLogger.debug() << "sloDirectoryHashedAndScanned" << directoryPath // << newDirectory << hash; // For statistics tracking -- if we hashed a directory then we scanned it @@ -491,7 +454,7 @@ void LibraryScanner::slotDirectoryHashedAndScanned(const QString& directoryPath, void LibraryScanner::slotDirectoryUnchanged(const QString& directoryPath) { ScopedTimer timer("LibraryScanner::slotDirectoryUnchanged"); - //qDebug() << "LibraryScanner::slotDirectoryUnchanged" << directoryPath; + //kLogger.debug() << "slotDirectoryUnchanged" << directoryPath; if (m_scannerGlobal) { m_scannerGlobal->addVerifiedDirectory(directoryPath); } @@ -499,7 +462,7 @@ void LibraryScanner::slotDirectoryUnchanged(const QString& directoryPath) { } void LibraryScanner::slotTrackExists(const QString& trackPath) { - //qDebug() << "LibraryScanner::slotTrackExists" << trackPath; + //kLogger.debug() << "slotTrackExists" << trackPath; ScopedTimer timer("LibraryScanner::slotTrackExists"); if (m_scannerGlobal) { m_scannerGlobal->addVerifiedTrack(trackPath); @@ -507,7 +470,7 @@ void LibraryScanner::slotTrackExists(const QString& trackPath) { } void LibraryScanner::slotAddNewTrack(const QString& trackPath) { - //qDebug() << "LibraryScanner::slotAddNewTrack" << trackPath; + //kLogger.debug() << "slotAddNewTrack" << trackPath; ScopedTimer timer("LibraryScanner::addNewTrack"); // For statistics tracking and to detect moved tracks TrackPointer pTrack(m_trackDao.addTracksAddFile(trackPath, false)); @@ -530,7 +493,7 @@ void LibraryScanner::slotAddNewTrack(const QString& trackPath) { if (m_scannerGlobal) { m_scannerGlobal->trackAdded(trackPath); } - qWarning() + kLogger.warning() << "Failed to add track to library:" << trackPath; } @@ -550,14 +513,14 @@ bool LibraryScanner::changeScannerState(ScannerState newState) { // twice if (m_stateSema.tryAcquire()) { if (m_state != IDLE) { - qDebug() << "LibraryScanner: Scan already in progress."; + kLogger.debug() << "Scan already in progress"; m_stateSema.release(); return false; } m_state = STARTING; return true; } else { - qDebug() << "LibraryScanner: can't acquire semaphore, state =" << m_state; + kLogger.debug() << "can't acquire semaphore, state =" << m_state; return false; } case SCANNING: diff --git a/src/library/scanner/libraryscanner.h b/src/library/scanner/libraryscanner.h index ac71288fa6c5..b45e908422b1 100644 --- a/src/library/scanner/libraryscanner.h +++ b/src/library/scanner/libraryscanner.h @@ -1,34 +1,10 @@ -/*************************************************************************** - libraryscanner.h - scans library in a thread - ------------------- - begin : 11/27/2007 - copyright : (C) 2007 Albert Santoni - email : gamegod \a\t users.sf.net -***************************************************************************/ - -/*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -***************************************************************************/ - -#ifndef LIBRARYSCANNER_H -#define LIBRARYSCANNER_H +#ifndef MIXXX_LIBRARYSCANNER_H +#define MIXXX_LIBRARYSCANNER_H #include #include -#include #include -#include -#include -#include #include -#include -#include -#include #include #include @@ -39,11 +15,12 @@ #include "library/dao/trackdao.h" #include "library/dao/analysisdao.h" #include "library/scanner/scannerglobal.h" -#include "library/scanner/scannertask.h" -#include "util/sandbox.h" #include "track/track.h" +#include "util/db/dbconnectionpool.h" + #include +class ScannerTask; class LibraryScannerDlg; class TrackCollection; @@ -51,9 +28,11 @@ class LibraryScanner : public QThread { FRIEND_TEST(LibraryScannerTest, ScannerRoundtrip); Q_OBJECT public: - LibraryScanner(TrackCollection* collection, - UserSettingsPointer pConfig); - virtual ~LibraryScanner(); + LibraryScanner( + mixxx::DbConnectionPoolPtr pDbConnectionPool, + TrackCollection* pTrackCollection, + const UserSettingsPointer& pConfig); + ~LibraryScanner() override; public slots: // Call from any thread to start a scan. Does nothing if a scan is already @@ -119,12 +98,11 @@ class LibraryScanner : public QThread { void cleanUpScan(); + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; + // The library trackcollection. Do not touch this from the library scanner // thread. - TrackCollection* m_pCollection; - - // The library scanner thread's database connection. - QSqlDatabase m_database; + TrackCollection* m_pTrackCollection; // The pool of threads used for worker tasks. QThreadPool m_pool; @@ -151,4 +129,4 @@ class LibraryScanner : public QThread { QScopedPointer m_pProgressDlg; }; -#endif +#endif // MIXXX_LIBRARYSCANNER_H diff --git a/src/library/schemamanager.cpp b/src/library/schemamanager.cpp deleted file mode 100644 index 057bfe3be196..000000000000 --- a/src/library/schemamanager.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// schemamanager.cpp -// Created 12/29/2009 by RJ Ryan (rryan@mit.edu) - -#include - -#include "library/schemamanager.h" -#include "library/queryutil.h" -#include "util/xml.h" -#include "util/assert.h" - -const QString SchemaManager::SETTINGS_VERSION_STRING = "mixxx.schema.version"; -const QString SchemaManager::SETTINGS_MINCOMPATIBLE_STRING = "mixxx.schema.min_compatible_version"; - -// static -SchemaManager::Result SchemaManager::upgradeToSchemaVersion( - const QString& schemaFilename, - QSqlDatabase& db, const int targetVersion) { - SettingsDAO settings(db); - int currentVersion = getCurrentSchemaVersion(settings); - VERIFY_OR_DEBUG_ASSERT(currentVersion >= 0) { - return RESULT_UPGRADE_FAILED; - } - - if (currentVersion == targetVersion) { - qDebug() << "SchemaManager::upgradeToSchemaVersion already at version" - << targetVersion; - return RESULT_OK; - } else if (currentVersion < targetVersion) { - qDebug() << "SchemaManager::upgradeToSchemaVersion upgrading" - << targetVersion-currentVersion << "versions to version" - << targetVersion; - } else { - qDebug() << "SchemaManager::upgradeToSchemaVersion already past target " - "version. currentVersion:" - << currentVersion << "targetVersion:" - << targetVersion; - - if (isBackwardsCompatible(settings, currentVersion, targetVersion)) { - qDebug() << "Current schema version is backwards-compatible with" << targetVersion; - return RESULT_OK; - } else { - return RESULT_BACKWARDS_INCOMPATIBLE; - } - } - - qDebug() << "Loading schema" << schemaFilename; - QDomElement schemaRoot = XmlParse::openXMLFile(schemaFilename, "schema"); - - if (schemaRoot.isNull()) { - // Error parsing xml file - return RESULT_SCHEMA_ERROR; - } - - QDomNodeList revisions = schemaRoot.childNodes(); - - QMap revisionMap; - - for (int i = 0; i < revisions.count(); i++) { - QDomElement revision = revisions.at(i).toElement(); - QString version = revision.attribute("version"); - VERIFY_OR_DEBUG_ASSERT(!version.isNull()) { - // xml file is not valid - return RESULT_SCHEMA_ERROR; - } - int iVersion = version.toInt(); - revisionMap[iVersion] = revision; - } - - // The checks above guarantee that currentVersion < targetVersion when we - // get here. - while (currentVersion < targetVersion) { - int thisTarget = currentVersion + 1; - - // Now that we bake the schema.xml into the binary it is a programming - // error if we include a schema.xml that does not have information on - // how to get all the way to targetVersion. - if (!revisionMap.contains(thisTarget)) { - qDebug() << "SchemaManager::upgradeToSchemaVersion" - << "Don't know how to get to" - << thisTarget << "from" << currentVersion; - return RESULT_SCHEMA_ERROR; - } - - QDomElement revision = revisionMap[thisTarget]; - QDomElement eDescription = revision.firstChildElement("description"); - QDomElement eSql = revision.firstChildElement("sql"); - QString minCompatibleVersion = revision.attribute("min_compatible"); - - // Default the min-compatible version to the current version string if - // it's not in the schema.xml - if (minCompatibleVersion.isNull()) { - minCompatibleVersion = QString::number(thisTarget); - } - - VERIFY_OR_DEBUG_ASSERT(!eSql.isNull()) { - // xml file is not valid - return RESULT_SCHEMA_ERROR; - } - - QString description = eDescription.text(); - QString sql = eSql.text(); - - qDebug() << "Applying version" << thisTarget << ":" - << description.trimmed(); - - ScopedTransaction transaction(db); - - // TODO(XXX) We can't have semicolons in schema.xml for anything other - // than statement separators. - QStringList sqlStatements = sql.split(";"); - - QStringListIterator it(sqlStatements); - - QSqlQuery query(db); - bool result = true; - while (result && it.hasNext()) { - QString statement = it.next().trimmed(); - if (statement.isEmpty()) { - continue; - } - result = result && query.exec(statement); - if (!result) { - qDebug() << "Failed query:" - << statement - << query.lastError(); - } - } - - if (result) { - currentVersion = thisTarget; - settings.setValue(SETTINGS_VERSION_STRING, thisTarget); - settings.setValue(SETTINGS_MINCOMPATIBLE_STRING, minCompatibleVersion); - transaction.commit(); - } else { - qDebug() << "Failed to move from version" << currentVersion - << "to version" << thisTarget; - transaction.rollback(); - return RESULT_UPGRADE_FAILED; - } - } - return RESULT_OK; -} - -// static -int SchemaManager::getCurrentSchemaVersion(SettingsDAO& settings) { - QString currentSchemaVersion = settings.getValue(SETTINGS_VERSION_STRING); - // May be a null string if the schema has not been created. We default the - // startVersion to 0 so that we automatically try to upgrade to revision 1. - int currentVersion = 0; - if (!currentSchemaVersion.isNull()) { - currentVersion = currentSchemaVersion.toInt(); - } - return currentVersion; -} - -// static -bool SchemaManager::isBackwardsCompatible(SettingsDAO& settings, - int currentVersion, - int targetVersion) { - QString backwardsCompatibleVersion = - settings.getValue(SETTINGS_MINCOMPATIBLE_STRING); - bool ok = false; - int iBackwardsCompatibleVersion = backwardsCompatibleVersion.toInt(&ok); - - // If the current backwards compatible schema version is not stored in the - // settings table, assume the current schema version is only backwards - // compatible with itself. - if (backwardsCompatibleVersion.isNull() || !ok) { - // rryan 11/2010 We just added the backwards compatible flags, and some - // people using the Mixxx trunk are already on schema version 7. This - // special case is for them. Schema version 7 is backwards compatible - // with schema version 3. - if (currentVersion == 7) { - iBackwardsCompatibleVersion = 3; - } else { - iBackwardsCompatibleVersion = currentVersion; - } - } - - // If the target version is greater than the minimum compatible version of - // the current schema, then the current schema is backwards compatible with - // targetVersion. - return iBackwardsCompatibleVersion <= targetVersion; -} diff --git a/src/library/schemamanager.h b/src/library/schemamanager.h deleted file mode 100644 index 3997a6c95b3e..000000000000 --- a/src/library/schemamanager.h +++ /dev/null @@ -1,34 +0,0 @@ -// schemamanager.h -// Created 12/29/2009 by RJ Ryan (rryan@mit.edu) - -#ifndef SCHEMAMANAGER_H -#define SCHEMAMANAGER_H - -#include - -#include "preferences/usersettings.h" -#include "library/dao/settingsdao.h" - -class SchemaManager { - public: - enum Result { - RESULT_OK, - RESULT_BACKWARDS_INCOMPATIBLE, - RESULT_UPGRADE_FAILED, - RESULT_SCHEMA_ERROR - }; - - static Result upgradeToSchemaVersion(const QString& schemaFilename, - QSqlDatabase& db, const int targetVersion); - - static const QString SETTINGS_VERSION_STRING; - static const QString SETTINGS_MINCOMPATIBLE_STRING; - - private: - static bool isBackwardsCompatible(SettingsDAO& settings, - int currentVersion, - int targetVersion); - static int getCurrentSchemaVersion(SettingsDAO& settings); -}; - -#endif /* SCHEMAMANAGER_H */ diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 0a5fd9f23283..e6e4bae75441 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -1,127 +1,56 @@ -#include -#include +#include #include "library/trackcollection.h" -#include "library/librarytablemodel.h" -#include "library/schemamanager.h" -#include "library/crate/cratestorage.h" #include "track/track.h" +#include "util/logger.h" #include "util/db/sqltransaction.h" #include "util/assert.h" -// static -const int TrackCollection::kRequiredSchemaVersion = 27; - -TrackCollection::TrackCollection(UserSettingsPointer pConfig) - : m_pConfig(pConfig), - m_dbConnection(m_pConfig->getSettingsPath()), - m_playlistDao(database()), - m_cueDao(database()), - m_directoryDao(database()), - m_analysisDao(database(), pConfig), - m_libraryHashDao(database()), - m_trackDao(database(), m_cueDao, m_playlistDao, - m_analysisDao, m_libraryHashDao, pConfig) { - // Check for tables and create them if missing - if (!checkForTables()) { - // TODO(XXX) something a little more elegant - exit(-1); - } -} -TrackCollection::~TrackCollection() { - qDebug() << "~TrackCollection()"; - m_trackDao.finish(); - m_crates.detachDatabase(); -} +namespace { + mixxx::Logger kLogger("TrackCollection"); +} // anonymous namespace -bool TrackCollection::checkForTables() { - if (!m_dbConnection) { - QMessageBox::critical(0, tr("Cannot open database"), - tr("Unable to establish a database connection.\n" - "Mixxx requires QT with SQLite support. Please read " - "the Qt SQL driver documentation for information on how " - "to build it.\n\n" - "Click OK to exit."), QMessageBox::Ok); - return false; - } - - // The schema XML is baked into the binary via Qt resources. - QString schemaFilename(":/schema.xml"); - QString okToExit = tr("Click OK to exit."); - QString upgradeFailed = tr("Cannot upgrade database schema"); - QString upgradeToVersionFailed = - tr("Unable to upgrade your database schema to version %1") - .arg(QString::number(kRequiredSchemaVersion)); - QString helpEmail = tr("For help with database issues contact:") + "\n" + - "mixxx-devel@lists.sourceforge.net"; - - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - schemaFilename, database(), kRequiredSchemaVersion); - switch (result) { - case SchemaManager::RESULT_BACKWARDS_INCOMPATIBLE: - QMessageBox::warning( - 0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("Your mixxxdb.sqlite file was created by a newer " - "version of Mixxx and is incompatible.") + - "\n\n" + okToExit, - QMessageBox::Ok); - return false; - case SchemaManager::RESULT_UPGRADE_FAILED: - QMessageBox::warning( - 0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("Your mixxxdb.sqlite file may be corrupt.") + "\n" + - tr("Try renaming it and restarting Mixxx.") + "\n" + - helpEmail + "\n\n" + okToExit, - QMessageBox::Ok); - return false; - case SchemaManager::RESULT_SCHEMA_ERROR: - QMessageBox::warning( - 0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("The database schema file is invalid.") + "\n" + - helpEmail + "\n\n" + okToExit, - QMessageBox::Ok); - return false; - case SchemaManager::RESULT_OK: - default: - break; - } - - m_trackDao.initialize(); - m_playlistDao.initialize(); - m_cueDao.initialize(); - m_directoryDao.initialize(); - m_libraryHashDao.initialize(); - m_crates.attachDatabase(database()); - return true; +TrackCollection::TrackCollection( + const UserSettingsPointer& pConfig) + : m_analysisDao(pConfig), + m_trackDao(m_cueDao, m_playlistDao, + m_analysisDao, m_libraryHashDao, pConfig) { } -TrackDAO& TrackCollection::getTrackDAO() { - return m_trackDao; +TrackCollection::~TrackCollection() { + kLogger.debug() << "~TrackCollection()"; + // The database should have been detached earlier + DEBUG_ASSERT(!m_database.isOpen()); } -PlaylistDAO& TrackCollection::getPlaylistDAO() { - return m_playlistDao; +void TrackCollection::repairDatabase(QSqlDatabase database) { + m_crates.repairDatabase(database); } -DirectoryDAO& TrackCollection::getDirectoryDAO() { - return m_directoryDao; +void TrackCollection::connectDatabase(QSqlDatabase database) { + m_database = database; + m_trackDao.initialize(database); + m_playlistDao.initialize(database); + m_cueDao.initialize(database); + m_directoryDao.initialize(database); + m_libraryHashDao.initialize(database); + m_crates.connectDatabase(database); } -QSharedPointer TrackCollection::getTrackSource() { - return m_defaultTrackSource; +void TrackCollection::disconnectDatabase() { + m_database = QSqlDatabase(); + m_trackDao.finish(); + m_crates.disconnectDatabase(); } -void TrackCollection::setTrackSource(QSharedPointer trackSource) { - VERIFY_OR_DEBUG_ASSERT(m_defaultTrackSource.isNull()) { +void TrackCollection::setTrackSource(QSharedPointer pTrackSource) { + VERIFY_OR_DEBUG_ASSERT(m_pTrackSource.isNull()) { return; } - m_defaultTrackSource = trackSource; + m_pTrackSource = pTrackSource; } void TrackCollection::relocateDirectory(QString oldDir, QString newDir) { @@ -180,11 +109,11 @@ bool TrackCollection::hideTracks(const QList& trackIds) { } // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_trackDao.onHidingTracks(transaction, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_trackDao.onHidingTracks(trackIds)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -208,11 +137,11 @@ bool TrackCollection::hideTracks(const QList& trackIds) { bool TrackCollection::unhideTracks(const QList& trackIds) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_trackDao.onUnhidingTracks(transaction, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_trackDao.onUnhidingTracks(trackIds)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -236,11 +165,11 @@ bool TrackCollection::unhideTracks(const QList& trackIds) { bool TrackCollection::purgeTracks( const QList& trackIds) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_trackDao.onPurgingTracks(transaction, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_trackDao.onPurgingTracks(trackIds)) { return false; } // Collect crates of tracks that will be purged before actually purging @@ -248,7 +177,7 @@ bool TrackCollection::purgeTracks( // all crates on purging. QSet modifiedCrateSummaries( m_crates.collectCrateIdsOfTracks(trackIds)); - VERIFY_OR_DEBUG_ASSERT(m_crates.onPurgingTracks(transaction, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onPurgingTracks(trackIds)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -280,12 +209,12 @@ bool TrackCollection::insertCrate( const Crate& crate, CrateId* pCrateId) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } CrateId crateId; - VERIFY_OR_DEBUG_ASSERT(m_crates.onInsertingCrate(transaction, crate, &crateId)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onInsertingCrate(crate, &crateId)) { return false; } DEBUG_ASSERT(crateId.isValid()); @@ -305,11 +234,11 @@ bool TrackCollection::insertCrate( bool TrackCollection::updateCrate( const Crate& crate) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_crates.onUpdatingCrate(transaction, crate)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onUpdatingCrate(crate)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -325,11 +254,11 @@ bool TrackCollection::updateCrate( bool TrackCollection::deleteCrate( CrateId crateId) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_crates.onDeletingCrate(transaction, crateId)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onDeletingCrate(crateId)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -346,11 +275,11 @@ bool TrackCollection::addCrateTracks( CrateId crateId, const QList& trackIds) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_crates.onAddingCrateTracks(transaction, crateId, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onAddingCrateTracks(crateId, trackIds)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { @@ -367,11 +296,11 @@ bool TrackCollection::removeCrateTracks( CrateId crateId, const QList& trackIds) { // Transactional - SqlTransaction transaction(database()); + SqlTransaction transaction(m_database); VERIFY_OR_DEBUG_ASSERT(transaction) { return false; } - VERIFY_OR_DEBUG_ASSERT(m_crates.onRemovingCrateTracks(transaction, crateId, trackIds)) { + VERIFY_OR_DEBUG_ASSERT(m_crates.onRemovingCrateTracks(crateId, trackIds)) { return false; } VERIFY_OR_DEBUG_ASSERT(transaction.commit()) { diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 903421dcf59a..97b2c769b44b 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -1,7 +1,6 @@ #ifndef TRACKCOLLECTION_H #define TRACKCOLLECTION_H -#include #include #include #include @@ -15,50 +14,55 @@ #include "library/dao/analysisdao.h" #include "library/dao/directorydao.h" #include "library/dao/libraryhashdao.h" -#include "util/db/dbconnection.h" + // forward declaration(s) class Track; -#define AUTODJ_TABLE "Auto DJ" - -class BpmDetector; - // Manages everything around tracks. -class TrackCollection : public QObject { +class TrackCollection : public QObject, + public virtual /*implements*/ SqlStorage { Q_OBJECT public: - static const int kRequiredSchemaVersion; - - explicit TrackCollection(UserSettingsPointer pConfig); + explicit TrackCollection( + const UserSettingsPointer& pConfig); ~TrackCollection() override; - bool checkForTables(); + void repairDatabase( + QSqlDatabase database) override; - void resetLibaryCancellation(); + void connectDatabase( + QSqlDatabase database) override; + void disconnectDatabase() override; - QSqlDatabase& database() { - return m_dbConnection.database(); + QSqlDatabase database() const { + return m_database; } const CrateStorage& crates() const { return m_crates; } - TrackDAO& getTrackDAO(); - PlaylistDAO& getPlaylistDAO(); - DirectoryDAO& getDirectoryDAO(); + TrackDAO& getTrackDAO() { + return m_trackDao; + } + PlaylistDAO& getPlaylistDAO() { + return m_playlistDao; + } + DirectoryDAO& getDirectoryDAO() { + return m_directoryDao; + } AnalysisDao& getAnalysisDAO() { return m_analysisDao; } - QSharedPointer getTrackSource(); - void setTrackSource(QSharedPointer trackSource); - void cancelLibraryScan(); - UserSettingsPointer getConfig() { - return m_pConfig; + QSharedPointer getTrackSource() const { + return m_pTrackSource; } + void setTrackSource(QSharedPointer pTrackSource); + + void cancelLibraryScan(); void relocateDirectory(QString oldDir, QString newDir); @@ -89,9 +93,8 @@ class TrackCollection : public QObject { const QSet& crates); private: - UserSettingsPointer m_pConfig; - DbConnection m_dbConnection; - QSharedPointer m_defaultTrackSource; + QSqlDatabase m_database; + PlaylistDAO m_playlistDao; CrateStorage m_crates; CueDAO m_cueDao; @@ -99,6 +102,8 @@ class TrackCollection : public QObject { AnalysisDao m_analysisDao; LibraryHashDAO m_libraryHashDao; TrackDAO m_trackDao; + + QSharedPointer m_pTrackSource; }; #endif // TRACKCOLLECTION_H diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 553d807f56d2..106da892015f 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -11,7 +11,6 @@ #include "engine/enginedeck.h" #include "engine/enginemaster.h" #include "library/library.h" -#include "library/trackcollection.h" #include "mixer/auxiliary.h" #include "mixer/deck.h" #include "mixer/microphone.h" @@ -35,7 +34,7 @@ PlayerManager::PlayerManager(UserSettingsPointer pConfig, m_pEngine(pEngine), // NOTE(XXX) LegacySkinParser relies on these controls being Controls // and not ControlProxies. - m_pAnalyzerQueue(NULL), + m_pAnalyzerQueue(nullptr), m_pCONumDecks(new ControlObject( ConfigKey("[Master]", "num_decks"), true, true)), m_pCONumSamplers(new ControlObject( @@ -125,8 +124,7 @@ void PlayerManager::bindToLibrary(Library* pLibrary) { connect(this, SIGNAL(loadLocationToPlayer(QString, QString)), pLibrary, SLOT(slotLoadLocationToPlayer(QString, QString))); - m_pAnalyzerQueue = AnalyzerQueue::createDefaultAnalyzerQueue(m_pConfig, - pLibrary->getTrackCollection()); + m_pAnalyzerQueue = new AnalyzerQueue(pLibrary->dbConnectionPool(), m_pConfig); // Connect the player to the analyzer queue so that loaded tracks are // analysed. diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index 4922d661315e..54cc17708f4f 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -25,7 +25,6 @@ class PreviewDeck; class Sampler; class SamplerBank; class SoundManager; -class TrackCollection; // For mocking PlayerManager. class PlayerManagerInterface { diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index ad96ff555877..600929e0917e 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -1,9 +1,11 @@ #include "preferences/dialog/dlgprefwaveform.h" #include "mixxx.h" +#include "library/library.h" #include "preferences/waveformsettings.h" #include "waveform/waveformwidgetfactory.h" #include "waveform/renderers/waveformwidgetrenderer.h" +#include "util/db/dbconnectionpooled.h" DlgPrefWaveform::DlgPrefWaveform(QWidget* pParent, MixxxMainWindow* pMixxx, UserSettingsPointer pConfig, Library* pLibrary) @@ -217,27 +219,25 @@ void DlgPrefWaveform::slotWaveformMeasured(float frameRate, int droppedFrames) { } void DlgPrefWaveform::slotClearCachedWaveforms() { - TrackCollection* pTrackCollection = m_pLibrary->getTrackCollection(); - if (pTrackCollection != nullptr) { - AnalysisDao& analysisDao = pTrackCollection->getAnalysisDAO(); - analysisDao.deleteAnalysesByType(AnalysisDao::TYPE_WAVEFORM); - analysisDao.deleteAnalysesByType(AnalysisDao::TYPE_WAVESUMMARY); - calculateCachedWaveformDiskUsage(); - } + AnalysisDao analysisDao(m_pConfig); + const mixxx::DbConnectionPooled dbConnectionPooled(m_pLibrary->dbConnectionPool()); + QSqlDatabase dbConnection(dbConnectionPooled); + analysisDao.deleteAnalysesByType(dbConnection, AnalysisDao::TYPE_WAVEFORM); + analysisDao.deleteAnalysesByType(dbConnection, AnalysisDao::TYPE_WAVESUMMARY); + calculateCachedWaveformDiskUsage(); } void DlgPrefWaveform::calculateCachedWaveformDiskUsage() { - TrackCollection* pTrackCollection = m_pLibrary->getTrackCollection(); - if (pTrackCollection != nullptr) { - AnalysisDao& analysisDao = pTrackCollection->getAnalysisDAO(); - size_t waveformBytes = analysisDao.getDiskUsageInBytes(AnalysisDao::TYPE_WAVEFORM); - size_t wavesummaryBytes = analysisDao.getDiskUsageInBytes(AnalysisDao::TYPE_WAVESUMMARY); - - // Display total cached waveform size in mebibytes with 2 decimals. - QString sizeMebibytes = QString::number( - (waveformBytes + wavesummaryBytes) / (1024.0 * 1024.0), 'f', 2); - - waveformDiskUsage->setText( - tr("Cached waveforms occupy %1 MiB on disk.").arg(sizeMebibytes)); - } + AnalysisDao analysisDao(m_pConfig); + const mixxx::DbConnectionPooled dbConnectionPooled(m_pLibrary->dbConnectionPool()); + QSqlDatabase dbConnection(dbConnectionPooled); + size_t numBytes = analysisDao.getDiskUsageInBytes(dbConnection, AnalysisDao::TYPE_WAVEFORM) + + analysisDao.getDiskUsageInBytes(dbConnection, AnalysisDao::TYPE_WAVESUMMARY); + + // Display total cached waveform size in mebibytes with 2 decimals. + QString sizeMebibytes = QString::number( + numBytes / (1024.0 * 1024.0), 'f', 2); + + waveformDiskUsage->setText( + tr("Cached waveforms occupy %1 MiB on disk.").arg(sizeMebibytes)); } diff --git a/src/preferences/dialog/dlgprefwaveform.h b/src/preferences/dialog/dlgprefwaveform.h index 07927afed1c0..7e62809c51ea 100644 --- a/src/preferences/dialog/dlgprefwaveform.h +++ b/src/preferences/dialog/dlgprefwaveform.h @@ -3,12 +3,12 @@ #include -#include "library/library.h" #include "preferences/dialog/ui_dlgprefwaveformdlg.h" #include "preferences/usersettings.h" #include "preferences/dlgpreferencepage.h" class MixxxMainWindow; +class Library; class DlgPrefWaveform : public DlgPreferencePage, public Ui::DlgPrefWaveformDlg { Q_OBJECT diff --git a/src/preferences/upgrade.cpp b/src/preferences/upgrade.cpp index b5ff333a5caf..ddfa5a2fd151 100644 --- a/src/preferences/upgrade.cpp +++ b/src/preferences/upgrade.cpp @@ -24,6 +24,7 @@ #include #include "preferences/usersettings.h" +#include "database/mixxxdb.h" #include "controllers/defs_controllers.h" #include "defs_version.h" #include "library/library_preferences.h" @@ -31,6 +32,8 @@ #include "track/beat_preferences.h" #include "util/cmdlineargs.h" #include "util/math.h" +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" Upgrade::Upgrade() : m_bFirstRun(false), @@ -358,21 +361,35 @@ UserSettingsPointer Upgrade::versionUpgrade(const QString& settingsPath) { if (configVersion.startsWith("1.11")) { qDebug() << "Upgrading from v1.11.x..."; - - // upgrade to the multi library folder settings - QString currentFolder = config->getValueString(PREF_LEGACY_LIBRARY_DIR); - // to migrate the DB just add the current directory to the new - // directories table - TrackCollection tc(config); - DirectoryDAO directoryDAO = tc.getDirectoryDAO(); - - // NOTE(rryan): We don't have to ask for sandbox permission to this - // directory because the normal startup integrity check in Library will - // notice if we don't have permission and ask for access. Also, the - // Sandbox isn't setup yet at this point in startup because it relies on - // the config settings path and this function is what loads the config - // so it's not ready yet. - bool successful = directoryDAO.addDirectory(currentFolder); + bool successful = false; + { + MixxxDb mixxxDb(config); + const mixxx::DbConnectionPooler dbConnectionPooler( + mixxxDb.connectionPool()); + if (dbConnectionPooler) { + const mixxx::DbConnectionPooled dbConnectionPooled(mixxxDb.connectionPool()); + QSqlDatabase dbConnection(dbConnectionPooled); + DEBUG_ASSERT(dbConnection.isOpen()); + if (MixxxDb::initDatabaseSchema(dbConnection)) { + TrackCollection tc(config); + tc.connectDatabase(dbConnection); + + // upgrade to the multi library folder settings + QString currentFolder = config->getValueString(PREF_LEGACY_LIBRARY_DIR); + // to migrate the DB just add the current directory to the new + // directories table + // NOTE(rryan): We don't have to ask for sandbox permission to this + // directory because the normal startup integrity check in Library will + // notice if we don't have permission and ask for access. Also, the + // Sandbox isn't setup yet at this point in startup because it relies on + // the config settings path and this function is what loads the config + // so it's not ready yet. + successful = tc.getDirectoryDAO().addDirectory(currentFolder); + + tc.disconnectDatabase(); + } + } + } // ask for library rescan to activate cover art. We can later ask for // this variable when the library scanner is constructed. diff --git a/src/sources/soundsourcepluginlibrary.cpp b/src/sources/soundsourcepluginlibrary.cpp index f00639bfcdf8..7270a039c217 100644 --- a/src/sources/soundsourcepluginlibrary.cpp +++ b/src/sources/soundsourcepluginlibrary.cpp @@ -22,8 +22,8 @@ const Logger kLogger("SoundSourcePluginLibrary"); if (s_loadedPluginLibraries.contains(libFilePath)) { return s_loadedPluginLibraries.value(libFilePath); } else { - SoundSourcePluginLibraryPointer pPluginLibrary( - std::make_shared(libFilePath)); + auto pPluginLibrary = + std::make_shared(libFilePath); if (pPluginLibrary->init()) { s_loadedPluginLibraries.insert(libFilePath, pPluginLibrary); return pPluginLibrary; diff --git a/src/test/analyserwaveformtest.cpp b/src/test/analyserwaveformtest.cpp index f94391e2a158..ad619b842fa0 100644 --- a/src/test/analyserwaveformtest.cpp +++ b/src/test/analyserwaveformtest.cpp @@ -2,10 +2,12 @@ #include #include -#include "track/track.h" -#include "analyzer/analyzerwaveform.h" #include "test/mixxxtest.h" +#include "analyzer/analyzerwaveform.h" +#include "library/dao/analysisdao.h" +#include "track/track.h" + #define BIGBUF_SIZE (1024 * 1024) //Megabyte #define CANARY_SIZE (1024*4) #define MAGIC_FLOAT 1234.567890f @@ -15,8 +17,13 @@ namespace { class AnalyzerWaveformTest: public MixxxTest { protected: + AnalyzerWaveformTest() + : aw(config()), + bigbuf(nullptr), + canaryBigBuf(nullptr) { + } + void SetUp() override { - aw = new AnalyzerWaveform(config()); tio = Track::newTemporary(); tio->setSampleRate(44100); @@ -37,12 +44,12 @@ class AnalyzerWaveformTest: public MixxxTest { } void TearDown() override { - delete aw; delete [] bigbuf; delete [] canaryBigBuf; } - AnalyzerWaveform* aw; + protected: + AnalyzerWaveform aw; TrackPointer tio; CSAMPLE* bigbuf; CSAMPLE* canaryBigBuf; @@ -50,9 +57,9 @@ class AnalyzerWaveformTest: public MixxxTest { //Test to make sure we don't modify the source buffer. TEST_F(AnalyzerWaveformTest, simpleAnalyze) { - aw->initialize(tio, tio->getSampleRate(), BIGBUF_SIZE); - aw->process(bigbuf, BIGBUF_SIZE); - aw->finalize(tio); + aw.initialize(tio, tio->getSampleRate(), BIGBUF_SIZE); + aw.process(bigbuf, BIGBUF_SIZE); + aw.finalize(tio); for (int i = 0; i < BIGBUF_SIZE; i++) { EXPECT_FLOAT_EQ(bigbuf[i], MAGIC_FLOAT); } @@ -60,9 +67,9 @@ TEST_F(AnalyzerWaveformTest, simpleAnalyze) { //Basic test to make sure we don't step out of bounds. TEST_F(AnalyzerWaveformTest, canary) { - aw->initialize(tio, tio->getSampleRate(), BIGBUF_SIZE); - aw->process(&canaryBigBuf[CANARY_SIZE], BIGBUF_SIZE); - aw->finalize(tio); + aw.initialize(tio, tio->getSampleRate(), BIGBUF_SIZE); + aw.process(&canaryBigBuf[CANARY_SIZE], BIGBUF_SIZE); + aw.finalize(tio); for (int i = 0; i < CANARY_SIZE; i++) { EXPECT_FLOAT_EQ(canaryBigBuf[i], CANARY_FLOAT); } @@ -75,14 +82,14 @@ TEST_F(AnalyzerWaveformTest, canary) { //initialize(..) and process(..) is told to process more samples than that, //that we don't step out of bounds. TEST_F(AnalyzerWaveformTest, wrongTotalSamples) { - aw->initialize(tio, tio->getSampleRate(), BIGBUF_SIZE/2); + aw.initialize(tio, tio->getSampleRate(), BIGBUF_SIZE/2); // Deliver double the expected samples int wrongTotalSamples = BIGBUF_SIZE; int blockSize = 2*32768; for (int i = CANARY_SIZE; i < CANARY_SIZE+wrongTotalSamples; i += blockSize) { - aw->process(&canaryBigBuf[i], blockSize); + aw.process(&canaryBigBuf[i], blockSize); } - aw->finalize(tio); + aw.finalize(tio); //Ensure the source buffer is intact for (int i = CANARY_SIZE; i < BIGBUF_SIZE; i++) { EXPECT_FLOAT_EQ(canaryBigBuf[i], MAGIC_FLOAT); diff --git a/src/test/coverartutils_test.cpp b/src/test/coverartutils_test.cpp index f110773fdc3e..42b944ffdd75 100644 --- a/src/test/coverartutils_test.cpp +++ b/src/test/coverartutils_test.cpp @@ -39,7 +39,7 @@ class CoverArtUtilTest : public LibraryTest, public CoverArtCache { void TearDown() override { // make sure we clean up the db - QSqlQuery query(collection()->database()); + QSqlQuery query(dbConnection()); query.prepare("DELETE FROM " % DIRECTORYDAO_TABLE); ASSERT_TRUE(query.exec()); query.prepare("DELETE FROM library"); diff --git a/src/test/directorydaotest.cpp b/src/test/directorydaotest.cpp index 4d55912380fc..bbcc62cabe80 100644 --- a/src/test/directorydaotest.cpp +++ b/src/test/directorydaotest.cpp @@ -28,7 +28,7 @@ class DirectoryDAOTest : public LibraryTest { void TearDown() override { // make sure we clean up the db - QSqlQuery query(collection()->database()); + QSqlQuery query(dbConnection()); query.prepare("DELETE FROM " % DIRECTORYDAO_TABLE); query.exec(); query.prepare("DELETE FROM library"); @@ -73,7 +73,7 @@ TEST_F(DirectoryDAOTest, addDirTest) { success = m_DirectoryDao.addDirectory(testParent); EXPECT_EQ(ALL_FINE, success); - QSqlQuery query(collection()->database()); + QSqlQuery query(dbConnection()); query.prepare("SELECT " % DIRECTORYDAO_DIR % " FROM " % DIRECTORYDAO_TABLE); success = query.exec(); @@ -99,7 +99,7 @@ TEST_F(DirectoryDAOTest, removeDirTest) { EXPECT_EQ(ALL_FINE, success); // we do not trust what directory dao thinks and better check up on it - QSqlQuery query(collection()->database()); + QSqlQuery query(dbConnection()); query.prepare("SELECT " % DIRECTORYDAO_DIR % " FROM " % DIRECTORYDAO_TABLE); success = query.exec(); QStringList dirs; diff --git a/src/test/libraryscannertest.cpp b/src/test/libraryscannertest.cpp index 61d15c354641..93063b1b9494 100644 --- a/src/test/libraryscannertest.cpp +++ b/src/test/libraryscannertest.cpp @@ -1,55 +1,54 @@ -#include #include #include -#include "mixxxtest.h" +#include "test/librarytest.h" + #include "library/scanner/libraryscanner.h" -#include "util/memory.h" -class LibraryScannerTest : public MixxxTest { +class LibraryScannerTest : public LibraryTest { protected: - void SetUp() override { - m_pLibraryScanner = std::make_unique(nullptr, config()); + LibraryScannerTest() + : m_libraryScanner(dbConnectionPool(), collection(), config()) { } - std::unique_ptr m_pLibraryScanner; + LibraryScanner m_libraryScanner; }; TEST_F(LibraryScannerTest, ScannerRoundtrip) { // Normal flow: - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); - m_pLibraryScanner->changeScannerState(LibraryScanner::STARTING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::STARTING); - m_pLibraryScanner->changeScannerState(LibraryScanner::SCANNING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::SCANNING); - m_pLibraryScanner->changeScannerState(LibraryScanner::FINISHED); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); + m_libraryScanner.changeScannerState(LibraryScanner::STARTING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::STARTING); + m_libraryScanner.changeScannerState(LibraryScanner::SCANNING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::SCANNING); + m_libraryScanner.changeScannerState(LibraryScanner::FINISHED); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); // No Tracks: - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); - m_pLibraryScanner->changeScannerState(LibraryScanner::STARTING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::STARTING); - m_pLibraryScanner->changeScannerState(LibraryScanner::IDLE); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); + m_libraryScanner.changeScannerState(LibraryScanner::STARTING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::STARTING); + m_libraryScanner.changeScannerState(LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); // Cancel during scaning: - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); - m_pLibraryScanner->changeScannerState(LibraryScanner::STARTING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::STARTING); - m_pLibraryScanner->changeScannerState(LibraryScanner::SCANNING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::SCANNING); - m_pLibraryScanner->changeScannerState(LibraryScanner::CANCELING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::CANCELING); - m_pLibraryScanner->changeScannerState(LibraryScanner::FINISHED); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); - m_pLibraryScanner->changeScannerState(LibraryScanner::IDLE); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); + m_libraryScanner.changeScannerState(LibraryScanner::STARTING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::STARTING); + m_libraryScanner.changeScannerState(LibraryScanner::SCANNING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::SCANNING); + m_libraryScanner.changeScannerState(LibraryScanner::CANCELING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::CANCELING); + m_libraryScanner.changeScannerState(LibraryScanner::FINISHED); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); + m_libraryScanner.changeScannerState(LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); // restart during canceling : - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); - m_pLibraryScanner->changeScannerState(LibraryScanner::CANCELING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::CANCELING); - m_pLibraryScanner->changeScannerState(LibraryScanner::STARTING); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::CANCELING); - m_pLibraryScanner->changeScannerState(LibraryScanner::IDLE); - EXPECT_EQ(m_pLibraryScanner->m_state, LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); + m_libraryScanner.changeScannerState(LibraryScanner::CANCELING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::CANCELING); + m_libraryScanner.changeScannerState(LibraryScanner::STARTING); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::CANCELING); + m_libraryScanner.changeScannerState(LibraryScanner::IDLE); + EXPECT_EQ(m_libraryScanner.m_state, LibraryScanner::IDLE); } diff --git a/src/test/librarytest.h b/src/test/librarytest.h index 804a4252161f..011957134ceb 100644 --- a/src/test/librarytest.h +++ b/src/test/librarytest.h @@ -3,24 +3,44 @@ #include "test/mixxxtest.h" +#include "database/mixxxdb.h" #include "library/trackcollection.h" -#include "util/memory.h" +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" class LibraryTest : public MixxxTest { protected: - LibraryTest() { - m_pTrackCollection = std::make_unique(config()); + LibraryTest() + : m_mixxxDb(config()), + m_dbConnectionPooler(m_mixxxDb.connectionPool()), + m_dbConnectionPooled(m_mixxxDb.connectionPool()), + m_trackCollection(config()) { + QSqlDatabase dbConnection(m_dbConnectionPooled); + MixxxDb::initDatabaseSchema(dbConnection); + m_trackCollection.connectDatabase(dbConnection); } ~LibraryTest() override { + m_trackCollection.disconnectDatabase(); } - TrackCollection* collection() const { - return m_pTrackCollection.get(); + mixxx::DbConnectionPoolPtr dbConnectionPool() const { + return m_mixxxDb.connectionPool(); + } + + QSqlDatabase dbConnection() const { + return static_cast(m_dbConnectionPooled); + } + + TrackCollection* collection() { + return &m_trackCollection; } private: - std::unique_ptr m_pTrackCollection; + const MixxxDb m_mixxxDb; + const mixxx::DbConnectionPooler m_dbConnectionPooler; + const mixxx::DbConnectionPooled m_dbConnectionPooled; + TrackCollection m_trackCollection; }; diff --git a/src/test/queryutiltest.cpp b/src/test/queryutiltest.cpp index 9f11bcb3f40f..94c7e01ebdc2 100644 --- a/src/test/queryutiltest.cpp +++ b/src/test/queryutiltest.cpp @@ -1,46 +1,44 @@ #include -#include -#include -#include + +#include "test/mixxxtest.h" + +#include "database/mixxxdb.h" #include "library/queryutil.h" #include "util/db/sqllikewildcardescaper.h" - -class QueryUtilTest : public testing::Test {}; +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" + +class QueryUtilTest : public MixxxTest { + protected: + QueryUtilTest() + : m_mixxxDb(config()), + m_dbConnectionPooler(m_mixxxDb.connectionPool()), + m_dbConnectionPooled(m_mixxxDb.connectionPool()), + m_dbConnection(m_dbConnectionPooled) { + // This test only needs a connection to an empty database + // without any particular schema. No need to initialize the + // database schema. + } + + private: + const MixxxDb m_mixxxDb; + const mixxx::DbConnectionPooler m_dbConnectionPooler; + const mixxx::DbConnectionPooled m_dbConnectionPooled; + + protected: + QSqlDatabase m_dbConnection; +}; TEST_F(QueryUtilTest, FieldEscaperEscapesQuotes) { - QTemporaryFile databaseFile("mixxxdb.sqlite"); - ASSERT_TRUE(databaseFile.open()); - - QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); - db.setHostName("localhost"); - db.setUserName("mixxx"); - db.setPassword("mixxx"); - qDebug() << "Temp file is" << databaseFile.fileName(); - db.setDatabaseName(databaseFile.fileName()); - ASSERT_TRUE(db.open()); - FieldEscaper f(db); - - + FieldEscaper fieldEscaper(m_dbConnection); EXPECT_STREQ(qPrintable(QString("'foobar'")), - qPrintable(f.escapeString("foobar"))); + qPrintable(fieldEscaper.escapeString("foobar"))); EXPECT_STREQ(qPrintable(QString("'foobar''s'")), - qPrintable(f.escapeString("foobar's"))); + qPrintable(fieldEscaper.escapeString("foobar's"))); } -TEST_F(QueryUtilTest, FieldEscaperEscapesForLike) { - QTemporaryFile databaseFile("mixxxdb.sqlite"); - ASSERT_TRUE(databaseFile.open()); - - QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); - db.setHostName("localhost"); - db.setUserName("mixxx"); - db.setPassword("mixxx"); - qDebug() << "Temp file is" << databaseFile.fileName(); - db.setDatabaseName(databaseFile.fileName()); - ASSERT_TRUE(db.open()); - FieldEscaper f(db); - +TEST_F(QueryUtilTest, SqlLikeWildcardEscaperEscapesForLike) { EXPECT_STREQ(qPrintable(QString("xx44xx4%yy4_yy")), qPrintable(SqlLikeWildcardEscaper::apply("xx4xx%yy_yy", '4'))); } diff --git a/src/test/schemamanager_test.cpp b/src/test/schemamanager_test.cpp index 6322e5b6d03e..b9523516d7b9 100644 --- a/src/test/schemamanager_test.cpp +++ b/src/test/schemamanager_test.cpp @@ -1,99 +1,132 @@ #include -#include -#include -#include - #include "test/mixxxtest.h" -#include "library/trackcollection.h" -#include "library/schemamanager.h" + +#include "database/mixxxdb.h" +#include "database/schemamanager.h" +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" + #include "library/dao/settingsdao.h" + #include "util/assert.h" + class SchemaManagerTest : public MixxxTest { protected: SchemaManagerTest() - : m_dbFile("mixxxdb.sqlite") { - RELEASE_ASSERT(m_dbFile.open()); - m_db = QSqlDatabase::addDatabase("QSQLITE"); - m_db.setHostName("localhost"); - m_db.setUserName("mixxx"); - m_db.setPassword("mixxx"); - m_db.setDatabaseName(m_dbFile.fileName()); - RELEASE_ASSERT(m_db.open()); + : m_mixxxDb(config()), + m_dbConnectionPooler(m_mixxxDb.connectionPool()), + m_dbConnectionPooled(m_mixxxDb.connectionPool()), + m_dbConnection(m_dbConnectionPooled) { + } + + QSqlDatabase dbConnection() const { + return static_cast(m_dbConnectionPooled); } - QTemporaryFile m_dbFile; - QSqlDatabase m_db; + private: + const MixxxDb m_mixxxDb; + const mixxx::DbConnectionPooler m_dbConnectionPooler; + const mixxx::DbConnectionPooled m_dbConnectionPooled; + + protected: + QSqlDatabase m_dbConnection; }; TEST_F(SchemaManagerTest, CanUpgradeFreshDatabaseToRequiredVersion) { - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, TrackCollection::kRequiredSchemaVersion); - EXPECT_EQ(SchemaManager::RESULT_OK, result); + // Initial upgrade + { + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::UpgradeSucceeded, result); + } + // Subsequent upgrade(s) + { + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::CurrentVersion, result); + } } TEST_F(SchemaManagerTest, NonExistentSchema) { - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - ":file_doesnt_exist.xml", m_db, - TrackCollection::kRequiredSchemaVersion); - EXPECT_EQ(SchemaManager::RESULT_SCHEMA_ERROR, result); + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + ":file_doesnt_exist.xml", MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::SchemaError, result); } TEST_F(SchemaManagerTest, BackwardsCompatibleVersion) { - // Upgrade to version 1 to get the settings table. - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, 1); - EXPECT_EQ(SchemaManager::RESULT_OK, result); - - SettingsDAO settings(m_db); - settings.initialize(); - - // Pretend the database version is one past the required version but - // min_compatible is the required version. - settings.setValue(SchemaManager::SETTINGS_VERSION_STRING, - TrackCollection::kRequiredSchemaVersion + 1); - settings.setValue(SchemaManager::SETTINGS_MINCOMPATIBLE_STRING, - TrackCollection::kRequiredSchemaVersion); - - result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, TrackCollection::kRequiredSchemaVersion); - EXPECT_EQ(SchemaManager::RESULT_OK, result); + // Establish preconditions for test + { + // Upgrade to version 1 to get the settings table. + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, 1); + EXPECT_EQ(SchemaManager::Result::UpgradeSucceeded, result); + + SettingsDAO settings(dbConnection()); + settings.initialize(); + + // Pretend the database version is one past the required version but + // min_compatible is the required version. + settings.setValue(SchemaManager::SETTINGS_VERSION_STRING, + MixxxDb::kRequiredSchemaVersion + 1); + settings.setValue(SchemaManager::SETTINGS_MINCOMPATIBLE_STRING, + MixxxDb::kRequiredSchemaVersion); + } + + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::NewerVersionBackwardsCompatible, result); } TEST_F(SchemaManagerTest, BackwardsIncompatibleVersion) { - // Upgrade to version 1 to get the settings table. - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, 1); - EXPECT_EQ(SchemaManager::RESULT_OK, result); - - SettingsDAO settings(m_db); - settings.initialize(); - - // Pretend the database version is one past the required version and - // min_compatible is one past the required version. - settings.setValue(SchemaManager::SETTINGS_VERSION_STRING, - TrackCollection::kRequiredSchemaVersion + 1); - settings.setValue(SchemaManager::SETTINGS_MINCOMPATIBLE_STRING, - TrackCollection::kRequiredSchemaVersion + 1); - - result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, TrackCollection::kRequiredSchemaVersion); - EXPECT_EQ(SchemaManager::RESULT_BACKWARDS_INCOMPATIBLE, result); + // Establish preconditions for test + { + // Upgrade to version 1 to get the settings table. + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, 1); + EXPECT_EQ(SchemaManager::Result::UpgradeSucceeded, result); + + SettingsDAO settings(dbConnection()); + settings.initialize(); + + // Pretend the database version is one past the required version and + // min_compatible is one past the required version. + settings.setValue(SchemaManager::SETTINGS_VERSION_STRING, + MixxxDb::kRequiredSchemaVersion + 1); + settings.setValue(SchemaManager::SETTINGS_MINCOMPATIBLE_STRING, + MixxxDb::kRequiredSchemaVersion + 1); + } + + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::NewerVersionIncompatible, result); } TEST_F(SchemaManagerTest, FailedUpgrade) { - // Upgrade to version 3 to get the modern library table. - SchemaManager::Result result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, 3); - EXPECT_EQ(SchemaManager::RESULT_OK, result); + // Establish preconditions for test + { + // Upgrade to version 3 to get the modern library table. + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, 3); + EXPECT_EQ(SchemaManager::Result::UpgradeSucceeded, result); + } // Add a column that is added in verison 24. - QSqlQuery query(m_db); + QSqlQuery query(dbConnection()); EXPECT_TRUE(query.exec( "ALTER TABLE library ADD COLUMN coverart_source TEXT")); - result = SchemaManager::upgradeToSchemaVersion( - ":/schema.xml", m_db, TrackCollection::kRequiredSchemaVersion); - EXPECT_EQ(SchemaManager::RESULT_UPGRADE_FAILED, result); + SchemaManager schemaManager(dbConnection()); + SchemaManager::Result result = schemaManager.upgradeToSchemaVersion( + MixxxDb::kDefaultSchemaFile, MixxxDb::kRequiredSchemaVersion); + EXPECT_EQ(SchemaManager::Result::UpgradeFailed, result); } diff --git a/src/test/sqliteliketest.cpp b/src/test/sqliteliketest.cpp index 674100605582..13f96c0f68ec 100644 --- a/src/test/sqliteliketest.cpp +++ b/src/test/sqliteliketest.cpp @@ -12,40 +12,40 @@ TEST_F(SqliteLikeTest, PatternTest) { pattern = QString::fromUtf8("%väth%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%vath%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%väth%"); string = QString::fromUtf8("Sven Vath"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v_th%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v_th%%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v%_%th%%"); string = QString::fromUtf8("Sven Väth"); esc = '\0'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%v!%th%"); string = QString::fromUtf8("Sven V%th"); esc = '!'; - EXPECT_TRUE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_TRUE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); pattern = QString::fromUtf8("%ä%"); string = QString::fromUtf8("Tiësto"); esc = '\0'; - EXPECT_FALSE(DbConnection::likeCompareLatinLow(&pattern, &string, esc)); + EXPECT_FALSE(mixxx::DbConnection::likeCompareLatinLow(&pattern, &string, esc)); } diff --git a/src/util/container.h b/src/util/container.h deleted file mode 100644 index 662d98beeb03..000000000000 --- a/src/util/container.h +++ /dev/null @@ -1,23 +0,0 @@ -// Misc. container helper functions. - -#ifndef CONTAINER_H -#define CONTAINER_H - -#include - -// Helper function to delete the values of a QMap if they are pointers. -template -void deleteMapValues(QMap* pMap) { - if (pMap == NULL) { - return; - } - QMutableMapIterator it(*pMap); - while (it.hasNext()) { - it.next(); - V value = it.value(); - it.remove(); - delete value; - } -} - -#endif /* CONTAINER_H */ diff --git a/src/util/db/dbconnection.cpp b/src/util/db/dbconnection.cpp index 295c10d8f0e4..89fac3e87b1c 100644 --- a/src/util/db/dbconnection.cpp +++ b/src/util/db/dbconnection.cpp @@ -1,6 +1,3 @@ -#include "util/db/dbconnection.h" - -#include #include #include @@ -8,23 +5,57 @@ #include #endif // __SQLITE3__ -#include "util/db/sqllikewildcards.h" +#include "util/db/dbconnection.h" -#include "util/string.h" +#include "util/db/sqllikewildcards.h" +#include "util/memory.h" +#include "util/logger.h" #include "util/assert.h" // Originally from public domain code: // http://www.archivum.info/qt-interest@trolltech.com/2008-12/00584/Re-%28Qt-interest%29-Qt-Sqlite-UserDefinedFunction.html +namespace mixxx { + namespace { -const QString kDatabaseType = "QSQLITE"; +const mixxx::Logger kLogger("DbConnection"); -const QString kDatabaseHostName = "localhost"; -const QString kDatabaseFileName = "mixxxdb.sqlite"; -const QString kDatabaseUserName = "mixxx"; -const QString kDatabasePassword = "mixxx"; +QSqlDatabase createDatabase( + const DbConnection::Params& params, + const QString connectionName) { + kLogger.debug() + << "Available drivers for database connections:" + << QSqlDatabase::drivers(); + + QSqlDatabase database = + QSqlDatabase::addDatabase(params.type, connectionName); + database.setHostName(params.hostName); + database.setDatabaseName(params.filePath); + database.setUserName(params.userName); + database.setPassword(params.password); + return database; +} + +QSqlDatabase cloneDatabase( + const QSqlDatabase& database, + const QString connectionName) { + DEBUG_ASSERT(!database.isOpen()); + return QSqlDatabase::cloneDatabase(database, connectionName); +} + +void removeDatabase( + const QSqlDatabase& database) { + DEBUG_ASSERT(!database.isOpen()); + QSqlDatabase::removeDatabase(database.connectionName()); +} + +// The default comparison of strings for sorting. +inline int compareLocaleAwareCaseInsensitive( + const QString& first, const QString& second) { + return QString::localeAwareCompare(first.toLower(), second.toLower()); +} void makeLatinLow(QChar* c, int count) { for (int i = 0; i < count; ++i) { @@ -140,7 +171,7 @@ int sqliteStringCompareUTF16(void* pArg, return compareLocaleAwareCaseInsensitive(string1, string2); } -const char* const kLexicographicalCollationFunc = "mixxxLexicographicalCollation"; +const char* const kLexicographicalCollationFunc = "mixxxLexicographicalCollationFunc"; // This implements the like() SQL function. This is used by the LIKE operator. // The SQL statement 'A LIKE B' is implemented as 'like(B, A)', and if there is @@ -184,110 +215,126 @@ void sqliteLike(sqlite3_context *context, #endif // __SQLITE3__ -} // anonymous namespace - -DbConnection::DbConnection(const QString& dirPath) - : m_filePath(QDir(dirPath).filePath(kDatabaseFileName)), - m_database(QSqlDatabase::addDatabase(kDatabaseType)) { - qDebug() - << "Available drivers for database connection:" - << QSqlDatabase::drivers(); +bool initDatabase(QSqlDatabase database) { + DEBUG_ASSERT(database.isOpen()); +#ifdef __SQLITE3__ + QVariant v = database.driver()->handle(); + VERIFY_OR_DEBUG_ASSERT(v.isValid()) { + kLogger.debug() << "Driver handle is invalid"; + return false; // abort + } + if (strcmp(v.typeName(), "sqlite3*") != 0) { + kLogger.warning() + << "Unsupported database driver:" + << v.typeName(); + return false; // abort + } + // v.data() returns a pointer to the handle + sqlite3* handle = *static_cast(v.data()); + VERIFY_OR_DEBUG_ASSERT(handle != nullptr) { + kLogger.warning() + << "SQLite3 handle is invalid"; + return false; // abort + } - m_database.setHostName(kDatabaseHostName); - m_database.setDatabaseName(m_filePath); - m_database.setUserName(kDatabaseUserName); - m_database.setPassword(kDatabasePassword); - if (!m_database.open()) { - qWarning() << "Failed to open database connection:" - << *this - << m_database.lastError(); - DEBUG_ASSERT(!*this); // failure - return; // early exit + int result = sqlite3_create_collation( + handle, + kLexicographicalCollationFunc, + SQLITE_UTF16, + nullptr, + sqliteStringCompareUTF16); + VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { + kLogger.warning() + << "Failed to install locale-aware lexicographical collation function for SQLite3:" + << result; } - QVariant v = m_database.driver()->handle(); - VERIFY_OR_DEBUG_ASSERT(v.isValid()) { - return; // early exit + result = sqlite3_create_function( + handle, + "like", + 2, + SQLITE_ANY, + nullptr, + sqliteLike, + nullptr, nullptr); + VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { + kLogger.warning() + << "Failed to install custom 2-arg LIKE function for SQLite3:" + << result; } -#ifdef __SQLITE3__ - if (strcmp(v.typeName(), "sqlite3*") == 0) { - // v.data() returns a pointer to the handle - sqlite3* handle = *static_cast(v.data()); - VERIFY_OR_DEBUG_ASSERT(handle != nullptr) { - qWarning() << "Could not get sqlite3 handle"; - m_database.close(); - DEBUG_ASSERT(!*this); // failure - return; // early exit - } - int result = sqlite3_create_collation( - handle, - kLexicographicalCollationFunc, - SQLITE_UTF16, - nullptr, - sqliteStringCompareUTF16); - VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { - qWarning() << "Failed to install lexicographical collation function:" << result; - } + result = sqlite3_create_function( + handle, + "like", + 3, + SQLITE_UTF8, // No conversion, Data is stored as UTF8 + nullptr, + sqliteLike, + nullptr, nullptr); + VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { + kLogger.warning() + << "Failed to install custom 3-arg LIKE function for SQLite3:" + << result; + } +#endif // __SQLITE3__ + return true; +} - result = sqlite3_create_function( - handle, - "like", - 2, - SQLITE_ANY, - nullptr, - sqliteLike, - nullptr, nullptr); - VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { - qWarning() << "Failed to install like 2 function:" << result; - } +} // anonymous namespace - result = sqlite3_create_function( - handle, - "like", - 3, - SQLITE_UTF8, // No conversion, Data is stored as UTF8 - nullptr, - sqliteLike, - nullptr, nullptr); - VERIFY_OR_DEBUG_ASSERT(result == SQLITE_OK) { - qWarning() << "Failed to install like 3 function:" << result; - } +DbConnection::DbConnection( + const Params& params, + const QString& connectionName) + : m_sqlDatabase(createDatabase(params, connectionName)) { +} - DEBUG_ASSERT(*this); // success - return; // early exit - } else { - qWarning() << "localecompare requires a SQLite3 database driver, found:" - << v.typeName(); - } -#endif // __SQLITE3__ +DbConnection::DbConnection( + const DbConnection& prototype, + const QString& connectionName) + : m_sqlDatabase(cloneDatabase(prototype.m_sqlDatabase, connectionName)) { } DbConnection::~DbConnection() { - VERIFY_OR_DEBUG_ASSERT(*this) { - qWarning() - << "Database connection has already been closed:" + close(); + removeDatabase(m_sqlDatabase); +} + +bool DbConnection::open() { + kLogger.debug() + << "Opening database connection" << *this; - return; // early exit + if (!m_sqlDatabase.open()) { + kLogger.warning() + << "Failed to open database connection" + << *this + << m_sqlDatabase.lastError(); + return false; // abort } - // There should never be an outstanding transaction when this code is - // called. If there is, it means we probably aren't committing a - // transaction somewhere that should be. - VERIFY_OR_DEBUG_ASSERT(!m_database.rollback()) { - qWarning() - << "Rolled back open transaction before closing database connection:" - << *this; + if (!initDatabase(m_sqlDatabase)) { + kLogger.warning() + << "Failed to initialize database connection" + << *this; + m_sqlDatabase.close(); + return false; // abort } - DEBUG_ASSERT(*this); - qDebug() - << "Closing database connection:" - << *this; - m_database.close(); - DEBUG_ASSERT(!*this); + return true; } -QDebug operator<<(QDebug debug, const DbConnection& dbConnection) { - return debug << kDatabaseType << dbConnection.m_filePath; +void DbConnection::close() { + if (m_sqlDatabase.isOpen()) { + // There should never be an outstanding transaction when this code is + // called. If there is, it means we probably aren't committing a + // transaction somewhere that should be. + VERIFY_OR_DEBUG_ASSERT(!m_sqlDatabase.rollback()) { + kLogger.warning() + << "Rolled back open transaction before closing database connection:" + << *this; + } + kLogger.debug() + << "Closing database connection:" + << *this; + m_sqlDatabase.close(); + } } //static @@ -311,3 +358,11 @@ int DbConnection::likeCompareLatinLow( string->data(), string->length(), esc); } + +QDebug operator<<(QDebug debug, const DbConnection& connection) { + return debug + << connection.name() + << connection.m_sqlDatabase; +} + +} // namespace mixxx diff --git a/src/util/db/dbconnection.h b/src/util/db/dbconnection.h index ee320a460a48..b0f080533cf1 100644 --- a/src/util/db/dbconnection.h +++ b/src/util/db/dbconnection.h @@ -3,23 +3,14 @@ #include + #include +namespace mixxx { + class DbConnection final { public: - explicit DbConnection( - const QString& dirPath); - ~DbConnection(); - - operator bool() const { - return m_database.isOpen(); - } - - QSqlDatabase& database() { - return m_database; - } - // Order string fields lexicographically with a // custom collation function if available (SQLite3). // Otherwise the query is returned unmodified. @@ -31,14 +22,48 @@ class DbConnection final { QString* string, QChar esc); - friend QDebug operator<<( - QDebug debug, - const DbConnection& dbConnection); + struct Params { + QString type; + QString hostName; + QString filePath; + QString userName; + QString password; + }; + + // All constructors are reserved for DbConnectionPool!! + DbConnection( + const Params& params, + const QString& connectionName); + DbConnection( + const DbConnection& prototype, + const QString& connectionName); + ~DbConnection(); + + QString name() const { + return m_sqlDatabase.connectionName(); + } + + bool open(); + void close(); + + bool isOpen() const { + return m_sqlDatabase.isOpen(); + } + + operator QSqlDatabase() const { + return m_sqlDatabase; + } + + friend QDebug operator<<(QDebug debug, const DbConnection& connection); private: - QString m_filePath; - QSqlDatabase m_database; + DbConnection(const DbConnection&) = delete; + DbConnection(const DbConnection&&) = delete; + + QSqlDatabase m_sqlDatabase; }; +} // namespace mixxx + #endif // MIXXX_DBCONNECTION_H diff --git a/src/util/db/dbconnectionpool.cpp b/src/util/db/dbconnectionpool.cpp new file mode 100644 index 000000000000..8fd07d8f2afb --- /dev/null +++ b/src/util/db/dbconnectionpool.cpp @@ -0,0 +1,60 @@ +#include "util/db/dbconnectionpool.h" + +#include "util/logger.h" + + +namespace mixxx { + +namespace { + +const Logger kLogger("DbConnectionPool"); + +} // anonymous namespace + +bool DbConnectionPool::createThreadLocalConnection() { + VERIFY_OR_DEBUG_ASSERT(!m_threadLocalConnections.hasLocalData()) { + DEBUG_ASSERT(m_threadLocalConnections.localData()); + kLogger.critical() + << "Thread-local database connection already exists" + << *m_threadLocalConnections.localData(); + return false; // abort + } + const int connectionIndex = + m_connectionCounter.fetchAndAddAcquire(1) + 1; + const QString indexedConnectionName = + QString("%1-%2").arg( + m_prototypeConnection.name(), + QString::number(connectionIndex)); + auto pConnection = std::make_unique(m_prototypeConnection, indexedConnectionName); + if (!pConnection->open()) { + kLogger.critical() + << "Failed to open thread-local database connection" + << *pConnection; + return false; // abort + } + m_threadLocalConnections.setLocalData(pConnection.get()); // transfer ownership + pConnection.release(); // release ownership + DEBUG_ASSERT(m_threadLocalConnections.hasLocalData()); + DEBUG_ASSERT(m_threadLocalConnections.localData()); + kLogger.info() + << "Cloned thread-local database connection" + << *m_threadLocalConnections.localData(); + return true; +} + +void DbConnectionPool::destroyThreadLocalConnection() { + VERIFY_OR_DEBUG_ASSERT(m_threadLocalConnections.hasLocalData()) { + kLogger.critical() + << "Thread-local database connection not found"; + } + m_threadLocalConnections.setLocalData(nullptr); +} + +DbConnectionPool::DbConnectionPool( + const DbConnection::Params& params, + const QString& connectionName) + : m_prototypeConnection(params, connectionName), + m_connectionCounter(0) { +} + +} // namespace mixxx diff --git a/src/util/db/dbconnectionpool.h b/src/util/db/dbconnectionpool.h new file mode 100644 index 000000000000..5ce30495075e --- /dev/null +++ b/src/util/db/dbconnectionpool.h @@ -0,0 +1,68 @@ +#ifndef MIXXX_DBCONNECTIONPOOL_H +#define MIXXX_DBCONNECTIONPOOL_H + + +#include +#include + +#include "util/db/dbconnection.h" +#include "util/memory.h" +#include "util/assert.h" + + +namespace mixxx { + +class DbConnectionPool; +typedef std::shared_ptr DbConnectionPoolPtr; + +class DbConnectionPool final { + public: + // Creates a new pool of database connections (one per thread) that + // all use the same connection parameters. Unique connection names + // will be generated based on the given connection name that serves + // as the base name (= common prefix). + static DbConnectionPoolPtr create( + const DbConnection::Params& params, + const QString& connectionName) { + return std::make_shared(params, connectionName); + } + + // NOTE(uklotzde): Should be private, but must be public for invocation + // from std::make_shared()! + DbConnectionPool( + const DbConnection::Params& params, + const QString& connectionName); + + private: + DbConnectionPool(const DbConnectionPool&) = delete; + DbConnectionPool(const DbConnectionPool&&) = delete; + + friend class DbConnectionPooler; + bool createThreadLocalConnection(); + void destroyThreadLocalConnection(); + + // Returns a database connection for the current thread, that has + // previously been created by instantiating DbConnectionPooler. The + // returned connection is only valid within the current thread! It + // will be closed and removed from the pool upon the destruction of + // the owning DbConnectionPooler or as a very last resort implicitly + // when the current thread terminates. Since all connections need + // to be created through DbConnectionPooler the latter case should + // never happen. + friend class DbConnectionPooled; + const DbConnection* threadLocalConnection() const { + return m_threadLocalConnections.localData(); + } + + const DbConnection m_prototypeConnection; + + QAtomicInt m_connectionCounter; + + QThreadStorage m_threadLocalConnections; + +}; + +} // namespace mixxx + + +#endif // MIXXX_DBCONNECTIONPOOL_H diff --git a/src/util/db/dbconnectionpooled.cpp b/src/util/db/dbconnectionpooled.cpp new file mode 100644 index 000000000000..cf74c4d2d5cc --- /dev/null +++ b/src/util/db/dbconnectionpooled.cpp @@ -0,0 +1,35 @@ +#include "util/db/dbconnectionpooled.h" + +#include "util/logger.h" + + +namespace mixxx { + +namespace { + +const Logger kLogger("DbConnectionPooled"); + +} // anonymous namespace + +DbConnectionPooled::operator QSqlDatabase() const { + VERIFY_OR_DEBUG_ASSERT(m_pDbConnectionPool) { + kLogger.critical() + << "No connection pool"; + return QSqlDatabase(); // abort + } + const DbConnection* pDbConnection = m_pDbConnectionPool->threadLocalConnection(); + // The return pointer is at least valid until leaving this + // function, because only the current thread is able to + // remove this connection from the pool. + VERIFY_OR_DEBUG_ASSERT(pDbConnection) { + kLogger.critical() + << "Thread-local database connection not found"; + return QSqlDatabase(); // abort + } + kLogger.debug() + << "Found thread-local database connection" + << *pDbConnection;; + return *pDbConnection; +} + +} // namespace mixxx diff --git a/src/util/db/dbconnectionpooled.h b/src/util/db/dbconnectionpooled.h new file mode 100644 index 000000000000..a1b2a3171641 --- /dev/null +++ b/src/util/db/dbconnectionpooled.h @@ -0,0 +1,42 @@ +#ifndef MIXXX_DBCONNECTIONPOOLED_H +#define MIXXX_DBCONNECTIONPOOLED_H + + +#include + +#include "util/db/dbconnectionpool.h" + + +namespace mixxx { + +// Dynamically provides thread-local database connections from +// the pool. +class DbConnectionPooled final { + public: + explicit DbConnectionPooled( + DbConnectionPoolPtr pDbConnectionPool = DbConnectionPoolPtr()) + : m_pDbConnectionPool(std::move(pDbConnectionPool)) { + } + + // Checks if this instance actually references a connection pool + // needed for obtaining thread-local database connections (see below). + explicit operator bool() const { + return static_cast(m_pDbConnectionPool); + } + + // Tries to obtain an existing thread-local database connection + // from the pool. This might fail if either the reference to the + // connection pool is missing or if the pool does not contain a + // thread-local connection for this thread (previously created + // by some DbConnectionPooler). On failure a non-functional default + // constructed database commection is returned. + explicit operator QSqlDatabase() const; + + private: + DbConnectionPoolPtr m_pDbConnectionPool; +}; + +} // namespace mixxx + + +#endif // MIXXX_DBCONNECTIONPOOLED_H diff --git a/src/util/db/dbconnectionpooler.cpp b/src/util/db/dbconnectionpooler.cpp new file mode 100644 index 000000000000..4eb35bb4678d --- /dev/null +++ b/src/util/db/dbconnectionpooler.cpp @@ -0,0 +1,31 @@ +#include "util/db/dbconnectionpooler.h" + +#include "util/logger.h" + + +namespace mixxx { + +namespace { + +const Logger kLogger("DbConnectionPooler"); + +} // anonymous namespace + +DbConnectionPooler::DbConnectionPooler( + DbConnectionPoolPtr pDbConnectionPool) { + if (pDbConnectionPool && pDbConnectionPool->createThreadLocalConnection()) { + // m_pDbConnectionPool indicates if the thread-local connection has actually + // been created during construction. Otherwise this instance is non-functional. + m_pDbConnectionPool = std::move(pDbConnectionPool); + } +} + +DbConnectionPooler::~DbConnectionPooler() { + if (m_pDbConnectionPool) { + // Only destroy the thread-local connection if it has actually been created + // during construction (see above). + m_pDbConnectionPool->destroyThreadLocalConnection(); + } +} + +} // namespace mixxx diff --git a/src/util/db/dbconnectionpooler.h b/src/util/db/dbconnectionpooler.h new file mode 100644 index 000000000000..b9cd76a1b4a0 --- /dev/null +++ b/src/util/db/dbconnectionpooler.h @@ -0,0 +1,50 @@ +#ifndef MIXXX_DBCONNECTIONPOOLER_H +#define MIXXX_DBCONNECTIONPOOLER_H + + +#include "util/db/dbconnectionpool.h" + + +namespace mixxx { + +// Manages the lifetime of a thread-local database connection that is +// shared through DbConnectionPool. It is created and added to the pool +// upon construction and will be closed and removed from the pool upon +// destruction. +// +// Ultimately upon termination of a thread the corresponding connection +// would also be closed and removed implicitly by the pool, but that +// should never happen! Therefore this class should always be allocated +// on the stack and not dynamically on the heap so that it cannot outlive +// the corresponding thread. +class DbConnectionPooler final { + public: + explicit DbConnectionPooler( + DbConnectionPoolPtr pDbConnectionPool = DbConnectionPoolPtr()); + DbConnectionPooler( + DbConnectionPooler&& other) = default; + ~DbConnectionPooler(); + + // Checks if a thread-local connection has actually been created + // during construction. Otherwise this instance does not store + // any reference to the connection pool and is non-functional. + explicit operator bool() const { + return static_cast(m_pDbConnectionPool); + } + + private: + DbConnectionPooler(const DbConnectionPooler&) = delete; + DbConnectionPooler& operator=(const DbConnectionPooler&) = delete; + DbConnectionPooler& operator=(DbConnectionPooler&&) = delete; + + // Prevent heap allocation + static void * operator new(std::size_t); + static void * operator new[](std::size_t); + + DbConnectionPoolPtr m_pDbConnectionPool; +}; + +} // namespace mixxx + + +#endif // MIXXX_DBCONNECTIONPOOLER_H diff --git a/src/util/db/fwdsqlquery.cpp b/src/util/db/fwdsqlquery.cpp index 74d4df8f0895..293dd8e18f42 100644 --- a/src/util/db/fwdsqlquery.cpp +++ b/src/util/db/fwdsqlquery.cpp @@ -2,8 +2,6 @@ #include -#include - #include "util/performancetimer.h" #include "util/logger.h" #include "util/assert.h" @@ -41,9 +39,10 @@ FwdSqlQuery::FwdSqlQuery( m_prepared(prepareQuery(*this, statement)) { if (!m_prepared) { DEBUG_ASSERT(!database.isOpen() || hasError()); - qCritical() << "Failed to prepare SQL query" - << "for [" << statement << "]" - << "on [" << database.connectionName() << "]:" + kLogger.critical() + << "Failed to prepare" + << statement + << ":" << lastError(); } } @@ -70,16 +69,11 @@ bool FwdSqlQuery::execPrepared() { DEBUG_ASSERT(size() < 0); return true; } else { - if (lastQuery() == executedQuery()) { - qCritical() << "Failed to execute prepared SQL query" - << "lastQuery [" << lastQuery() << "]:" - << lastError(); - } else { - qCritical() << "Failed to execute prepared SQL query" - << "lastQuery [" << lastQuery() << "]" - << "executedQuery [" << executedQuery() << "]:" - << lastError(); - } + kLogger.warning() + << "Failed to execute" + << lastQuery() + << ":" + << lastError(); DEBUG_ASSERT(hasError()); return false; } @@ -90,9 +84,9 @@ DbFieldIndex FwdSqlQuery::fieldIndex(const QString& fieldName) const { DEBUG_ASSERT(isSelect()); DbFieldIndex fieldIndex(record().indexOf(fieldName)); VERIFY_OR_DEBUG_ASSERT(fieldIndex.isValid()) { - qCritical() << "Field named" - << fieldName - << "not found in record of SQL query" + kLogger.critical() + << "Field named" << fieldName + << "not found in result from" << executedQuery(); } DEBUG_ASSERT(!hasError()); @@ -112,12 +106,16 @@ namespace { bool ok = false; int value = variant.toInt(&ok); VERIFY_OR_DEBUG_ASSERT(ok) { - qWarning() << "Invalid boolean value in database:" << variant; + kLogger.critical() + << "Invalid boolean value in database:" + << variant; } VERIFY_OR_DEBUG_ASSERT( (value == FwdSqlQuery::BOOLEAN_FALSE) || (value == FwdSqlQuery::BOOLEAN_TRUE)) { - qWarning() << "Invalid boolean value in database:" << value; + kLogger.critical() + << "Invalid boolean value in database:" + << value; } // C-style conversion from int to bool DEBUG_ASSERT(FwdSqlQuery::BOOLEAN_FALSE == 0); diff --git a/src/util/db/sqlstorage.h b/src/util/db/sqlstorage.h index 29eae3492665..fa42bf4ab20c 100644 --- a/src/util/db/sqlstorage.h +++ b/src/util/db/sqlstorage.h @@ -6,7 +6,8 @@ // Common base class/interface of all persistent storage based on an -// SQL database. +// SQL database. Instances of this class will always be accessed by +// the same thread. class SqlStorage { public: virtual ~SqlStorage() = default; @@ -28,14 +29,14 @@ class SqlStorage { // until it is detached (see below). Implementations must // store an implicitly shared copy of the QSqlDatabase for // accessing it. - virtual void attachDatabase(QSqlDatabase database) = 0; + virtual void connectDatabase(QSqlDatabase database) = 0; // Detach the currently attached database, e.g. before // closing it. // Implementations should perform the necessary cleanup // and discard all internally cached data that depends // on the database connection. - virtual void detachDatabase() = 0; + virtual void disconnectDatabase() = 0; protected: SqlStorage() = default; diff --git a/src/util/db/sqltransaction.cpp b/src/util/db/sqltransaction.cpp index 6bd1a9d8b6ed..463637743ca8 100644 --- a/src/util/db/sqltransaction.cpp +++ b/src/util/db/sqltransaction.cpp @@ -1,38 +1,45 @@ #include "util/db/sqltransaction.h" -#include - +#include "util/logger.h" #include "util/assert.h" namespace { - inline - bool beginTransaction(QSqlDatabase& database) { - if (!database.isOpen()) { - // Should only happen during tests - qWarning() << "Failed to begin SQL database transaction on" - << database.connectionName(); - return false; - } - if (database.transaction()) { - qDebug() << "Started new SQL database transaction on" + +const mixxx::Logger kLogger("SqlTransaction"); + +inline +bool beginTransaction(QSqlDatabase database) { + if (!database.isOpen()) { + // Should only happen during tests + kLogger.warning() + << "Failed to begin SQL database transaction on" + << database.connectionName(); + return false; + } + if (database.transaction()) { + kLogger.debug() + << "Started new SQL database transaction on" << database.connectionName(); - return true; - } else { - qWarning() << "Failed to begin SQL database transaction on" + return true; + } else { + kLogger.warning() + << "Failed to begin SQL database transaction on" << database.connectionName(); - return false; - } + return false; } +} } // anonymous namespace -SqlTransaction::SqlTransaction(QSqlDatabase database) +SqlTransaction::SqlTransaction( + const QSqlDatabase& database) : m_database(database), // implicitly shared (not copied) m_active(beginTransaction(m_database)) { } -SqlTransaction::SqlTransaction(SqlTransaction&& other) +SqlTransaction::SqlTransaction( + SqlTransaction&& other) : m_database(std::move(other.m_database)), // implicitly shared (not moved) m_active(other.m_active) { other.release(); @@ -51,16 +58,19 @@ void SqlTransaction::release() { bool SqlTransaction::commit() { DEBUG_ASSERT(m_active); if (!m_database.isOpen()) { - qWarning() << "Failed to commit transaction: No open SQL database connection"; + kLogger.warning() + << "Failed to commit transaction: No open SQL database connection"; return false; } if (m_database.commit()) { - qDebug() << "Committed SQL database transaction on" + kLogger.debug() + << "Committed SQL database transaction on" << m_database.connectionName(); release(); // commit/rollback only once return true; } else { - qWarning() << "Failed to commit SQL database transaction on" + kLogger.warning() + << "Failed to commit SQL database transaction on" << m_database.connectionName(); return false; } @@ -69,16 +79,19 @@ bool SqlTransaction::commit() { bool SqlTransaction::rollback() { DEBUG_ASSERT(m_active); if (!m_database.isOpen()) { - qWarning() << "Failed to rollback transaction: No open SQL database connection"; + kLogger.warning() + << "Failed to rollback transaction: No open SQL database connection"; return false; } if (m_database.rollback()) { - qDebug() << "Rolled back SQL database transaction on" + kLogger.debug() + << "Rolled back SQL database transaction on" << m_database.connectionName(); release(); // commit/rollback only once return true; } else { - qWarning() << "Failed to rollback SQL database transaction on" + kLogger.warning() + << "Failed to rollback SQL database transaction on" << m_database.connectionName(); return false; } diff --git a/src/util/db/sqltransaction.h b/src/util/db/sqltransaction.h index dc93a87478bf..8a172cea5a14 100644 --- a/src/util/db/sqltransaction.h +++ b/src/util/db/sqltransaction.h @@ -7,14 +7,11 @@ class SqlTransaction final { public: - explicit SqlTransaction(QSqlDatabase database); + explicit SqlTransaction( + const QSqlDatabase& database); SqlTransaction(SqlTransaction&& other); ~SqlTransaction(); - QSqlDatabase& database() { - return m_database; - } - operator bool() const { return m_active; }