1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 08:09:32 +02:00

Dependency cleanup (#627)

* DBConnection doesn't use ProjectFileIO or need friendship...

... Instead, it is given its own weak_ptr to the project

* Demote the bypass flag into DBConnection...

... So SqliteSampleBlock needs ProjectFileIO only to get the DBConnection

* Accessor functions for the connection objects for SqliteSampleBlock

* Another level of indirection to get to the DBConnection object...

... The document holds the unique_ptr to DBConnection in an attached object;
later we want the SqliteSampleBlockFactory to locate the same pointer without
using ProjectFileIO

* SqliteSampleBlock and its factory don't use class ProjectFileIO...

... Instead they share a pointer to the pointer to the current DBConnection.

This means they no longer know how to invoke the lazy opening of that
connection.

So just require that this be done before any operations on blocks happen.  If
it hasn't, throw and let the application recover.

* ProjectFileIO no longer needs weak_ptr to Project for safety...

... so eliminate much ugliness from 127696879dcc5ca687ec50a4ccef7acbed563926

* Move DBConnection to new files...

... And SqliteSampleBlock does not depend on ProjectFileIO.

* SampleBlock.h doesn't need ClientData.h

* Function ProjectFileIO::Conn() isn't needed
This commit is contained in:
Paul Licameli 2020-07-23 02:04:46 -04:00 committed by GitHub
parent 38e830edf0
commit a3fcd611b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 535 additions and 441 deletions

View File

@ -106,6 +106,8 @@ list( APPEND SOURCES
CrashReport.cpp
CrashReport.h
DarkThemeAsCeeCode.h
DBConnection.cpp
DBConnection.h
DeviceChange.cpp
DeviceChange.h
DeviceManager.cpp

325
src/DBConnection.cpp Normal file
View File

@ -0,0 +1,325 @@
/**********************************************************************
Audacity: A Digital Audio Editor
DBConection.cpp
Paul Licameli -- split from ProjectFileIO.cpp
**********************************************************************/
#include "DBConnection.h"
#include "sqlite3.h"
#include <wx/progdlg.h>
#include <wx/string.h>
#include "Internat.h"
#include "Project.h"
// Configuration to provide "safe" connections
static const char *SafeConfig =
"PRAGMA <schema>.locking_mode = SHARED;"
"PRAGMA <schema>.synchronous = NORMAL;"
"PRAGMA <schema>.journal_mode = WAL;"
"PRAGMA <schema>.wal_autocheckpoint = 0;";
// Configuration to provide "Fast" connections
static const char *FastConfig =
"PRAGMA <schema>.locking_mode = SHARED;"
"PRAGMA <schema>.synchronous = OFF;"
"PRAGMA <schema>.journal_mode = OFF;";
DBConnection::DBConnection(const std::weak_ptr<AudacityProject> &pProject)
: mpProject{ pProject }
{
mDB = nullptr;
mBypass = false;
}
DBConnection::~DBConnection()
{
wxASSERT(mDB == nullptr);
}
void DBConnection::SetBypass( bool bypass )
{
mBypass = bypass;
}
bool DBConnection::ShouldBypass()
{
return mBypass;
}
bool DBConnection::Open(const char *fileName)
{
wxASSERT(mDB == nullptr);
int rc;
rc = sqlite3_open(fileName, &mDB);
if (rc != SQLITE_OK)
{
sqlite3_close(mDB);
mDB = nullptr;
return false;
}
// Set default mode
SafeMode();
// Kick off the checkpoint thread
mCheckpointStop = false;
mCheckpointWaitingPages = 0;
mCheckpointCurrentPages = 0;
mCheckpointThread = std::thread([this]{ CheckpointThread(); });
// Install our checkpoint hook
sqlite3_wal_hook(mDB, CheckpointHook, this);
return mDB;
}
bool DBConnection::Close()
{
wxASSERT(mDB != nullptr);
int rc;
// Protect...
if (mDB == nullptr)
{
return true;
}
// Uninstall our checkpoint hook so that no additional checkpoints
// are sent our way. (Though this shouldn't really happen.)
sqlite3_wal_hook(mDB, nullptr, nullptr);
// Display a progress dialog if there's active or pending checkpoints
if (mCheckpointWaitingPages || mCheckpointCurrentPages)
{
TranslatableString title = XO("Checkpointing project");
// Get access to the active project
auto project = mpProject.lock();
if (project)
{
title = XO("Checkpointing %s").Format(project->GetProjectName());
}
// Provides a progress dialog with indeterminate mode
wxGenericProgressDialog pd(title.Translation(),
XO("This may take several seconds").Translation(),
300000, // range
nullptr, // parent
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH);
// Wait for the checkpoints to end
while (mCheckpointWaitingPages || mCheckpointCurrentPages)
{
wxMilliSleep(50);
pd.Pulse();
}
}
// Tell the checkpoint thread to shutdown
{
std::lock_guard<std::mutex> guard(mCheckpointMutex);
mCheckpointStop = true;
mCheckpointCondition.notify_one();
}
// And wait for it to do so
mCheckpointThread.join();
// We're done with the prepared statements
for (auto stmt : mStatements)
{
sqlite3_finalize(stmt.second);
}
mStatements.clear();
// Close the DB
rc = sqlite3_close(mDB);
if (rc != SQLITE_OK)
{
// I guess we could try to recover by repreparing statements and reinstalling
// the hook, but who knows if that would work either.
//
// Should we throw an error???
}
mDB = nullptr;
return true;
}
bool DBConnection::SafeMode(const char *schema /* = "main" */)
{
return ModeConfig(mDB, schema, SafeConfig);
}
bool DBConnection::FastMode(const char *schema /* = "main" */)
{
return ModeConfig(mDB, schema, FastConfig);
}
bool DBConnection::ModeConfig(sqlite3 *db, const char *schema, const char *config)
{
// Ensure attached DB connection gets configured
int rc;
// Replace all schema "keywords" with the schema name
wxString sql = config;
sql.Replace(wxT("<schema>"), schema);
// Set the configuration
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
return rc != SQLITE_OK;
}
sqlite3 *DBConnection::DB()
{
wxASSERT(mDB != nullptr);
return mDB;
}
int DBConnection::GetLastRC() const
{
return sqlite3_errcode(mDB);
}
const wxString DBConnection::GetLastMessage() const
{
return sqlite3_errmsg(mDB);
}
sqlite3_stmt *DBConnection::Prepare(enum StatementID id, const char *sql)
{
int rc;
// Return an existing statement if it's already been prepared
auto iter = mStatements.find(id);
if (iter != mStatements.end())
{
return iter->second;
}
// Prepare the statement
sqlite3_stmt *stmt = nullptr;
rc = sqlite3_prepare_v3(mDB, sql, -1, SQLITE_PREPARE_PERSISTENT, &stmt, 0);
if (rc != SQLITE_OK)
{
wxLogDebug("prepare error %s", sqlite3_errmsg(mDB));
THROW_INCONSISTENCY_EXCEPTION;
}
// And remember it
mStatements.insert({id, stmt});
return stmt;
}
sqlite3_stmt *DBConnection::GetStatement(enum StatementID id)
{
// Look it up
auto iter = mStatements.find(id);
// It should always be there
wxASSERT(iter != mStatements.end());
// Return it
return iter->second;
}
void DBConnection::CheckpointThread()
{
// Open another connection to the DB to prevent blocking the main thread.
//
// If it fails, then we won't checkpoint until the main thread closes
// the associated DB.
sqlite3 *db = nullptr;
if (sqlite3_open(sqlite3_db_filename(mDB, nullptr), &db) == SQLITE_OK)
{
// Configure it to be safe
ModeConfig(db, "main", SafeConfig);
while (true)
{
{
// Wait for work or the stop signal
std::unique_lock<std::mutex> lock(mCheckpointMutex);
mCheckpointCondition.wait(lock,
[&]
{
return mCheckpointWaitingPages || mCheckpointStop;
});
// Requested to stop, so bail
if (mCheckpointStop)
{
break;
}
// Capture the number of pages that need checkpointing and reset
mCheckpointCurrentPages.store( mCheckpointWaitingPages );
mCheckpointWaitingPages = 0;
}
// And kick off the checkpoint. This may not checkpoint ALL frames
// in the WAL. They'll be gotten the next time around.
sqlite3_wal_checkpoint_v2(db, nullptr, SQLITE_CHECKPOINT_PASSIVE, nullptr, nullptr);
// Reset
mCheckpointCurrentPages = 0;
}
}
// All done (always close)
sqlite3_close(db);
return;
}
int DBConnection::CheckpointHook(void *data, sqlite3 *db, const char *schema, int pages)
{
// Get access to our object
DBConnection *that = static_cast<DBConnection *>(data);
// Queue the database pointer for our checkpoint thread to process
std::lock_guard<std::mutex> guard(that->mCheckpointMutex);
that->mCheckpointWaitingPages = pages;
that->mCheckpointCondition.notify_one();
return SQLITE_OK;
}
ConnectionPtr::~ConnectionPtr() = default;
static const AudacityProject::AttachedObjects::RegisteredFactory
sConnectionPtrKey{
[]( AudacityProject & ){
// Ignore the argument; this is just a holder of a
// unique_ptr to DBConnection, which must be filled in later
// (when we can get a weak_ptr to the project)
auto result = std::make_shared< ConnectionPtr >();
return result;
}
};
ConnectionPtr &ConnectionPtr::Get( AudacityProject &project )
{
auto &result =
project.AttachedObjects::Get< ConnectionPtr >( sConnectionPtrKey );
return result;
}
const ConnectionPtr &ConnectionPtr::Get( const AudacityProject &project )
{
return Get( const_cast< AudacityProject & >( project ) );
}

106
src/DBConnection.h Normal file
View File

@ -0,0 +1,106 @@
/**********************************************************************
Audacity: A Digital Audio Editor
DBConection.h
Paul Licameli -- split from ProjectFileIO.h
**********************************************************************/
#ifndef __AUDACITY_DB_CONNECTION__
#define __AUDACITY_DB_CONNECTION__
#include <atomic>
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include "ClientData.h"
struct sqlite3;
struct sqlite3_stmt;
class wxString;
class AudacityProject;
class DBConnection
{
public:
explicit
DBConnection(const std::weak_ptr<AudacityProject> &pProject);
~DBConnection();
bool Open(const char *fileName);
bool Close();
bool SafeMode(const char *schema = "main");
bool FastMode(const char *schema = "main");
bool Assign(sqlite3 *handle);
sqlite3 *Detach();
sqlite3 *DB();
int GetLastRC() const ;
const wxString GetLastMessage() const;
enum StatementID
{
GetSamples,
GetSummary256,
GetSummary64k,
LoadSampleBlock,
InsertSampleBlock,
DeleteSampleBlock
};
sqlite3_stmt *GetStatement(enum StatementID id);
sqlite3_stmt *Prepare(enum StatementID id, const char *sql);
void SetBypass( bool bypass );
bool ShouldBypass();
private:
bool ModeConfig(sqlite3 *db, const char *schema, const char *config);
void CheckpointThread();
static int CheckpointHook(void *data, sqlite3 *db, const char *schema, int pages);
private:
std::weak_ptr<AudacityProject> mpProject;
sqlite3 *mDB;
std::thread mCheckpointThread;
std::condition_variable mCheckpointCondition;
std::mutex mCheckpointMutex;
std::atomic_bool mCheckpointStop{ false };
std::atomic_int mCheckpointWaitingPages{ 0 };
std::atomic_int mCheckpointCurrentPages{ 0 };
std::map<enum StatementID, sqlite3_stmt *> mStatements;
// Bypass transactions if database will be deleted after close
bool mBypass;
};
using Connection = std::unique_ptr<DBConnection>;
// This object attached to the project simply holds the pointer to the
// project's current database connection, which is initialized on demand,
// and may be redirected, temporarily or permanently, to another connection
// when backing the project up or saving or saving-as.
class ConnectionPtr final
: public ClientData::Base
, public std::enable_shared_from_this< ConnectionPtr >
{
public:
static ConnectionPtr &Get( AudacityProject &project );
static const ConnectionPtr &Get( const AudacityProject &project );
~ConnectionPtr() override;
Connection mpConnection;
};
#endif

View File

@ -18,6 +18,7 @@ Paul Licameli split from AudacityProject.cpp
#include <wx/sstream.h>
#include <wx/xml/xml.h>
#include "DBConnection.h"
#include "FileNames.h"
#include "Project.h"
#include "ProjectFileIORegistry.h"
@ -129,19 +130,6 @@ static const char *ProjectFileSchema =
" samples BLOB"
");";
// Configuration to provide "safe" connections
static const char *SafeConfig =
"PRAGMA <schema>.locking_mode = SHARED;"
"PRAGMA <schema>.synchronous = NORMAL;"
"PRAGMA <schema>.journal_mode = WAL;"
"PRAGMA <schema>.wal_autocheckpoint = 0;";
// Configuration to provide "Fast" connections
static const char *FastConfig =
"PRAGMA <schema>.locking_mode = SHARED;"
"PRAGMA <schema>.synchronous = OFF;"
"PRAGMA <schema>.journal_mode = OFF;";
// This singleton handles initialization/shutdown of the SQLite library.
// It is needed because our local SQLite is built with SQLITE_OMIT_AUTOINIT
// defined.
@ -251,34 +239,27 @@ const ProjectFileIO &ProjectFileIO::Get( const AudacityProject &project )
return Get( const_cast< AudacityProject & >( project ) );
}
ProjectFileIO::ProjectFileIO(AudacityProject &)
ProjectFileIO::ProjectFileIO(AudacityProject &project)
: mProject{ project }
{
mPrevConn = nullptr;
mCurrConn = nullptr;
mRecovered = false;
mModified = false;
mTemporary = true;
mBypass = false;
UpdatePrefs();
}
void ProjectFileIO::Init( AudacityProject &project )
{
// This step can't happen in the ctor of ProjectFileIO because ctor of
// AudacityProject wasn't complete
mpProject = project.shared_from_this();
}
ProjectFileIO::~ProjectFileIO()
{
wxASSERT_MSG(mCurrConn == nullptr, wxT("Project file was not closed at shutdown"));
wxASSERT_MSG(!CurrConn(), wxT("Project file was not closed at shutdown"));
}
Connection &ProjectFileIO::Conn()
sqlite3 *ProjectFileIO::DB()
{
if (!mCurrConn)
auto &curConn = CurrConn();
if (!curConn)
{
if (!OpenConnection())
{
@ -289,17 +270,13 @@ Connection &ProjectFileIO::Conn()
}
}
return mCurrConn;
}
sqlite3 *ProjectFileIO::DB()
{
return Conn()->DB();
return curConn->DB();
}
bool ProjectFileIO::OpenConnection(FilePath fileName /* = {} */)
{
wxASSERT(mCurrConn == nullptr);
auto &curConn = CurrConn();
wxASSERT(!curConn);
bool temp = false;
if (fileName.empty())
@ -312,10 +289,11 @@ bool ProjectFileIO::OpenConnection(FilePath fileName /* = {} */)
}
}
mCurrConn = std::make_unique<DBConnection>(this);
if (!mCurrConn->Open(fileName))
// Pass weak_ptr to project into DBConnection constructor
curConn = std::make_unique<DBConnection>(mProject.shared_from_this());
if (!curConn->Open(fileName))
{
mCurrConn = nullptr;
curConn.reset();
return false;
}
@ -334,13 +312,14 @@ bool ProjectFileIO::OpenConnection(FilePath fileName /* = {} */)
bool ProjectFileIO::CloseConnection()
{
wxASSERT(mCurrConn != nullptr);
auto &curConn = CurrConn();
wxASSERT(curConn);
if (!mCurrConn->Close())
if (!curConn->Close())
{
return false;
}
mCurrConn = nullptr;
curConn.reset();
SetFileName({});
@ -354,7 +333,7 @@ void ProjectFileIO::SaveConnection()
// Should do nothing in proper usage, but be sure not to leak a connection:
DiscardConnection();
mPrevConn = std::move(mCurrConn);
mPrevConn = std::move(CurrConn());
mPrevFileName = mFileName;
mPrevTemporary = mTemporary;
@ -381,9 +360,10 @@ void ProjectFileIO::DiscardConnection()
// Close any current connection and switch back to using the saved
void ProjectFileIO::RestoreConnection()
{
if (mCurrConn)
auto &curConn = CurrConn();
if (curConn)
{
if (!mCurrConn->Close())
if (!curConn->Close())
{
// Store an error message
SetDBError(
@ -392,7 +372,7 @@ void ProjectFileIO::RestoreConnection()
}
}
mCurrConn = std::move(mPrevConn);
curConn = std::move(mPrevConn);
SetFileName(mPrevFileName);
mTemporary = mPrevTemporary;
@ -401,9 +381,10 @@ void ProjectFileIO::RestoreConnection()
void ProjectFileIO::UseConnection(Connection &&conn, const FilePath &filePath)
{
wxASSERT(mCurrConn == nullptr);
auto &curConn = CurrConn();
wxASSERT(!curConn);
mCurrConn = std::move(conn);
curConn = std::move(conn);
SetFileName(filePath);
}
@ -724,11 +705,7 @@ Connection ProjectFileIO::CopyTo(const FilePath &destpath,
const std::shared_ptr<TrackList> &tracks /* = nullptr */)
{
// Get access to the active tracklist
auto pProject = mpProject.lock();
if (!pProject)
{
return nullptr;
}
auto pProject = &mProject;
auto &tracklist = tracks ? *tracks : TrackList::Get(*pProject);
BlockIDs blockids;
@ -808,7 +785,7 @@ Connection ProjectFileIO::CopyTo(const FilePath &destpath,
}
// Ensure attached DB connection gets configured
mCurrConn->FastMode("outbound");
CurrConn()->FastMode("outbound");
// Install our schema into the new database
if (!InstallSchema(db, "outbound"))
@ -932,7 +909,7 @@ Connection ProjectFileIO::CopyTo(const FilePath &destpath,
}
// Open the newly created database
destConn = std::make_unique<DBConnection>(this);
destConn = std::make_unique<DBConnection>(mProject.shared_from_this());
if (!destConn->Open(destpath))
{
SetDBError(
@ -1025,6 +1002,12 @@ bool ProjectFileIO::ShouldVacuum(const std::shared_ptr<TrackList> &tracks)
return true;
}
Connection &ProjectFileIO::CurrConn()
{
auto &connectionPtr = ConnectionPtr::Get( mProject );
return connectionPtr.mpConnection;
}
void ProjectFileIO::Vacuum(const std::shared_ptr<TrackList> &tracks)
{
// Haven't vacuumed yet
@ -1076,7 +1059,8 @@ void ProjectFileIO::Vacuum(const std::shared_ptr<TrackList> &tracks)
}
// Reopen the original database using the temporary name
Connection tempConn = std::make_unique<DBConnection>(this);
Connection tempConn =
std::make_unique<DBConnection>(mProject.shared_from_this());
if (!tempConn->Open(tempName))
{
SetDBError(XO("Failed to open project file"));
@ -1142,11 +1126,7 @@ void ProjectFileIO::UpdatePrefs()
// Pass a number in to show project number, or -1 not to.
void ProjectFileIO::SetProjectTitle(int number)
{
auto pProject = mpProject.lock();
if (! pProject )
return;
auto &project = *pProject;
auto &project = mProject;
auto pWindow = project.GetFrame();
if (!pWindow)
{
@ -1196,10 +1176,7 @@ const FilePath &ProjectFileIO::GetFileName() const
void ProjectFileIO::SetFileName(const FilePath &fileName)
{
auto pProject = mpProject.lock();
if (! pProject )
return;
auto &project = *pProject;
auto &project = mProject;
mFileName = fileName;
@ -1217,10 +1194,7 @@ void ProjectFileIO::SetFileName(const FilePath &fileName)
bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
auto pProject = mpProject.lock();
if (! pProject )
return false;
auto &project = *pProject;
auto &project = mProject;
auto &window = GetProjectFrame(project);
auto &viewInfo = ViewInfo::Get(project);
auto &settings = ProjectSettings::Get(project);
@ -1350,10 +1324,7 @@ bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
XMLTagHandler *ProjectFileIO::HandleXMLChild(const wxChar *tag)
{
auto pProject = mpProject.lock();
if (! pProject )
return nullptr;
auto &project = *pProject;
auto &project = mProject;
auto fn = ProjectFileIORegistry::Lookup(tag);
if (fn)
{
@ -1383,10 +1354,7 @@ void ProjectFileIO::WriteXML(XMLWriter &xmlFile,
const std::shared_ptr<TrackList> &tracks /* = nullptr */)
// may throw
{
auto pProject = mpProject.lock();
if (! pProject )
THROW_INCONSISTENCY_EXCEPTION;
auto &proj = *pProject;
auto &proj = mProject;
auto &tracklist = tracks ? *tracks : TrackList::Get(proj);
auto &viewInfo = ViewInfo::Get(proj);
auto &tags = Tags::Get(proj);
@ -1655,11 +1623,7 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName)
};
// Get access to the active tracklist
auto pProject = mpProject.lock();
if (!pProject)
{
return false;
}
auto pProject = &mProject;
auto &tracklist = TrackList::Get(*pProject);
// Search for a timetrack and remove it if the project already has one
@ -2058,10 +2022,11 @@ bool ProjectFileIO::SaveCopy(const FilePath& fileName)
bool ProjectFileIO::CloseProject()
{
wxASSERT(mCurrConn != nullptr);
auto &currConn = CurrConn();
wxASSERT(currConn);
// Protect...
if (mCurrConn == nullptr)
if (!currConn)
{
return true;
}
@ -2106,7 +2071,7 @@ bool ProjectFileIO::IsRecovered() const
void ProjectFileIO::Reset()
{
wxASSERT_MSG(mCurrConn == nullptr, wxT("Resetting project with open project file"));
wxASSERT_MSG(!CurrConn(), wxT("Resetting project with open project file"));
mModified = false;
mRecovered = false;
@ -2146,13 +2111,14 @@ void ProjectFileIO::SetError(const TranslatableString &msg)
void ProjectFileIO::SetDBError(const TranslatableString &msg)
{
auto &currConn = CurrConn();
mLastError = msg;
wxLogDebug(wxT("SQLite error: %s"), mLastError.Debug());
printf(" Lib error: %s", mLastError.Debug().mb_str().data());
if (mCurrConn)
if (currConn)
{
mLibraryError = Verbatim(sqlite3_errmsg(mCurrConn->DB()));
mLibraryError = Verbatim(sqlite3_errmsg(currConn->DB()));
wxLogDebug(wxT(" Lib error: %s"), mLibraryError.Debug());
printf(" Lib error: %s", mLibraryError.Debug().mb_str().data());
}
@ -2162,13 +2128,18 @@ void ProjectFileIO::SetDBError(const TranslatableString &msg)
void ProjectFileIO::SetBypass()
{
auto &currConn = CurrConn();
if (!currConn)
return;
// Determine if we can bypass sample block deletes during shutdown.
//
// IMPORTANT:
// If the project was vacuumed, then we MUST bypass further
// deletions since the new file doesn't have the blocks that the
// Sequences expect to be there.
mBypass = true;
currConn->SetBypass( true );
// Only permanent project files need cleaning at shutdown
if (!IsTemporary() && !WasVacuumed())
@ -2182,18 +2153,13 @@ void ProjectFileIO::SetBypass()
// changes.
if (HadUnused())
{
mBypass = false;
currConn->SetBypass( false );
}
}
return;
}
bool ProjectFileIO::ShouldBypass()
{
return mBypass;
}
AutoCommitTransaction::AutoCommitTransaction(ProjectFileIO &projectFileIO,
const char *name)
: mIO(projectFileIO),
@ -2233,259 +2199,3 @@ bool AutoCommitTransaction::Rollback()
return mInTrans;
}
DBConnection::DBConnection(ProjectFileIO *io)
: mIO(*io)
{
mDB = nullptr;
}
DBConnection::~DBConnection()
{
wxASSERT(mDB == nullptr);
}
bool DBConnection::Open(const char *fileName)
{
wxASSERT(mDB == nullptr);
int rc;
rc = sqlite3_open(fileName, &mDB);
if (rc != SQLITE_OK)
{
sqlite3_close(mDB);
mDB = nullptr;
return false;
}
// Set default mode
SafeMode();
// Kick off the checkpoint thread
mCheckpointStop = false;
mCheckpointWaitingPages = 0;
mCheckpointCurrentPages = 0;
mCheckpointThread = std::thread([this]{ CheckpointThread(); });
// Install our checkpoint hook
sqlite3_wal_hook(mDB, CheckpointHook, this);
return mDB;
}
bool DBConnection::Close()
{
wxASSERT(mDB != nullptr);
int rc;
// Protect...
if (mDB == nullptr)
{
return true;
}
// Uninstall our checkpoint hook so that no additional checkpoints
// are sent our way. (Though this shouldn't really happen.)
sqlite3_wal_hook(mDB, nullptr, nullptr);
// Display a progress dialog if there's active or pending checkpoints
if (mCheckpointWaitingPages || mCheckpointCurrentPages)
{
TranslatableString title = XO("Checkpointing project");
// Get access to the active project
auto project = mIO.mpProject.lock();
if (project)
{
title = XO("Checkpointing %s").Format(project->GetProjectName());
}
// Provides a progress dialog with indeterminate mode
wxGenericProgressDialog pd(title.Translation(),
XO("This may take several seconds").Translation(),
300000, // range
nullptr, // parent
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH);
// Wait for the checkpoints to end
while (mCheckpointWaitingPages || mCheckpointCurrentPages)
{
wxMilliSleep(50);
pd.Pulse();
}
}
// Tell the checkpoint thread to shutdown
{
std::lock_guard<std::mutex> guard(mCheckpointMutex);
mCheckpointStop = true;
mCheckpointCondition.notify_one();
}
// And wait for it to do so
mCheckpointThread.join();
// We're done with the prepared statements
for (auto stmt : mStatements)
{
sqlite3_finalize(stmt.second);
}
mStatements.clear();
// Close the DB
rc = sqlite3_close(mDB);
if (rc != SQLITE_OK)
{
// I guess we could try to recover by repreparing statements and reinstalling
// the hook, but who knows if that would work either.
//
// Should we throw an error???
}
mDB = nullptr;
return true;
}
bool DBConnection::SafeMode(const char *schema /* = "main" */)
{
return ModeConfig(mDB, schema, SafeConfig);
}
bool DBConnection::FastMode(const char *schema /* = "main" */)
{
return ModeConfig(mDB, schema, FastConfig);
}
bool DBConnection::ModeConfig(sqlite3 *db, const char *schema, const char *config)
{
// Ensure attached DB connection gets configured
int rc;
// Replace all schema "keywords" with the schema name
wxString sql = config;
sql.Replace(wxT("<schema>"), schema);
// Set the configuration
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
return rc != SQLITE_OK;
}
sqlite3 *DBConnection::DB()
{
wxASSERT(mDB != nullptr);
return mDB;
}
int DBConnection::GetLastRC() const
{
return sqlite3_errcode(mDB);
}
const wxString DBConnection::GetLastMessage() const
{
return sqlite3_errmsg(mDB);
}
sqlite3_stmt *DBConnection::Prepare(enum StatementID id, const char *sql)
{
int rc;
// Return an existing statement if it's already been prepared
auto iter = mStatements.find(id);
if (iter != mStatements.end())
{
return iter->second;
}
// Prepare the statement
sqlite3_stmt *stmt = nullptr;
rc = sqlite3_prepare_v3(mDB, sql, -1, SQLITE_PREPARE_PERSISTENT, &stmt, 0);
if (rc != SQLITE_OK)
{
wxLogDebug("prepare error %s", sqlite3_errmsg(mDB));
THROW_INCONSISTENCY_EXCEPTION;
}
// And remember it
mStatements.insert({id, stmt});
return stmt;
}
sqlite3_stmt *DBConnection::GetStatement(enum StatementID id)
{
// Look it up
auto iter = mStatements.find(id);
// It should always be there
wxASSERT(iter != mStatements.end());
// Return it
return iter->second;
}
void DBConnection::CheckpointThread()
{
// Open another connection to the DB to prevent blocking the main thread.
//
// If it fails, then we won't checkpoint until the main thread closes
// the associated DB.
sqlite3 *db = nullptr;
if (sqlite3_open(sqlite3_db_filename(mDB, nullptr), &db) == SQLITE_OK)
{
// Configure it to be safe
ModeConfig(db, "main", SafeConfig);
while (true)
{
{
// Wait for work or the stop signal
std::unique_lock<std::mutex> lock(mCheckpointMutex);
mCheckpointCondition.wait(lock,
[&]
{
return mCheckpointWaitingPages || mCheckpointStop;
});
// Requested to stop, so bail
if (mCheckpointStop)
{
break;
}
// Capture the number of pages that need checkpointing and reset
mCheckpointCurrentPages.store( mCheckpointWaitingPages );
mCheckpointWaitingPages = 0;
}
// And kick off the checkpoint. This may not checkpoint ALL frames
// in the WAL. They'll be gotten the next time around.
sqlite3_wal_checkpoint_v2(db, nullptr, SQLITE_CHECKPOINT_PASSIVE, nullptr, nullptr);
// Reset
mCheckpointCurrentPages = 0;
}
}
// All done (always close)
sqlite3_close(db);
return;
}
int DBConnection::CheckpointHook(void *data, sqlite3 *db, const char *schema, int pages)
{
// Get access to our object
DBConnection *that = static_cast<DBConnection *>(data);
// Queue the database pointer for our checkpoint thread to process
std::lock_guard<std::mutex> guard(that->mCheckpointMutex);
that->mCheckpointWaitingPages = pages;
that->mCheckpointCondition.notify_one();
return SQLITE_OK;
}

View File

@ -11,12 +11,7 @@ Paul Licameli split from AudacityProject.h
#ifndef __AUDACITY_PROJECT_FILE_IO__
#define __AUDACITY_PROJECT_FILE_IO__
#include <atomic>
#include <condition_variable>
#include <memory>
#include <map>
#include <mutex>
#include <thread>
#include <set>
#include "ClientData.h" // to inherit
@ -60,9 +55,6 @@ public:
static const ProjectFileIO &Get( const AudacityProject &project );
explicit ProjectFileIO( AudacityProject &project );
// unfortunate two-step construction needed because of
// enable_shared_from_this
void Init( AudacityProject &project );
ProjectFileIO( const ProjectFileIO & ) PROHIBITED;
ProjectFileIO &operator=( const ProjectFileIO & ) PROHIBITED;
@ -110,7 +102,6 @@ public:
// SqliteSampleBlock::~SqliteSampleBlock()
// ProjectManager::OnCloseWindow()
void SetBypass();
bool ShouldBypass();
// Remove all unused space within a project file
void Vacuum(const std::shared_ptr<TrackList> &tracks);
@ -146,8 +137,6 @@ private:
// if opening fails.
sqlite3 *DB();
Connection &Conn();
bool OpenConnection(FilePath fileName = {});
bool CloseConnection();
@ -195,8 +184,10 @@ private:
bool ShouldVacuum(const std::shared_ptr<TrackList> &tracks);
private:
Connection &CurrConn();
// non-static data members
std::weak_ptr<AudacityProject> mpProject;
AudacityProject &mProject;
// The project's file path
FilePath mFileName;
@ -210,9 +201,6 @@ private:
// Is this project still a temporary/unsaved project
bool mTemporary;
// Bypass transactions if database will be deleted after close
bool mBypass;
// Project was vacuumed last time Vacuum() ran
bool mWasVacuumed;
@ -223,13 +211,10 @@ private:
FilePath mPrevFileName;
bool mPrevTemporary;
Connection mCurrConn;
TranslatableString mLastError;
TranslatableString mLibraryError;
friend SqliteSampleBlock;
friend AutoCommitTransaction;
friend DBConnection;
};
class AutoCommitTransaction
@ -247,58 +232,6 @@ private:
wxString mName;
};
class DBConnection
{
public:
DBConnection(ProjectFileIO *io);
~DBConnection();
bool Open(const char *fileName);
bool Close();
bool SafeMode(const char *schema = "main");
bool FastMode(const char *schema = "main");
bool Assign(sqlite3 *handle);
sqlite3 *Detach();
sqlite3 *DB();
int GetLastRC() const ;
const wxString GetLastMessage() const;
enum StatementID
{
GetSamples,
GetSummary256,
GetSummary64k,
LoadSampleBlock,
InsertSampleBlock,
DeleteSampleBlock
};
sqlite3_stmt *GetStatement(enum StatementID id);
sqlite3_stmt *Prepare(enum StatementID id, const char *sql);
private:
bool ModeConfig(sqlite3 *db, const char *schema, const char *config);
void CheckpointThread();
static int CheckpointHook(void *data, sqlite3 *db, const char *schema, int pages);
private:
ProjectFileIO &mIO;
sqlite3 *mDB;
std::thread mCheckpointThread;
std::condition_variable mCheckpointCondition;
std::mutex mCheckpointMutex;
std::atomic_bool mCheckpointStop{ false };
std::atomic_int mCheckpointWaitingPages{ 0 };
std::atomic_int mCheckpointCurrentPages{ 0 };
std::map<enum StatementID, sqlite3_stmt *> mStatements;
};
class wxTopLevelWindow;
// TitleRestorer restores project window titles to what they were, in its destructor.

View File

@ -536,7 +536,6 @@ AudacityProject *ProjectManager::New()
InitProjectWindow( window );
auto &projectFileIO = ProjectFileIO::Get( *p );
projectFileIO.Init( *p );
projectFileIO.SetProjectTitle();
MenuManager::Get( project ).CreateMenusAndCommands( project );

View File

@ -7,6 +7,7 @@ SampleBlock.cpp
**********************************************************************/
#include "Audacity.h"
#include "InconsistencyException.h"
#include "SampleBlock.h"
#include "SampleFormat.h"

View File

@ -9,7 +9,7 @@ SampleBlock.h
#ifndef __AUDACITY_SAMPLE_BLOCK__
#define __AUDACITY_SAMPLE_BLOCK__
#include "ClientData.h" // to inherit
#include "audacity/Types.h"
#include <functional>
#include <memory>

View File

@ -11,8 +11,8 @@ Paul Licameli -- split from SampleBlock.cpp and SampleBlock.h
#include <float.h>
#include <sqlite3.h>
#include "DBConnection.h"
#include "SampleFormat.h"
#include "ProjectFileIO.h"
#include "xml/XMLTagHandler.h"
#include "SampleBlock.h" // to inherit
@ -22,7 +22,8 @@ class SqliteSampleBlock final : public SampleBlock
{
public:
explicit SqliteSampleBlock(ProjectFileIO &io);
explicit SqliteSampleBlock(
const std::shared_ptr<ConnectionPtr> &ppConnection);
~SqliteSampleBlock() override;
void CloseLock() override;
@ -75,9 +76,25 @@ private:
void CalcSummary();
private:
DBConnection *Conn() const
{
auto &pConnection = mppConnection->mpConnection;
if (!pConnection) {
throw SimpleMessageBoxException
{
XO("Failed to open the project's database")
};
}
return pConnection.get();
}
sqlite3 *DB() const
{
return Conn()->DB();
}
friend SqliteSampleBlockFactory;
ProjectFileIO & mIO;
const std::shared_ptr<ConnectionPtr> mppConnection;
bool mValid;
bool mDirty;
bool mSilent;
@ -126,11 +143,11 @@ public:
const wxChar **attrs) override;
private:
std::shared_ptr<ProjectFileIO> mpIO;
const std::shared_ptr<ConnectionPtr> mppConnection;
};
SqliteSampleBlockFactory::SqliteSampleBlockFactory( AudacityProject &project )
: mpIO{ ProjectFileIO::Get(project).shared_from_this() }
: mppConnection{ ConnectionPtr::Get(project).shared_from_this() }
{
}
@ -140,7 +157,7 @@ SqliteSampleBlockFactory::~SqliteSampleBlockFactory() = default;
SampleBlockPtr SqliteSampleBlockFactory::DoCreate(
samplePtr src, size_t numsamples, sampleFormat srcformat )
{
auto sb = std::make_shared<SqliteSampleBlock>(*mpIO);
auto sb = std::make_shared<SqliteSampleBlock>(mppConnection);
sb->SetSamples(src, numsamples, srcformat);
return sb;
}
@ -148,7 +165,7 @@ SampleBlockPtr SqliteSampleBlockFactory::DoCreate(
SampleBlockPtr SqliteSampleBlockFactory::DoCreateSilent(
size_t numsamples, sampleFormat srcformat )
{
auto sb = std::make_shared<SqliteSampleBlock>(*mpIO);
auto sb = std::make_shared<SqliteSampleBlock>(mppConnection);
sb->SetSilent(numsamples, srcformat);
return sb;
}
@ -157,7 +174,7 @@ SampleBlockPtr SqliteSampleBlockFactory::DoCreateSilent(
SampleBlockPtr SqliteSampleBlockFactory::DoCreateFromXML(
sampleFormat srcformat, const wxChar **attrs )
{
auto sb = std::make_shared<SqliteSampleBlock>(*mpIO);
auto sb = std::make_shared<SqliteSampleBlock>(mppConnection);
sb->mSampleFormat = srcformat;
int found = 0;
@ -223,13 +240,14 @@ SampleBlockPtr SqliteSampleBlockFactory::DoCreateFromXML(
SampleBlockPtr SqliteSampleBlockFactory::DoGet( SampleBlockID sbid )
{
auto sb = std::make_shared<SqliteSampleBlock>(*mpIO);
auto sb = std::make_shared<SqliteSampleBlock>(mppConnection);
sb->Load(sbid);
return sb;
}
SqliteSampleBlock::SqliteSampleBlock(ProjectFileIO &io)
: mIO(io)
SqliteSampleBlock::SqliteSampleBlock(
const std::shared_ptr<ConnectionPtr> &ppConnection)
: mppConnection(ppConnection)
{
mValid = false;
mSilent = false;
@ -250,7 +268,7 @@ SqliteSampleBlock::SqliteSampleBlock(ProjectFileIO &io)
SqliteSampleBlock::~SqliteSampleBlock()
{
// See ProjectFileIO::Bypass() for a description of mIO.mBypass
if (!mLocked && !mIO.ShouldBypass())
if (!mLocked && !Conn()->ShouldBypass())
{
// In case Delete throws, don't let an exception escape a destructor,
// but we can still enqueue the delayed handler so that an error message
@ -287,7 +305,7 @@ size_t SqliteSampleBlock::DoGetSamples(samplePtr dest,
size_t numsamples)
{
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSamples,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::GetSamples,
"SELECT samples FROM sampleblocks WHERE blockid = ?1;");
return GetBlob(dest,
@ -334,7 +352,7 @@ bool SqliteSampleBlock::GetSummary256(float *dest,
size_t numframes)
{
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary256,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::GetSummary256,
"SELECT summary256 FROM sampleblocks WHERE blockid = ?1;");
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
@ -345,7 +363,7 @@ bool SqliteSampleBlock::GetSummary64k(float *dest,
size_t numframes)
{
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary64k,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::GetSummary64k,
"SELECT summary64k FROM sampleblocks WHERE blockid = ?1;");
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
@ -447,7 +465,7 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
size_t srcoffset,
size_t srcbytes)
{
auto db = mIO.DB();
auto db = DB();
wxASSERT(mBlockID > 0);
@ -516,7 +534,7 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
void SqliteSampleBlock::Load(SampleBlockID sbid)
{
auto db = mIO.DB();
auto db = DB();
int rc;
wxASSERT(sbid > 0);
@ -531,7 +549,7 @@ void SqliteSampleBlock::Load(SampleBlockID sbid)
mSumMin = 0.0;
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::LoadSampleBlock,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::LoadSampleBlock,
"SELECT sampleformat, summin, summax, sumrms,"
" length('summary256'), length('summary64k'), length('samples')"
" FROM sampleblocks WHERE blockid = ?1;");
@ -579,11 +597,11 @@ void SqliteSampleBlock::Load(SampleBlockID sbid)
void SqliteSampleBlock::Commit()
{
auto db = mIO.DB();
auto db = DB();
int rc;
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::InsertSampleBlock,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::InsertSampleBlock,
"INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms,"
" summary256, summary64k, samples)"
" VALUES(?1,?2,?3,?4,?5,?6,?7);");
@ -634,13 +652,13 @@ void SqliteSampleBlock::Commit()
void SqliteSampleBlock::Delete()
{
auto db = mIO.DB();
auto db = DB();
int rc;
wxASSERT(mBlockID > 0);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::DeleteSampleBlock,
sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::DeleteSampleBlock,
"DELETE FROM sampleblocks WHERE blockid = ?1;");
// Bind statement paraemters