AUP3: Fix previous commit and rework db handling
This moves direct handling of the sqlite3 DB handle into it's own class, along with the checkpointing thread and prepared statement cache.
This commit is contained in:
parent
7e9a3f49b8
commit
6f771d1783
File diff suppressed because it is too large
Load Diff
|
@ -30,6 +30,7 @@ struct sqlite3_value;
|
|||
|
||||
class AudacityProject;
|
||||
class AutoCommitTransaction;
|
||||
class DBConnection;
|
||||
class ProjectSerializer;
|
||||
class SqliteSampleBlock;
|
||||
class TrackList;
|
||||
|
@ -40,6 +41,8 @@ using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
|
|||
// From SampleBlock.h
|
||||
using SampleBlockID = long long;
|
||||
|
||||
using Connection = std::unique_ptr<DBConnection>;
|
||||
|
||||
///\brief Object associated with a project that manages reading and writing
|
||||
/// of Audacity project file formats, and autosave
|
||||
class ProjectFileIO final
|
||||
|
@ -143,6 +146,11 @@ private:
|
|||
// if opening fails.
|
||||
sqlite3 *DB();
|
||||
|
||||
Connection &Conn();
|
||||
|
||||
bool OpenConnection(FilePath fileName = {});
|
||||
bool CloseConnection();
|
||||
|
||||
// Put the current database connection aside, keeping it open, so that
|
||||
// another may be opened with OpenDB()
|
||||
void SaveConnection();
|
||||
|
@ -153,26 +161,8 @@ private:
|
|||
// Close any current connection and switch back to using the saved
|
||||
void RestoreConnection();
|
||||
|
||||
// Use a connection that is already open rather than invoke OpenDB
|
||||
void UseConnection(sqlite3 *db, const FilePath &filePath);
|
||||
|
||||
// Make sure the connection/schema combo is configured the way we want
|
||||
void Config(sqlite3 *db, const char *config, const wxString &schema = wxT("main"));
|
||||
|
||||
sqlite3 *OpenDB(FilePath fileName = {});
|
||||
bool CloseDB();
|
||||
|
||||
enum StatementID
|
||||
{
|
||||
GetSamples,
|
||||
GetSummary256,
|
||||
GetSummary64k,
|
||||
LoadSampleBlock,
|
||||
InsertSampleBlock,
|
||||
DeleteSampleBlock
|
||||
};
|
||||
void Prepare(enum StatementID id, const char *sql);
|
||||
sqlite3_stmt *GetStatement(enum StatementID id);
|
||||
// Use a connection that is already open rather than invoke OpenConnection
|
||||
void UseConnection(Connection &&conn, const FilePath &filePath);
|
||||
|
||||
bool Query(const char *sql, const ExecCB &callback);
|
||||
|
||||
|
@ -184,7 +174,7 @@ private:
|
|||
bool UpgradeSchema();
|
||||
|
||||
// Write project or autosave XML (binary) documents
|
||||
bool WriteDoc(const char *table, const ProjectSerializer &autosave, sqlite3 *db = nullptr);
|
||||
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema = "main");
|
||||
|
||||
// Application defined function to verify blockid exists is in set of blockids
|
||||
using BlockIDs = std::set<SampleBlockID>;
|
||||
|
@ -194,10 +184,10 @@ private:
|
|||
bool CheckForOrphans(BlockIDs &blockids);
|
||||
|
||||
// Return a database connection if successful, which caller must close
|
||||
sqlite3 *CopyTo(const FilePath &destpath,
|
||||
const TranslatableString &msg,
|
||||
bool prune = false,
|
||||
const std::shared_ptr<TrackList> &tracks = nullptr);
|
||||
Connection CopyTo(const FilePath &destpath,
|
||||
const TranslatableString &msg,
|
||||
bool prune = false,
|
||||
const std::shared_ptr<TrackList> &tracks = nullptr);
|
||||
|
||||
void SetError(const TranslatableString & msg);
|
||||
void SetDBError(const TranslatableString & msg);
|
||||
|
@ -232,24 +222,17 @@ private:
|
|||
// Project had unused blocks during last Vacuum()
|
||||
bool mHadUnused;
|
||||
|
||||
sqlite3 *mPrevDB;
|
||||
Connection mPrevConn;
|
||||
FilePath mPrevFileName;
|
||||
bool mPrevTemporary;
|
||||
|
||||
sqlite3 *mDB;
|
||||
Connection mCurrConn;
|
||||
TranslatableString mLastError;
|
||||
TranslatableString mLibraryError;
|
||||
|
||||
std::thread mCheckpointThread;
|
||||
std::condition_variable mCheckpointCondition;
|
||||
std::mutex mCheckpointMutex;
|
||||
std::atomic_bool mCheckpointStop{ false };
|
||||
std::atomic< std::uint64_t > mCheckpointWaitingPages{ 0 };
|
||||
std::atomic< std::uint64_t > mCheckpointCurrentPages{ 0 };
|
||||
|
||||
std::map<enum StatementID, sqlite3_stmt *> mStatements;
|
||||
|
||||
friend SqliteSampleBlock;
|
||||
friend AutoCommitTransaction;
|
||||
friend DBConnection;
|
||||
};
|
||||
|
||||
class AutoCommitTransaction
|
||||
|
@ -267,6 +250,58 @@ 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.
|
||||
|
|
|
@ -144,7 +144,13 @@ auto ProjectFileManager::ReadProjectFile( const FilePath &fileName )
|
|||
{
|
||||
if (projectFileIO.IsRecovered())
|
||||
{
|
||||
bool resaved = projectFileIO.SaveProject(fileName);
|
||||
bool resaved = false;
|
||||
|
||||
if (!projectFileIO.IsTemporary())
|
||||
{
|
||||
projectFileIO.SaveProject(fileName);
|
||||
}
|
||||
|
||||
AudacityMessageBox(
|
||||
resaved
|
||||
? XO("This project was not saved properly the last time Audacity ran.\n\n"
|
||||
|
|
|
@ -38,9 +38,9 @@ public:
|
|||
SampleBlockID GetBlockID() override;
|
||||
|
||||
size_t DoGetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples) override;
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples) override;
|
||||
sampleFormat GetSampleFormat() const;
|
||||
size_t GetSampleCount() const override;
|
||||
|
||||
|
@ -64,11 +64,11 @@ private:
|
|||
bool GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
ProjectFileIO::StatementID id,
|
||||
sqlite3_stmt *stmt,
|
||||
size_t srcbytes);
|
||||
size_t GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
ProjectFileIO::StatementID id,
|
||||
sqlite3_stmt *stmt,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes);
|
||||
|
@ -286,9 +286,13 @@ size_t SqliteSampleBlock::DoGetSamples(samplePtr dest,
|
|||
size_t sampleoffset,
|
||||
size_t numsamples)
|
||||
{
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSamples,
|
||||
"SELECT samples FROM sampleblocks WHERE blockid = ?1;");
|
||||
|
||||
return GetBlob(dest,
|
||||
destformat,
|
||||
ProjectFileIO::GetSamples,
|
||||
stmt,
|
||||
mSampleFormat,
|
||||
sampleoffset * SAMPLE_SIZE(mSampleFormat),
|
||||
numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
|
@ -330,33 +334,33 @@ bool SqliteSampleBlock::GetSummary256(float *dest,
|
|||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest,
|
||||
frameoffset,
|
||||
numframes,
|
||||
ProjectFileIO::GetSummary256,
|
||||
mSummary256Bytes);
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary256,
|
||||
"SELECT summary256 FROM sampleblocks WHERE blockid = ?1;");
|
||||
|
||||
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary64k(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest,
|
||||
frameoffset,
|
||||
numframes,
|
||||
ProjectFileIO::GetSummary64k,
|
||||
mSummary256Bytes);
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary64k,
|
||||
"SELECT summary64k FROM sampleblocks WHERE blockid = ?1;");
|
||||
|
||||
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
ProjectFileIO::StatementID id,
|
||||
sqlite3_stmt *stmt,
|
||||
size_t srcbytes)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
floatSample,
|
||||
id,
|
||||
stmt,
|
||||
floatSample,
|
||||
frameoffset * 3 * SAMPLE_SIZE(floatSample),
|
||||
numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample);
|
||||
|
@ -401,12 +405,7 @@ MinMaxRMS SqliteSampleBlock::DoGetMinMaxRMS(size_t start, size_t len)
|
|||
SampleBuffer blockData(len, floatSample);
|
||||
float *samples = (float *) blockData.ptr();
|
||||
|
||||
size_t copied = GetBlob(samples,
|
||||
floatSample,
|
||||
ProjectFileIO::GetSamples,
|
||||
mSampleFormat,
|
||||
start * SAMPLE_SIZE(mSampleFormat),
|
||||
len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
size_t copied = DoGetSamples((samplePtr) samples, floatSample, start, len);
|
||||
for (size_t i = 0; i < copied; ++i, ++samples)
|
||||
{
|
||||
float sample = *samples;
|
||||
|
@ -444,7 +443,7 @@ size_t SqliteSampleBlock::GetSpaceUsage() const
|
|||
|
||||
size_t SqliteSampleBlock::GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
ProjectFileIO::StatementID id,
|
||||
sqlite3_stmt *stmt,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes)
|
||||
|
@ -461,9 +460,6 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
|
|||
int rc;
|
||||
size_t minbytes = 0;
|
||||
|
||||
// Retrieve prepared statement
|
||||
sqlite3_stmt *stmt = mIO.GetStatement(id);
|
||||
|
||||
// Bind statement paraemters
|
||||
// Might return SQLITE_MISUSE which means it's our mistake that we violated
|
||||
// preconditions; should return SQL_OK which is 0
|
||||
|
@ -535,8 +531,11 @@ void SqliteSampleBlock::Load(SampleBlockID sbid)
|
|||
mSumMax = -FLT_MAX;
|
||||
mSumMin = 0.0;
|
||||
|
||||
// Retrieve prepared statement
|
||||
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::LoadSampleBlock);
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::LoadSampleBlock,
|
||||
"SELECT sampleformat, summin, summax, sumrms,"
|
||||
" length('summary256'), length('summary64k'), length('samples')"
|
||||
" FROM sampleblocks WHERE blockid = ?1;");
|
||||
|
||||
// Bind statement paraemters
|
||||
// Might return SQLITE_MISUSE which means it's our mistake that we violated
|
||||
|
@ -584,8 +583,11 @@ void SqliteSampleBlock::Commit()
|
|||
auto db = mIO.DB();
|
||||
int rc;
|
||||
|
||||
// Retrieve prepared statement
|
||||
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::InsertSampleBlock);
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::InsertSampleBlock,
|
||||
"INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms,"
|
||||
" summary256, summary64k, samples)"
|
||||
" VALUES(?1,?2,?3,?4,?5,?6,?7);");
|
||||
|
||||
// Bind statement paraemters
|
||||
// Might return SQLITE_MISUSE which means it's our mistake that we violated
|
||||
|
@ -638,8 +640,9 @@ void SqliteSampleBlock::Delete()
|
|||
|
||||
wxASSERT(mBlockID > 0);
|
||||
|
||||
// Retrieve prepared statement
|
||||
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::DeleteSampleBlock);
|
||||
// Prepare and cache statement...automatically finalized at DB close
|
||||
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::DeleteSampleBlock,
|
||||
"DELETE FROM sampleblocks WHERE blockid = ?1;");
|
||||
|
||||
// Bind statement paraemters
|
||||
// Might return SQLITE_MISUSE which means it's our mistake that we violated
|
||||
|
@ -651,7 +654,7 @@ void SqliteSampleBlock::Delete()
|
|||
|
||||
// Execute the statement
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
if (rc != SQLITE_DONE)
|
||||
{
|
||||
wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));
|
||||
|
||||
|
|
Loading…
Reference in New Issue