1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 15:49:36 +02:00

TransactionScope usable including only DBConnection not ProjectFileIO

This commit is contained in:
Paul Licameli 2020-09-05 14:47:13 -04:00
commit e4319144c8
6 changed files with 214 additions and 168 deletions

View File

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

View File

@ -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<enum StatementID, sqlite3_stmt *> 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<DBConnection>;
// This object attached to the project simply holds the pointer to the

View File

@ -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<const ProjectFileIO::ExecCB *>(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());
if (currConn)
{
mLibraryError = Verbatim(sqlite3_errmsg(currConn->DB()));
wxLogDebug(wxT(" Lib error: %s"), mLibraryError.Debug());
printf(" Lib error: %s", mLibraryError.Debug().mb_str().data());
return currConn->GetLastError();
}
wxASSERT(false);
const TranslatableString &ProjectFileIO::GetLibraryError()
{
auto &currConn = CurrConn();
return currConn->GetLibraryError();
}
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;
}

View File

@ -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<int(int cols, char **vals, char **names)>;
DBConnection &GetConnection();
private:
void WriteXMLHeader(XMLWriter &xmlFile) const;
void WriteXML(XMLWriter &xmlFile, bool recording = false, const std::shared_ptr<TrackList> &tracks = nullptr) /* not override */;
@ -197,8 +194,13 @@ private:
bool prune = false,
const std::shared_ptr<TrackList> &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<TrackList> &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;

View File

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

View File

@ -27,6 +27,7 @@
#include <wx/tokenzr.h>
#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<AudacityProject*>(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();