From e75ab6eaad0cb94dcbe8caf0133449ab0f7c55a9 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Mon, 13 Jul 2020 00:45:14 -0500 Subject: [PATCH] AUP3: Fix last commit I hate conflicts...I ALWAYS get them wrong. --- src/ProjectFileIO.cpp | 152 +++++++++++++++++++++++++----------------- src/ProjectFileIO.h | 8 ++- 2 files changed, 97 insertions(+), 63 deletions(-) diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index fcd5505af..20cf3fc76 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -22,8 +22,10 @@ Paul Licameli split from AudacityProject.cpp #include "ProjectFileIORegistry.h" #include "ProjectSerializer.h" #include "ProjectSettings.h" +#include "SampleBlock.h" #include "Tags.h" #include "ViewInfo.h" +#include "WaveClip.h" #include "WaveTrack.h" #include "widgets/AudacityMessageBox.h" #include "widgets/NumericTextCtrl.h" @@ -44,10 +46,10 @@ static const int ProjectFileVersion = 1; // to sampleblocks. static const char *ProjectFileSchema = - "PRAGMA application_id = %d;" - "PRAGMA user_version = %d;" - "PRAGMA journal_mode = WAL;" - "PRAGMA locking_mode = EXCLUSIVE;" + "PRAGMA .application_id = %d;" + "PRAGMA .user_version = %d;" + "PRAGMA .journal_mode = WAL;" + "PRAGMA .locking_mode = EXCLUSIVE;" "" // project is a binary representation of an XML file. // it's in binary for speed. @@ -61,7 +63,7 @@ static const char *ProjectFileSchema = // There is no limit to document blob size. // dict will be smallish, with an entry for each // kind of field. - "CREATE TABLE IF NOT EXISTS project" + "CREATE TABLE IF NOT EXISTS .project" "(" " id INTEGER PRIMARY KEY," " dict BLOB," @@ -81,7 +83,7 @@ static const char *ProjectFileSchema = // There is no limit to document blob size. // dict will be smallish, with an entry for each // kind of field. - "CREATE TABLE IF NOT EXISTS autosave" + "CREATE TABLE IF NOT EXISTS .autosave" "(" " id INTEGER PRIMARY KEY," " dict BLOB," @@ -90,7 +92,7 @@ static const char *ProjectFileSchema = "" // CREATE SQL tags // tags is not used (yet) - "CREATE TABLE IF NOT EXISTS tags" + "CREATE TABLE IF NOT EXISTS .tags" "(" " name TEXT," " value BLOB" @@ -107,7 +109,7 @@ static const char *ProjectFileSchema = // blockID is a 64 bit number. // // summin to summary64K are summaries at 3 distance scales. - "CREATE TABLE IF NOT EXISTS sampleblocks" + "CREATE TABLE IF NOT EXISTS .sampleblocks" "(" " blockid INTEGER PRIMARY KEY AUTOINCREMENT," " sampleformat INTEGER," @@ -603,7 +605,7 @@ bool ProjectFileIO::CheckVersion() // must be a new project file. if (wxStrtol(result, nullptr, 10) == 0) { - return InstallSchema(); + return InstallSchema(db); } // Check for our application ID @@ -647,19 +649,15 @@ bool ProjectFileIO::CheckVersion() return true; } -bool ProjectFileIO::InstallSchema() +bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *dbname /* = "main" */) { - auto db = DB(); int rc; - char sql[1024]; - sqlite3_snprintf(sizeof(sql), - sql, - ProjectFileSchema, - ProjectFileID, - ProjectFileVersion); + wxString sql; + sql.Printf(ProjectFileSchema, ProjectFileID, ProjectFileVersion); + sql.Replace("", dbname); - rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); + rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); if (rc != SQLITE_OK) { SetDBError( @@ -682,12 +680,12 @@ bool ProjectFileIO::UpgradeSchema() // An SQLite function that takes a blockid and looks it up in a set of // blockids captured during project load. If the blockid isn't found // in the set, it will be deleted. -void ProjectFileIO::isorphan(sqlite3_context *context, int argc, sqlite3_value **argv) +void ProjectFileIO::InSet(sqlite3_context *context, int argc, sqlite3_value **argv) { BlockIDs *blockids = (BlockIDs *) sqlite3_user_data(context); SampleBlockID blockid = sqlite3_value_int64(argv[0]); - sqlite3_result_int(context, blockids->find(blockid) == blockids->end()); + sqlite3_result_int(context, blockids->find(blockid) != blockids->end()); } bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids) @@ -695,18 +693,25 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids) auto db = DB(); int rc; - // Add our function that will verify blockid against the set of valid blockids - rc = sqlite3_create_function(db, "isorphan", 1, SQLITE_UTF8, &blockids, isorphan, nullptr, nullptr); + auto cleanup = finally([&] + { + // Remove our function, whether it was successfully defined or not. + sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, nullptr, nullptr, nullptr); + }); + + // Add the function used to verify each rows blockid against the set of active blockids + rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &blockids, InSet, nullptr, nullptr); if (rc != SQLITE_OK) { - //asdfasdf + wxLogDebug(wxT("Unable to add 'inset' function")); return false; } // Delete all rows that are orphaned - rc = sqlite3_exec(db, "DELETE FROM sampleblocks WHERE isorphan(blockid);", nullptr, nullptr, nullptr); + rc = sqlite3_exec(db, "DELETE FROM sampleblocks WHERE NOT inset(blockid);", nullptr, nullptr, nullptr); if (rc != SQLITE_OK) { + wxLogWarning(XO("Cleanup of orphan blocks failed").Translation()); return false; } @@ -731,32 +736,81 @@ void ProjectFileIO::UpdateCallback(void *data, int operation, char const *dbname sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, bool prune /* = false */) { + // Get access to the active tracklist + auto pProject = mpProject.lock(); + if (!pProject) + { + return nullptr; + } + auto &tracklist = TrackList::Get(*pProject); + + // Collect all active blockids + BlockIDs blockids; + for (auto wt : tracklist.Any()) + { + // Scan all clips within current track + for (const auto &clip : wt->GetAllClips()) + { + // Scan all blockfiles within current clip + auto blocks = clip->GetSequenceBlockArray(); + for (const auto &block : *blocks) + { + blockids.insert(block.sb->GetBlockID()); + } + } + } + auto db = DB(); + sqlite3 *destdb = nullptr; + bool success = false; int rc; ProgressResult res = ProgressResult::Success; - sqlite3_stmt *stmt = nullptr; + // Cleanup in case things go awry auto cleanup = finally([&] { - if (stmt) + // Detach the destination database, whether it was successfully attached or not + sqlite3_exec(db, "DETACH DATABASE dest;", nullptr, nullptr, nullptr); + + // Remove our function, whether it was successfully defined or not. + sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, nullptr, nullptr, nullptr); + + if (!success) { - sqlite3_finalize(stmt); + sqlite3_close(destdb); + + wxRemoveFile(destpath); } }); - rc = sqlite3_prepare_v2(db, "VACUUM INTO ?;", -1, &stmt, 0); + // Add the function used to verify each rows blockid against the set of active blockids + rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &blockids, InSet, nullptr, nullptr); if (rc != SQLITE_OK) { SetDBError( - XO("Unable to prepare project file command") + XO("Unable to add 'inset' function") ); return nullptr; } - rc = sqlite3_bind_text(stmt, 1, destpath.mb_str().data(), destpath.mb_str().length(), SQLITE_STATIC); + // Attach the destination database + wxString sql; + sql.Printf("ATTACH DATABASE '%s' AS dest;", destpath); + + rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); if (rc != SQLITE_OK) { - THROW_INCONSISTENCY_EXCEPTION; + SetDBError( + XO("Unable to attach destination database") + ); + return nullptr; + } + + // Install our schema into the new database + if (!InstallSchema(db, "dest")) + { + // Message already set + return nullptr; } // This whole progress thing is a mess...gotta work on it more @@ -847,22 +901,17 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, bool prune /* = false * #endif } - sqlite3_finalize(stmt); - stmt = nullptr; - - // VACUUMing failed - if (rc != SQLITE_DONE) + // Copy failed + if (rc != SQLITE_OK) { SetDBError( - XO("Project file copy failed") + XO("Failed to copy project file") ); - wxRemoveFile(destpath); - return nullptr; } - sqlite3 *destdb = nullptr; + // Open the newly created database rc = sqlite3_open(destpath, &destdb); if (rc != SQLITE_OK) { @@ -870,13 +919,12 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, bool prune /* = false * XO("Failed to open copy of project file") ); - sqlite3_close(destdb); - - wxRemoveFile(destpath); - return nullptr; } + // Tell cleanup everything is good to go + success = true; + return destdb; } @@ -1519,14 +1567,6 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName) return false; } - // We need to remove the autosave info from the file since it is now - // clean and unmodified. Otherwise, it would be considered "recovered" - // when next opened. - if (!AutoSaveDelete()) - { - return false; - } - // Reaching this point defines success and all the rest are no-fail // operations: @@ -1581,14 +1621,6 @@ bool ProjectFileIO::SaveCopy(const FilePath& fileName) return false; } - // We need to remove the autosave info from the new DB since it is now - // clean and unmodified. Otherwise, it would be considered "recovered" - // when next opened. - if (!AutoSaveDelete(db)) - { - return false; - } - // Tell the finally block to behave success = true; diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 7f380706d..556408c75 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -151,15 +151,17 @@ private: bool GetBlob(const char *sql, wxMemoryBuffer &buffer); bool CheckVersion(); - bool InstallSchema(); + bool InstallSchema(sqlite3 *db, const char *dbname = "main"); bool UpgradeSchema(); // Write project or autosave XML (binary) documents bool WriteDoc(const char *table, const ProjectSerializer &autosave, sqlite3 *db = nullptr); - // Checks for orphan blocks. This will go away at a future date + // Application defined function to verify blockid exists is in set of blockids using BlockIDs = std::set; - static void isorphan(sqlite3_context *context, int argc, sqlite3_value **argv); + static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv); + + // Checks for orphan blocks. This will go away at a future date bool CheckForOrphans(BlockIDs &blockids); // Return a database connection if successful, which caller must close