diff --git a/src/DBConnection.cpp b/src/DBConnection.cpp index d0b3b8348..f1b54b170 100644 --- a/src/DBConnection.cpp +++ b/src/DBConnection.cpp @@ -55,6 +55,28 @@ bool DBConnection::ShouldBypass() return mBypass; } +void DBConnection::SetError( + const TranslatableString &msg, const TranslatableString &libraryError) +{ + mLastError = msg; + mLibraryError = libraryError; +} + +void DBConnection::SetDBError( + const TranslatableString &msg, const TranslatableString &libraryError) +{ + mLastError = msg; + wxLogDebug(wxT("SQLite error: %s"), mLastError.Debug()); + printf(" Lib error: %s", mLastError.Debug().mb_str().data()); + + mLibraryError = libraryError.empty() + ? Verbatim(sqlite3_errmsg(DB())) : libraryError; + wxLogDebug(wxT(" Lib error: %s"), mLibraryError.Debug()); + printf(" Lib error: %s", mLibraryError.Debug().mb_str().data()); + + wxASSERT(false); +} + bool DBConnection::Open(const char *fileName) { wxASSERT(mDB == nullptr); @@ -315,6 +337,107 @@ int DBConnection::CheckpointHook(void *data, sqlite3 *db, const char *schema, in return SQLITE_OK; } +bool TransactionScope::TransactionStart(const wxString &name) +{ + char *errmsg = nullptr; + + int rc = sqlite3_exec(mConnection.DB(), + wxT("SAVEPOINT ") + name + wxT(";"), + nullptr, + nullptr, + &errmsg); + + if (errmsg) + { + mConnection.SetDBError( + XO("Failed to create savepoint:\n\n%s").Format(name) + ); + sqlite3_free(errmsg); + } + + return rc == SQLITE_OK; +} + +bool TransactionScope::TransactionCommit(const wxString &name) +{ + char *errmsg = nullptr; + + int rc = sqlite3_exec(mConnection.DB(), + wxT("RELEASE ") + name + wxT(";"), + nullptr, + nullptr, + &errmsg); + + if (errmsg) + { + mConnection.SetDBError( + XO("Failed to release savepoint:\n\n%s").Format(name) + ); + sqlite3_free(errmsg); + } + + return rc == SQLITE_OK; +} + +bool TransactionScope::TransactionRollback(const wxString &name) +{ + char *errmsg = nullptr; + + int rc = sqlite3_exec(mConnection.DB(), + wxT("ROLLBACK TO ") + name + wxT(";"), + nullptr, + nullptr, + &errmsg); + + if (errmsg) + { + mConnection.SetDBError( + XO("Failed to release savepoint:\n\n%s").Format(name) + ); + sqlite3_free(errmsg); + } + + return rc == SQLITE_OK; +} + +TransactionScope::TransactionScope( + DBConnection &connection, const char *name) +: mConnection(connection), + mName(name) +{ + mInTrans = TransactionStart(mName); + if ( !mInTrans ) + // To do, improve the message + throw SimpleMessageBoxException( XO("Database error") ); +} + +TransactionScope::~TransactionScope() +{ + if (mInTrans) + { + // Rollback AND REMOVE the transaction + // -- must do both; rolling back a savepoint only rewinds it + // without removing it, unlike the ROLLBACK command + if (!(TransactionRollback(mName) && + TransactionCommit(mName) ) ) + { + // Do not throw from a destructor! + // This has to be a no-fail cleanup that does the best that it can. + } + } +} + +bool TransactionScope::Commit() +{ + if ( !mInTrans ) + // Misuse of this class + THROW_INCONSISTENCY_EXCEPTION; + + mInTrans = !TransactionCommit(mName); + + return mInTrans; +} + ConnectionPtr::~ConnectionPtr() { wxASSERT_MSG(!mpConnection, wxT("Project file was not closed at shutdown")); diff --git a/src/DBConnection.h b/src/DBConnection.h index c208639c3..131b85e8d 100644 --- a/src/DBConnection.h +++ b/src/DBConnection.h @@ -69,6 +69,21 @@ public: void SetBypass( bool bypass ); bool ShouldBypass(); + //! Just set stored errors + void SetError( + const TranslatableString &msg, + const TranslatableString &libraryError = {} ); + + //! Set stored errors and write to log; and default libraryError to what database library reports + void SetDBError( + const TranslatableString &msg, + const TranslatableString &libraryError = {} ); + + const TranslatableString &GetLastError() const + { return mLastError; } + const TranslatableString &GetLibraryError() const + { return mLibraryError; } + private: bool ModeConfig(sqlite3 *db, const char *schema, const char *config); @@ -88,10 +103,35 @@ private: std::map mStatements; + TranslatableString mLastError; + TranslatableString mLibraryError; + // Bypass transactions if database will be deleted after close bool mBypass; }; +// Make a savepoint (a transaction, possibly nested) with the given name; +// roll it back at destruction time, unless an explicit Commit() happened first. +// Commit() must not be called again after one successful call. +// An exception is thrown from the constructor if the transaction cannot open. +class TransactionScope +{ +public: + TransactionScope(DBConnection &connection, const char *name); + ~TransactionScope(); + + bool Commit(); + +private: + bool TransactionStart(const wxString &name); + bool TransactionCommit(const wxString &name); + bool TransactionRollback(const wxString &name); + + DBConnection &mConnection; + bool mInTrans; + wxString mName; +}; + using Connection = std::unique_ptr; // This object attached to the project simply holds the pointer to the diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index 0ea12f42d..a1b367c51 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -256,7 +256,7 @@ ProjectFileIO::~ProjectFileIO() { } -sqlite3 *ProjectFileIO::DB() +DBConnection &ProjectFileIO::GetConnection() { auto &curConn = CurrConn(); if (!curConn) @@ -270,9 +270,18 @@ sqlite3 *ProjectFileIO::DB() } } - return curConn->DB(); + return *curConn; } +sqlite3 *ProjectFileIO::DB() +{ + return GetConnection().DB(); +} + +/*! + @pre *CurConn() does not exist + @post *CurConn() exists or return value is false + */ bool ProjectFileIO::OpenConnection(FilePath fileName /* = {} */) { auto &curConn = CurrConn(); @@ -414,69 +423,6 @@ void ProjectFileIO::UseConnection(Connection &&conn, const FilePath &filePath) SetFileName(filePath); } -bool ProjectFileIO::TransactionStart(const wxString &name) -{ - char *errmsg = nullptr; - - int rc = sqlite3_exec(DB(), - wxT("SAVEPOINT ") + name + wxT(";"), - nullptr, - nullptr, - &errmsg); - - if (errmsg) - { - SetDBError( - XO("Failed to create savepoint:\n\n%s").Format(name) - ); - sqlite3_free(errmsg); - } - - return rc == SQLITE_OK; -} - -bool ProjectFileIO::TransactionCommit(const wxString &name) -{ - char *errmsg = nullptr; - - int rc = sqlite3_exec(DB(), - wxT("RELEASE ") + name + wxT(";"), - nullptr, - nullptr, - &errmsg); - - if (errmsg) - { - SetDBError( - XO("Failed to release savepoint:\n\n%s").Format(name) - ); - sqlite3_free(errmsg); - } - - return rc == SQLITE_OK; -} - -bool ProjectFileIO::TransactionRollback(const wxString &name) -{ - char *errmsg = nullptr; - - int rc = sqlite3_exec(DB(), - wxT("ROLLBACK TO ") + name + wxT(";"), - nullptr, - nullptr, - &errmsg); - - if (errmsg) - { - SetDBError( - XO("Failed to release savepoint:\n\n%s").Format(name) - ); - sqlite3_free(errmsg); - } - - return rc == SQLITE_OK; -} - static int ExecCallback(void *data, int cols, char **vals, char **names) { auto &cb = *static_cast(data); @@ -498,9 +444,9 @@ int ProjectFileIO::Exec(const char *query, const ExecCB &callback) if (rc != SQLITE_ABORT && errmsg) { SetDBError( - XO("Failed to execute a project file command:\n\n%s").Format(query) + XO("Failed to execute a project file command:\n\n%s").Format(query), + Verbatim(errmsg) ); - mLibraryError = Verbatim(errmsg); } if (errmsg) { @@ -1760,9 +1706,8 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) if (!xmlFile.ParseString(this, output.GetString())) { SetError( - XO("Unable to parse project information.") + XO("Unable to parse project information."), xmlFile.GetErrorStr() ); - mLibraryError = xmlFile.GetErrorStr(); return false; } @@ -1837,9 +1782,9 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName) if (!success) { SetError( - XO("Unable to parse project information.") + XO("Unable to parse project information."), + xmlFile.GetErrorStr() ); - mLibraryError = xmlFile.GetErrorStr(); return false; } @@ -2113,37 +2058,30 @@ wxLongLong ProjectFileIO::GetFreeDiskSpace() const return -1; } -const TranslatableString &ProjectFileIO::GetLastError() const -{ - return mLastError; -} - -const TranslatableString &ProjectFileIO::GetLibraryError() const -{ - return mLibraryError; -} - -void ProjectFileIO::SetError(const TranslatableString &msg) -{ - mLastError = msg; - mLibraryError = {}; -} - -void ProjectFileIO::SetDBError(const TranslatableString &msg) +const TranslatableString &ProjectFileIO::GetLastError() { auto &currConn = CurrConn(); - mLastError = msg; - wxLogDebug(wxT("SQLite error: %s"), mLastError.Debug()); - printf(" Lib error: %s", mLastError.Debug().mb_str().data()); + return currConn->GetLastError(); +} - if (currConn) - { - mLibraryError = Verbatim(sqlite3_errmsg(currConn->DB())); - wxLogDebug(wxT(" Lib error: %s"), mLibraryError.Debug()); - printf(" Lib error: %s", mLibraryError.Debug().mb_str().data()); - } +const TranslatableString &ProjectFileIO::GetLibraryError() +{ + auto &currConn = CurrConn(); + return currConn->GetLibraryError(); +} - wxASSERT(false); +void ProjectFileIO::SetError( + const TranslatableString &msg, const TranslatableString &libraryError ) +{ + auto &currConn = CurrConn(); + currConn->SetError(msg, libraryError); +} + +void ProjectFileIO::SetDBError( + const TranslatableString &msg, const TranslatableString &libraryError) +{ + auto &currConn = CurrConn(); + currConn->SetDBError(msg, libraryError); } void ProjectFileIO::SetBypass() @@ -2447,41 +2385,3 @@ int ProjectFileIO::get_varint(const unsigned char *ptr, int64_t *out) return 9; } - -TransactionScope::TransactionScope(ProjectFileIO &projectFileIO, - const char *name) -: mIO(projectFileIO), - mName(name) -{ - mInTrans = mIO.TransactionStart(mName); - if ( !mInTrans ) - // To do, improve the message - throw SimpleMessageBoxException( XO("Database error") ); -} - -TransactionScope::~TransactionScope() -{ - if (mInTrans) - { - // Rollback AND REMOVE the transaction - // -- must do both; rolling back a savepoint only rewinds it - // without removing it, unlike the ROLLBACK command - if (!(mIO.TransactionRollback(mName) && - mIO.TransactionCommit(mName) ) ) - { - // Do not throw from a destructor! - // This has to be a no-fail cleanup that does the best that it can. - } - } -} - -bool TransactionScope::Commit() -{ - if ( !mInTrans ) - // Misuse of this class - THROW_INCONSISTENCY_EXCEPTION; - - mInTrans = !mIO.TransactionCommit(mName); - - return mInTrans; -} diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 4434cb70f..ee95cfae2 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -24,7 +24,6 @@ struct sqlite3_stmt; struct sqlite3_value; class AudacityProject; -class TransactionScope; class DBConnection; class ProjectSerializer; class SqliteSampleBlock; @@ -104,8 +103,8 @@ public: // specific database. This is the workhorse for the above 3 methods. static int64_t GetDiskUsage(DBConnection *conn, SampleBlockID blockid); - const TranslatableString &GetLastError() const; - const TranslatableString &GetLibraryError() const; + const TranslatableString &GetLastError(); + const TranslatableString &GetLibraryError(); // Provides a means to bypass "DELETE"s at shutdown if the database // is just going to be deleted anyway. This prevents a noticable @@ -130,10 +129,6 @@ public: // The last compact check found unused blocks in the project file bool HadUnused(); - bool TransactionStart(const wxString &name); - bool TransactionCommit(const wxString &name); - bool TransactionRollback(const wxString &name); - // In one SQL command, delete sample blocks with ids in the given set, or // (when complement is true), with ids not in the given set. bool DeleteBlocks(const BlockIDs &blockids, bool complement); @@ -142,6 +137,8 @@ public: // 0 for success or non-zero to stop the query using ExecCB = std::function; + DBConnection &GetConnection(); + private: void WriteXMLHeader(XMLWriter &xmlFile) const; void WriteXML(XMLWriter &xmlFile, bool recording = false, const std::shared_ptr &tracks = nullptr) /* not override */; @@ -197,8 +194,13 @@ private: bool prune = false, const std::shared_ptr &tracks = nullptr); - void SetError(const TranslatableString & msg); - void SetDBError(const TranslatableString & msg); + //! Just set stored errors + void SetError(const TranslatableString & msg, + const TranslatableString &libraryError = {}); + + //! Set stored errors and write to log; and default libraryError to what database library reports + void SetDBError(const TranslatableString & msg, + const TranslatableString &libraryError = {}); bool ShouldCompact(const std::shared_ptr &tracks); @@ -234,27 +236,6 @@ private: Connection mPrevConn; FilePath mPrevFileName; bool mPrevTemporary; - - TranslatableString mLastError; - TranslatableString mLibraryError; -}; - -// Make a savepoint (a transaction, possibly nested) with the given name; -// roll it back at destruction time, unless an explicit Commit() happened first. -// Commit() must not be called again after one successful call. -// An exception is thrown from the constructor if the transaction cannot open. -class TransactionScope -{ -public: - TransactionScope(ProjectFileIO &projectFileIO, const char *name); - ~TransactionScope(); - - bool Commit(); - -private: - ProjectFileIO &mIO; - bool mInTrans; - wxString mName; }; class wxTopLevelWindow; diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index 835dbfe1e..e30c5fae5 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -15,6 +15,7 @@ Paul Licameli split from AudacityProject.cpp #include "AdornedRulerPanel.h" #include "AudioIO.h" #include "Clipboard.h" +#include "DBConnection.h" #include "FileNames.h" #include "Menus.h" #include "ModuleManager.h" @@ -740,7 +741,7 @@ void ProjectManager::OnCloseWindow(wxCloseEvent & event) projectFileIO.SetBypass(); { - TransactionScope trans(projectFileIO, "Shutdown"); + TransactionScope trans(projectFileIO.GetConnection(), "Shutdown"); // This can reduce reference counts of sample blocks in the project's // tracks. diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index 0c8389a5d..ada488b92 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -27,6 +27,7 @@ #include #include "../AudioIO.h" +#include "../DBConnection.h" #include "../LabelTrack.h" #include "../Mix.h" #include "../PluginManager.h" @@ -1215,7 +1216,7 @@ bool Effect::DoEffect(double projectRate, // This is for performance purposes only, no additional recovery implied auto &pProject = *const_cast(FindProject()); // how to remove this const_cast? auto &pIO = ProjectFileIO::Get(pProject); - TransactionScope trans(pIO, "Effect"); + TransactionScope trans(pIO.GetConnection(), "Effect"); // Update track/group counts CountWaveTracks();