mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-16 16:10:06 +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:
parent
38e830edf0
commit
a3fcd611b5
@ -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
325
src/DBConnection.cpp
Normal 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
106
src/DBConnection.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -536,7 +536,6 @@ AudacityProject *ProjectManager::New()
|
||||
InitProjectWindow( window );
|
||||
|
||||
auto &projectFileIO = ProjectFileIO::Get( *p );
|
||||
projectFileIO.Init( *p );
|
||||
projectFileIO.SetProjectTitle();
|
||||
|
||||
MenuManager::Get( project ).CreateMenusAndCommands( project );
|
||||
|
@ -7,6 +7,7 @@ SampleBlock.cpp
|
||||
**********************************************************************/
|
||||
|
||||
#include "Audacity.h"
|
||||
#include "InconsistencyException.h"
|
||||
#include "SampleBlock.h"
|
||||
#include "SampleFormat.h"
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user