1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-02 08:39:46 +02:00

AUP3: Detect and delete orphan blocks at startup

This commit is contained in:
Leland Lucius 2020-07-08 00:18:05 -05:00
parent f7d9513f8d
commit af6a23696b
3 changed files with 115 additions and 28 deletions

View File

@ -501,28 +501,26 @@ int ProjectFileIO::Exec(const char *query, ExecCB callback, wxString *result)
return rc; return rc;
} }
wxString ProjectFileIO::GetValue(const char *sql) bool ProjectFileIO::GetValue(const char *sql, wxString &result)
{ {
auto getresult = [&](wxString *result, int cols, char **vals, char **names) auto getresult = [](wxString *result, int cols, char **vals, char **names)
{ {
if (cols == 1 && vals[0]) if (cols == 1 && vals[0])
{ {
result->append(vals[0]); result->assign(vals[0]);
return SQLITE_OK; return SQLITE_OK;
} }
return SQLITE_ABORT; return SQLITE_ABORT;
}; };
wxString value; int rc = Exec(sql, getresult, &result);
int rc = Exec(sql, getresult, &value);
if (rc != SQLITE_OK) if (rc != SQLITE_OK)
{ {
// Message already captured return false;
} }
return value; return true;
} }
bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer) bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
@ -549,6 +547,13 @@ bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
} }
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
// A row wasn't found...not an error
if (rc == SQLITE_DONE)
{
return true;
}
if (rc != SQLITE_ROW) if (rc != SQLITE_ROW)
{ {
SetDBError( SetDBError(
@ -561,6 +566,7 @@ bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
const void *blob = sqlite3_column_blob(stmt, 0); const void *blob = sqlite3_column_blob(stmt, 0);
int size = sqlite3_column_bytes(stmt, 0); int size = sqlite3_column_bytes(stmt, 0);
buffer.Clear();
buffer.AppendData(blob, size); buffer.AppendData(blob, size);
return true; return true;
@ -572,43 +578,40 @@ bool ProjectFileIO::CheckVersion()
int rc; int rc;
// Install our schema if this is an empty DB // Install our schema if this is an empty DB
long count = -1; wxString result;
GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';").ToLong(&count); if (!GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';", result))
if (count == -1)
{ {
return false; return false;
} }
// If the return count is zero, then there are no tables defined, so this // If the return count is zero, then there are no tables defined, so this
// must be a new project file. // must be a new project file.
if (count == 0) if (wxStrtol<char **>(result, nullptr, 10) == 0)
{ {
return InstallSchema(); return InstallSchema();
} }
// Check for our application ID // Check for our application ID
long appid = -1; if (!GetValue("PRAGMA application_ID;", result))
GetValue("PRAGMA application_ID;").ToLong(&appid);
if (appid == -1)
{ {
return false; return false;
} }
// It's a database that SQLite recognizes, but it's not one of ours // It's a database that SQLite recognizes, but it's not one of ours
if (appid != ProjectFileID) if (wxStrtoul<char **>(result, nullptr, 10) != ProjectFileID)
{ {
SetError(XO("This is not an Audacity project file")); SetError(XO("This is not an Audacity project file"));
return false; return false;
} }
// Get the project file version // Get the project file version
long version = -1; if (!GetValue("PRAGMA user_version;", result))
GetValue("PRAGMA user_version;").ToLong(&version);
if (version == -1)
{ {
return false; return false;
} }
long version = wxStrtol<char **>(result, nullptr, 10);
// Project file version is higher than ours. We will refuse to // Project file version is higher than ours. We will refuse to
// process it since we can't trust anything about it. // process it since we can't trust anything about it.
if (version > ProjectFileVersion) if (version > ProjectFileVersion)
@ -658,6 +661,53 @@ bool ProjectFileIO::UpgradeSchema()
return true; return true;
} }
void ProjectFileIO::LoadedBlock(int64_t sbid)
{
if (sbid > mHighestBlockID)
{
mHighestBlockID = sbid;
}
}
bool ProjectFileIO::CheckForOrphans()
{
auto db = DB();
char sql[256];
sqlite3_snprintf(sizeof(sql),
sql,
"DELETE FROM sampleblocks WHERE blockid > %lld",
mHighestBlockID);
char *errmsg = nullptr;
int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
if (errmsg)
{
wxLogDebug(wxT("Failed to delete orphaned blocks: %s"), errmsg);
sqlite3_free(errmsg);
}
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to delete orphaned blocks")
);
return false;
}
int changes = sqlite3_changes(db);
if (changes > 0)
{
wxLogInfo(XO("Total orphan blocks deleted %d").Translation(), changes);
mRecovered = true;
}
return true;
}
sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath) sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
{ {
auto db = DB(); auto db = DB();
@ -1150,15 +1200,22 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
// Get the autosave doc, if any // Get the autosave doc, if any
bool wasAutosave = false; bool wasAutosave = false;
wxMemoryBuffer buffer; wxMemoryBuffer buffer;
if (GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer)) if (!GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer))
{
// Error already set
return false;
}
if (buffer.GetDataLen() > 0)
{ {
doc = AutoSaveFile::Decode(buffer); doc = AutoSaveFile::Decode(buffer);
wasAutosave = true; wasAutosave = true;
} }
// Otherwise, get the project doc // Otherwise, get the project doc
else else if (!GetValue("SELECT doc FROM project;", doc))
{ {
doc = GetValue("SELECT doc FROM project;"); // Error already set
return false;
} }
if (doc.empty()) if (doc.empty())
@ -1168,6 +1225,9 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
XMLFileReader xmlFile; XMLFileReader xmlFile;
// Reset the highest blockid
mHighestBlockID = 0;
success = xmlFile.ParseString(this, doc); success = xmlFile.ParseString(this, doc);
if (!success) if (!success)
{ {
@ -1180,6 +1240,16 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
// Remember if it was recovered or not // Remember if it was recovered or not
mRecovered = wasAutosave; mRecovered = wasAutosave;
// Check for orphans blocks...set mRecovered if any deleted
if (mHighestBlockID > 0)
{
if (!CheckForOrphans())
{
return false;
}
}
if (mRecovered) if (mRecovered)
{ {
mModified = true; mModified = true;
@ -1188,9 +1258,13 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
// A previously saved project will have a document in the project table, so // A previously saved project will have a document in the project table, so
// we use that knowledge to determine if this file is an unsaved/temporary // we use that knowledge to determine if this file is an unsaved/temporary
// file or not // file or not
long count = 0; wxString result;
GetValue("SELECT Count(*) FROM project;").ToLong(&count); if (!GetValue("SELECT Count(*) FROM project;", result))
mTemporary = (count != 1); {
return false;
}
mTemporary = (wxStrtol<char **>(result, nullptr, 10) != 1);
SetFileName(fileName); SetFileName(fileName);

View File

@ -98,6 +98,8 @@ public:
void Bypass(bool bypass); void Bypass(bool bypass);
bool ShouldBypass(); bool ShouldBypass();
void LoadedBlock(int64_t blockID);
private: private:
// XMLTagHandler callback methods // XMLTagHandler callback methods
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override; bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
@ -139,13 +141,16 @@ private:
bool TransactionCommit(const wxString &name); bool TransactionCommit(const wxString &name);
bool TransactionRollback(const wxString &name); bool TransactionRollback(const wxString &name);
wxString GetValue(const char *sql); bool GetValue(const char *sql, wxString &value);
bool GetBlob(const char *sql, wxMemoryBuffer &buffer); bool GetBlob(const char *sql, wxMemoryBuffer &buffer);
bool CheckVersion(); bool CheckVersion();
bool InstallSchema(); bool InstallSchema();
bool UpgradeSchema(); bool UpgradeSchema();
// Checks for orphan blocks. This will go away at a future date
bool CheckForOrphans();
// Return a database connection if successful, which caller must close // Return a database connection if successful, which caller must close
sqlite3 *CopyTo(const FilePath &destpath); sqlite3 *CopyTo(const FilePath &destpath);
@ -177,6 +182,9 @@ private:
TranslatableString mLastError; TranslatableString mLastError;
TranslatableString mLibraryError; TranslatableString mLibraryError;
// Track the highest blockid while loading a project
int64_t mHighestBlockID;
friend SqliteSampleBlock; friend SqliteSampleBlock;
}; };

View File

@ -60,7 +60,7 @@ public:
void SaveXML(XMLWriter &xmlFile) override; void SaveXML(XMLWriter &xmlFile) override;
private: private:
bool Load(SampleBlockID sbid); void Load(SampleBlockID sbid);
bool GetSummary(float *dest, bool GetSummary(float *dest,
size_t frameoffset, size_t frameoffset,
size_t numframes, size_t numframes,
@ -186,6 +186,11 @@ SampleBlockPtr SqliteSampleBlockFactory::DoCreateFromXML(
{ {
// This may throw // This may throw
sb->Load((SampleBlockID) nValue); sb->Load((SampleBlockID) nValue);
// Tell the IO manager the blockid we just loaded so it can track
// the highest one encountered.
mpIO->LoadedBlock(nValue);
found++; found++;
} }
else if (wxStrcmp(attr, wxT("samplecount")) == 0) else if (wxStrcmp(attr, wxT("samplecount")) == 0)
@ -518,7 +523,7 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
return srcbytes; return srcbytes;
} }
bool SqliteSampleBlock::Load(SampleBlockID sbid) void SqliteSampleBlock::Load(SampleBlockID sbid)
{ {
auto db = mIO.DB(); auto db = mIO.DB();