1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 23:59:41 +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;
}
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])
{
result->append(vals[0]);
result->assign(vals[0]);
return SQLITE_OK;
}
return SQLITE_ABORT;
};
wxString value;
int rc = Exec(sql, getresult, &value);
int rc = Exec(sql, getresult, &result);
if (rc != SQLITE_OK)
{
// Message already captured
return false;
}
return value;
return true;
}
bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
@ -549,6 +547,13 @@ bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
}
rc = sqlite3_step(stmt);
// A row wasn't found...not an error
if (rc == SQLITE_DONE)
{
return true;
}
if (rc != SQLITE_ROW)
{
SetDBError(
@ -561,6 +566,7 @@ bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
const void *blob = sqlite3_column_blob(stmt, 0);
int size = sqlite3_column_bytes(stmt, 0);
buffer.Clear();
buffer.AppendData(blob, size);
return true;
@ -572,43 +578,40 @@ bool ProjectFileIO::CheckVersion()
int rc;
// Install our schema if this is an empty DB
long count = -1;
GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';").ToLong(&count);
if (count == -1)
wxString result;
if (!GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';", result))
{
return false;
}
// If the return count is zero, then there are no tables defined, so this
// must be a new project file.
if (count == 0)
if (wxStrtol<char **>(result, nullptr, 10) == 0)
{
return InstallSchema();
}
// Check for our application ID
long appid = -1;
GetValue("PRAGMA application_ID;").ToLong(&appid);
if (appid == -1)
if (!GetValue("PRAGMA application_ID;", result))
{
return false;
}
// 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"));
return false;
}
// Get the project file version
long version = -1;
GetValue("PRAGMA user_version;").ToLong(&version);
if (version == -1)
if (!GetValue("PRAGMA user_version;", result))
{
return false;
}
long version = wxStrtol<char **>(result, nullptr, 10);
// Project file version is higher than ours. We will refuse to
// process it since we can't trust anything about it.
if (version > ProjectFileVersion)
@ -658,6 +661,53 @@ bool ProjectFileIO::UpgradeSchema()
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)
{
auto db = DB();
@ -1150,15 +1200,22 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
// Get the autosave doc, if any
bool wasAutosave = false;
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);
wasAutosave = true;
}
// 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())
@ -1168,6 +1225,9 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
XMLFileReader xmlFile;
// Reset the highest blockid
mHighestBlockID = 0;
success = xmlFile.ParseString(this, doc);
if (!success)
{
@ -1180,6 +1240,16 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
// Remember if it was recovered or not
mRecovered = wasAutosave;
// Check for orphans blocks...set mRecovered if any deleted
if (mHighestBlockID > 0)
{
if (!CheckForOrphans())
{
return false;
}
}
if (mRecovered)
{
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
// we use that knowledge to determine if this file is an unsaved/temporary
// file or not
long count = 0;
GetValue("SELECT Count(*) FROM project;").ToLong(&count);
mTemporary = (count != 1);
wxString result;
if (!GetValue("SELECT Count(*) FROM project;", result))
{
return false;
}
mTemporary = (wxStrtol<char **>(result, nullptr, 10) != 1);
SetFileName(fileName);

View File

@ -98,6 +98,8 @@ public:
void Bypass(bool bypass);
bool ShouldBypass();
void LoadedBlock(int64_t blockID);
private:
// XMLTagHandler callback methods
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
@ -139,13 +141,16 @@ private:
bool TransactionCommit(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 CheckVersion();
bool InstallSchema();
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
sqlite3 *CopyTo(const FilePath &destpath);
@ -177,6 +182,9 @@ private:
TranslatableString mLastError;
TranslatableString mLibraryError;
// Track the highest blockid while loading a project
int64_t mHighestBlockID;
friend SqliteSampleBlock;
};

View File

@ -60,7 +60,7 @@ public:
void SaveXML(XMLWriter &xmlFile) override;
private:
bool Load(SampleBlockID sbid);
void Load(SampleBlockID sbid);
bool GetSummary(float *dest,
size_t frameoffset,
size_t numframes,
@ -186,6 +186,11 @@ SampleBlockPtr SqliteSampleBlockFactory::DoCreateFromXML(
{
// This may throw
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++;
}
else if (wxStrcmp(attr, wxT("samplecount")) == 0)
@ -518,7 +523,7 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
return srcbytes;
}
bool SqliteSampleBlock::Load(SampleBlockID sbid)
void SqliteSampleBlock::Load(SampleBlockID sbid)
{
auto db = mIO.DB();