diff --git a/src/library/library.cpp b/src/library/library.cpp index 6d4cca6d14e..2f0c808c00b 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -62,43 +62,27 @@ const ConfigKey Library::kConfigKeyRepairDatabaseOnNextRestart(kConfigGroup, "Re // The default row height of the library. const int Library::kDefaultRowHeightPx = 20; -Library::Library(UserSettingsPointer pConfig, - PlayerManagerInterface* pPlayerManager, - RecordingManager* pRecordingManager) : - m_pConfig(pConfig), - m_mixxxDb(pConfig), - m_dbConnectionPooler(m_mixxxDb.connectionPool()), - m_pTrackCollection(new TrackCollection(pConfig)), - m_pLibraryControl(new LibraryControl(this)), - m_pRecordingManager(pRecordingManager), - m_scanner(m_mixxxDb.connectionPool(), m_pTrackCollection, pConfig), - m_pSidebarExpanded(nullptr), - m_hoveredFeature(nullptr), - m_focusedFeature(nullptr), - m_focusedPaneId(-1), - m_preselectedPane(-1), - m_previewPreselectedPane(-1) { - 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); - } +Library::Library( + UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr pDbConnectionPool, + PlayerManagerInterface* pPlayerManager, + RecordingManager* pRecordingManager) + : m_pConfig(pConfig), + m_pDbConnectionPool(pDbConnectionPool), + m_pTrackCollection(new TrackCollection(m_pConfig)), + m_pMixxxLibraryFeature(nullptr), + m_pPlaylistFeature(nullptr), + m_pCrateFeature(nullptr), + m_pAnalysisFeature(nullptr), + m_pLibraryControl(new LibraryControl(this)), + m_scanner(m_pDbConnectionPool, m_pTrackCollection, m_pConfig), + m_pSidebarExpanded(nullptr), + m_hoveredFeature(nullptr), + m_focusedFeature(nullptr), + m_focusedPaneId(-1), + m_preselectedPane(-1), + m_previewPreselectedPane(-1) { + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); // TODO(XXX): Add a checkbox in the library preferences for checking // and repairing the database on the next restart of the application. @@ -125,7 +109,7 @@ Library::Library(UserSettingsPointer pConfig, this, SLOT(slotRefreshLibraryModels())); createTrackCache(); - createFeatures(pConfig, pPlayerManager); + createFeatures(pConfig, pPlayerManager, pRecordingManager); // On startup we need to check if all of the user's library folders are // accessible to us. If the user is using a database from <1.12.0 with @@ -748,8 +732,10 @@ void Library::createTrackCache() { -void Library::createFeatures(UserSettingsPointer pConfig, - PlayerManagerInterface* pPlayerManager) { +void Library::createFeatures( + UserSettingsPointer pConfig, + PlayerManagerInterface* pPlayerManager, + RecordingManager* pRecordingManager) { m_pMixxxLibraryFeature = new MixxxLibraryFeature( pConfig, this, this, m_pTrackCollection); addFeature(m_pMixxxLibraryFeature); @@ -766,7 +752,7 @@ void Library::createFeatures(UserSettingsPointer pConfig, addFeature(m_pCrateFeature); BrowseFeature* browseFeature = new BrowseFeature( - pConfig, this, this, m_pTrackCollection, m_pRecordingManager); + pConfig, this, this, m_pTrackCollection, pRecordingManager); connect(browseFeature, SIGNAL(scanLibrary()), &m_scanner, SLOT(scan())); connect(&m_scanner, SIGNAL(scanStarted()), @@ -776,7 +762,7 @@ void Library::createFeatures(UserSettingsPointer pConfig, addFeature(browseFeature); addFeature(new RecordingFeature( - pConfig, this, this, m_pTrackCollection, m_pRecordingManager)); + pConfig, this, this, m_pTrackCollection, pRecordingManager)); addFeature(new HistoryFeature(pConfig, this, this, m_pTrackCollection)); diff --git a/src/library/library.h b/src/library/library.h index 4896f0ca85b..da3eecf4ed9 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -19,8 +19,7 @@ #include "track/track.h" #include "util/parented_ptr.h" #include "util/memory.h" -#include "database/mixxxdb.h" -#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpool.h" class AnalysisFeature; class CrateFeature; @@ -28,14 +27,11 @@ class KeyboardEventFilter; class LibraryPaneManager; class LibraryControl; class LibraryFeature; -class LibraryTableModel; class LibrarySidebarExpandedManager; class LibraryView; class MixxxLibraryFeature; class PlaylistFeature; class PlayerManagerInterface; -class SidebarModel; -class TrackModel; class TrackCollection; class WBaseLibrary; class WLibraryPane; @@ -61,12 +57,13 @@ class Library : public QObject { static const ConfigKey kConfigKeyRepairDatabaseOnNextRestart; Library(UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr pDbConnectionPool, PlayerManagerInterface* pPlayerManager, RecordingManager* pRecordingManager); - virtual ~Library(); + ~Library() override; mixxx::DbConnectionPoolPtr dbConnectionPool() const { - return m_mixxxDb.connectionPool(); + return m_pDbConnectionPool; } void bindSearchBar(WSearchLineEdit* searchLine, int id); @@ -158,21 +155,18 @@ class Library : public QObject { LibraryPaneManager* getPreselectedPane(); void createTrackCache(); - void createFeatures(UserSettingsPointer pConfig, PlayerManagerInterface *pPlayerManager); + void createFeatures( + UserSettingsPointer pConfig, + PlayerManagerInterface *pPlayerManager, + RecordingManager* pRecordingManager); void handleFocus(); void handlePreselection(); 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; + // The Mixxx database connection pool + const mixxx::DbConnectionPoolPtr m_pDbConnectionPool; TrackCollection* m_pTrackCollection; MixxxLibraryFeature* m_pMixxxLibraryFeature; @@ -180,7 +174,6 @@ class Library : public QObject { CrateFeature* m_pCrateFeature; AnalysisFeature* m_pAnalysisFeature; LibraryControl* m_pLibraryControl; - RecordingManager* m_pRecordingManager; LibraryScanner m_scanner; QFont m_trackTableFont; int m_iTrackTableRowHeight; diff --git a/src/library/scanner/libraryscanner.cpp b/src/library/scanner/libraryscanner.cpp index aa4c9b4785c..3468aa6c376 100644 --- a/src/library/scanner/libraryscanner.cpp +++ b/src/library/scanner/libraryscanner.cpp @@ -96,15 +96,13 @@ void LibraryScanner::run() { Trace trace("LibraryScanner"); const mixxx::DbConnectionPooler dbConnectionPooler(m_pDbConnectionPool); - const mixxx::DbConnectionPooled dbConnectionPooled(m_pDbConnectionPool); - if (!dbConnectionPooled) { + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); + if (!dbConnection.isOpen()) { kLogger.warning() << "Failed to open database connection for library scanner"; kLogger.debug() << "Exiting thread"; return; } - QSqlDatabase dbConnection(dbConnectionPooled); - DEBUG_ASSERT(dbConnection.isOpen()); m_libraryHashDao.initialize(dbConnection); m_cueDao.initialize(dbConnection); @@ -242,8 +240,7 @@ void LibraryScanner::cleanUpScan() { // Start a transaction for all the library hashing (moved file // detection) stuff. - const mixxx::DbConnectionPooled dbConnectionPooled(m_pDbConnectionPool); - QSqlDatabase dbConnection(dbConnectionPooled); + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); ScopedTransaction transaction(dbConnection); kLogger.debug() << "Marking tracks in changed directories as verified"; diff --git a/src/main.cpp b/src/main.cpp index a718c72acbd..e88b7e1b489 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,27 @@ #include #endif +namespace { + +int runMixxx(MixxxApplication* app, const CmdlineArgs& args) { + int result = -1; + MixxxMainWindow mainWindow(app, args); + // If startup produced a fatal error, then don't even start the + // Qt event loop. + if (ErrorDialogHandler::instance()->checkError()) { + mainWindow.finalize(); + } else { + qDebug() << "Displaying main window"; + mainWindow.show(); + + qDebug() << "Running Mixxx"; + result = app->exec(); + } + return result; +} + +} // anonymous namespace + int main(int argc, char * argv[]) { Console console; @@ -69,7 +90,7 @@ int main(int argc, char * argv[]) { mixxx::Logging::initialize(args.getSettingsPath(), args.getLogLevel(), args.getDebugAssertBreak()); - MixxxApplication a(argc, argv); + MixxxApplication app(argc, argv); // Support utf-8 for all translation strings. Not supported in Qt 5. // TODO(rryan): Is this needed when we switch to qt5? Some sources claim it @@ -98,26 +119,10 @@ int main(int argc, char * argv[]) { } #endif - MixxxMainWindow* mixxx = new MixxxMainWindow(&a, args); - // When the last window is closed, terminate the Qt event loop. - QObject::connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit())); - - int result = -1; - - // If startup produced a fatal error, then don't even start the Qt event - // loop. - if (ErrorDialogHandler::instance()->checkError()) { - mixxx->finalize(); - } else { - qDebug() << "Displaying mixxx"; - mixxx->show(); - - qDebug() << "Running Mixxx"; - result = a.exec(); - } + QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit())); - delete mixxx; + int result = runMixxx(&app, args); qDebug() << "Mixxx shutdown complete with code" << result; diff --git a/src/mixxx.cpp b/src/mixxx.cpp index b9953496cd1..69acdb4bc5d 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -49,6 +49,7 @@ #include "track/track.h" #include "waveform/waveformwidgetfactory.h" #include "waveform/sharedglcontext.h" +#include "database/mixxxdb.h" #include "util/debug.h" #include "util/statsmanager.h" #include "util/timer.h" @@ -67,6 +68,8 @@ #include "preferences/settingsmanager.h" #include "widget/wmainmenubar.h" #include "util/screensaver.h" +#include "util/logger.h" +#include "util/db/dbconnectionpooled.h" #ifdef __VINYLCONTROL__ #include "vinylcontrol/vinylcontrolmanager.h" @@ -76,6 +79,12 @@ #include "preferences/dialog/dlgprefmodplug.h" #endif +namespace { + +const mixxx::Logger kLogger("MixxxMainWindow"); + +} // anonymous namespace + // static const int MixxxMainWindow::kMicrophoneCount = 4; // static @@ -258,17 +267,32 @@ void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) { CoverArtCache::create(); - // (long) - m_pLibrary = new Library(pConfig, - m_pPlayerManager, - m_pRecordingManager); + m_pDbConnectionPool = MixxxDb(pConfig).connectionPool(); + if (!m_pDbConnectionPool) { + // TODO(XXX) something a little more elegant + exit(-1); + } + // Create a connection for the main thread + m_pDbConnectionPool->createThreadLocalConnection(); + if (!initializeDatabase()) { + // TODO(XXX) something a little more elegant + exit(-1); + } + + launchProgress(35); + + m_pLibrary = new Library( + pConfig, + m_pDbConnectionPool, + m_pPlayerManager, + m_pRecordingManager); m_pPlayerManager->bindToLibrary(m_pLibrary); - + new QShortcut( QKeySequence(tr("Ctrl+F", "Search|Focus")), this, SLOT(slotFocusSearch())); - launchProgress(35); + launchProgress(40); // Get Music dir bool hasChanged_MusicDir = false; @@ -547,6 +571,10 @@ void MixxxMainWindow::finalize() { qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting Library"; delete m_pLibrary; + qDebug() << t.elapsed(false).debugMillisWithUnit() << "closing database connection(s)"; + m_pDbConnectionPool->destroyThreadLocalConnection(); + m_pDbConnectionPool.reset(); // should drop the last reference + // PlayerManager depends on Engine, SoundManager, VinylControlManager, and Config qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting PlayerManager"; delete m_pPlayerManager; @@ -636,6 +664,23 @@ void MixxxMainWindow::finalize() { StatsManager::destroy(); } +bool MixxxMainWindow::initializeDatabase() { + kLogger.info() << "Connecting to database"; + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); + if (!dbConnection.isOpen()) { + 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; + } + + kLogger.info() << "Initializing or upgrading database schema"; + return MixxxDb::initDatabaseSchema(dbConnection); +} + void MixxxMainWindow::initializeWindow() { // be sure createMenuBar() is called first DEBUG_ASSERT(m_pMenuBar != nullptr); diff --git a/src/mixxx.h b/src/mixxx.h index bd199c55e1c..9d0e64b80b5 100644 --- a/src/mixxx.h +++ b/src/mixxx.h @@ -27,6 +27,7 @@ #include "track/track.h" #include "util/cmdlineargs.h" #include "util/timer.h" +#include "util/db/dbconnectionpool.h" #include "soundio/sounddeviceerror.h" class ControlPushButton; @@ -58,9 +59,8 @@ class MixxxMainWindow : public QMainWindow { public: // Construtor. files is a list of command line arguments MixxxMainWindow(QApplication *app, const CmdlineArgs& args); - virtual ~MixxxMainWindow(); + ~MixxxMainWindow() override; - void initialize(QApplication *app, const CmdlineArgs& args); void finalize(); // creates the menu_bar and inserts the file Menu @@ -114,12 +114,18 @@ class MixxxMainWindow : public QMainWindow { virtual bool event(QEvent* e); private: + void initialize(QApplication *app, const CmdlineArgs& args); + // progresses the launch image progress bar // this must be called from the GUi thread only void launchProgress(int progress); + void initializeWindow(); void initializeKeyboard(); void checkDirectRendering(); + + bool initializeDatabase(); + bool confirmExit(); QDialog::DialogCode soundDeviceErrorDlg( const QString &title, const QString &text, bool* retryClicked); @@ -161,6 +167,10 @@ class MixxxMainWindow : public QMainWindow { VinylControlManager* m_pVCManager; KeyboardEventFilter* m_pKeyboard; + + // The Mixxx database connection pool + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; + // The library management object Library* m_pLibrary; diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index 600929e0917..4d22d4ae472 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -220,8 +220,7 @@ void DlgPrefWaveform::slotWaveformMeasured(float frameRate, int droppedFrames) { void DlgPrefWaveform::slotClearCachedWaveforms() { AnalysisDao analysisDao(m_pConfig); - const mixxx::DbConnectionPooled dbConnectionPooled(m_pLibrary->dbConnectionPool()); - QSqlDatabase dbConnection(dbConnectionPooled); + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pLibrary->dbConnectionPool()); analysisDao.deleteAnalysesByType(dbConnection, AnalysisDao::TYPE_WAVEFORM); analysisDao.deleteAnalysesByType(dbConnection, AnalysisDao::TYPE_WAVESUMMARY); calculateCachedWaveformDiskUsage(); @@ -229,8 +228,7 @@ void DlgPrefWaveform::slotClearCachedWaveforms() { void DlgPrefWaveform::calculateCachedWaveformDiskUsage() { AnalysisDao analysisDao(m_pConfig); - const mixxx::DbConnectionPooled dbConnectionPooled(m_pLibrary->dbConnectionPool()); - QSqlDatabase dbConnection(dbConnectionPooled); + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pLibrary->dbConnectionPool()); size_t numBytes = analysisDao.getDiskUsageInBytes(dbConnection, AnalysisDao::TYPE_WAVEFORM) + analysisDao.getDiskUsageInBytes(dbConnection, AnalysisDao::TYPE_WAVESUMMARY); diff --git a/src/preferences/upgrade.cpp b/src/preferences/upgrade.cpp index ddfa5a2fd15..c67d6ab6426 100644 --- a/src/preferences/upgrade.cpp +++ b/src/preferences/upgrade.cpp @@ -367,8 +367,7 @@ UserSettingsPointer Upgrade::versionUpgrade(const QString& settingsPath) { const mixxx::DbConnectionPooler dbConnectionPooler( mixxxDb.connectionPool()); if (dbConnectionPooler) { - const mixxx::DbConnectionPooled dbConnectionPooled(mixxxDb.connectionPool()); - QSqlDatabase dbConnection(dbConnectionPooled); + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(mixxxDb.connectionPool()); DEBUG_ASSERT(dbConnection.isOpen()); if (MixxxDb::initDatabaseSchema(dbConnection)) { TrackCollection tc(config); diff --git a/src/test/librarytest.h b/src/test/librarytest.h index 011957134ce..96ce5cd377d 100644 --- a/src/test/librarytest.h +++ b/src/test/librarytest.h @@ -14,11 +14,10 @@ class LibraryTest : public MixxxTest { LibraryTest() : m_mixxxDb(config()), m_dbConnectionPooler(m_mixxxDb.connectionPool()), - m_dbConnectionPooled(m_mixxxDb.connectionPool()), + m_dbConnection(mixxx::DbConnectionPooled(m_mixxxDb.connectionPool())), m_trackCollection(config()) { - QSqlDatabase dbConnection(m_dbConnectionPooled); - MixxxDb::initDatabaseSchema(dbConnection); - m_trackCollection.connectDatabase(dbConnection); + MixxxDb::initDatabaseSchema(m_dbConnection); + m_trackCollection.connectDatabase(m_dbConnection); } ~LibraryTest() override { m_trackCollection.disconnectDatabase(); @@ -29,7 +28,7 @@ class LibraryTest : public MixxxTest { } QSqlDatabase dbConnection() const { - return static_cast(m_dbConnectionPooled); + return m_dbConnection; } TrackCollection* collection() { @@ -39,7 +38,7 @@ class LibraryTest : public MixxxTest { private: const MixxxDb m_mixxxDb; const mixxx::DbConnectionPooler m_dbConnectionPooler; - const mixxx::DbConnectionPooled m_dbConnectionPooled; + QSqlDatabase m_dbConnection; TrackCollection m_trackCollection; }; diff --git a/src/test/queryutiltest.cpp b/src/test/queryutiltest.cpp index 94c7e01ebdc..e11299c16e7 100644 --- a/src/test/queryutiltest.cpp +++ b/src/test/queryutiltest.cpp @@ -14,8 +14,7 @@ class QueryUtilTest : public MixxxTest { QueryUtilTest() : m_mixxxDb(config()), m_dbConnectionPooler(m_mixxxDb.connectionPool()), - m_dbConnectionPooled(m_mixxxDb.connectionPool()), - m_dbConnection(m_dbConnectionPooled) { + m_dbConnection(mixxx::DbConnectionPooled(m_mixxxDb.connectionPool())) { // This test only needs a connection to an empty database // without any particular schema. No need to initialize the // database schema. @@ -24,7 +23,6 @@ class QueryUtilTest : public MixxxTest { private: const MixxxDb m_mixxxDb; const mixxx::DbConnectionPooler m_dbConnectionPooler; - const mixxx::DbConnectionPooled m_dbConnectionPooled; protected: QSqlDatabase m_dbConnection; diff --git a/src/test/schemamanager_test.cpp b/src/test/schemamanager_test.cpp index b9523516d7b..bd24f794784 100644 --- a/src/test/schemamanager_test.cpp +++ b/src/test/schemamanager_test.cpp @@ -17,20 +17,16 @@ class SchemaManagerTest : public MixxxTest { SchemaManagerTest() : m_mixxxDb(config()), m_dbConnectionPooler(m_mixxxDb.connectionPool()), - m_dbConnectionPooled(m_mixxxDb.connectionPool()), - m_dbConnection(m_dbConnectionPooled) { + m_dbConnection(mixxx::DbConnectionPooled(m_mixxxDb.connectionPool())) { } QSqlDatabase dbConnection() const { - return static_cast(m_dbConnectionPooled); + return m_dbConnection; } private: const MixxxDb m_mixxxDb; const mixxx::DbConnectionPooler m_dbConnectionPooler; - const mixxx::DbConnectionPooled m_dbConnectionPooled; - - protected: QSqlDatabase m_dbConnection; }; diff --git a/src/util/db/dbconnectionpool.h b/src/util/db/dbconnectionpool.h index 5ce30495075..d52e467635a 100644 --- a/src/util/db/dbconnectionpool.h +++ b/src/util/db/dbconnectionpool.h @@ -33,14 +33,16 @@ class DbConnectionPool final { const DbConnection::Params& params, const QString& connectionName); + // Prefer to use DbConnectionPooler instead of the + // following functions. Only if there is no appropriate + // scoping possible then use these functions directly. + bool createThreadLocalConnection(); + void destroyThreadLocalConnection(); + 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 diff --git a/src/util/db/dbconnectionpooled.h b/src/util/db/dbconnectionpooled.h index a1b2a317164..6eb31e6fd01 100644 --- a/src/util/db/dbconnectionpooled.h +++ b/src/util/db/dbconnectionpooled.h @@ -29,8 +29,11 @@ class DbConnectionPooled final { // 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; + // constructed database connection is returned. + // + // The returned connections is not bound to this instance: + // QSqlDatabase dbConnection = DbConnectionPooled(...); + /*implicit*/ operator QSqlDatabase() const; private: DbConnectionPoolPtr m_pDbConnectionPool;