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:
commit
e4319144c8
@ -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"));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user