1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-02 17:09:26 +02:00

Revert "AUP3: Reworked CopyTo() yet again"

A bit more thought needs to do into this.
This commit is contained in:
Leland Lucius 2020-07-12 10:55:54 -05:00
parent e3c5563d35
commit a0008831e0
4 changed files with 67 additions and 115 deletions

View File

@ -19,10 +19,8 @@ Paul Licameli split from AudacityProject.cpp
#include "ProjectFileIORegistry.h" #include "ProjectFileIORegistry.h"
#include "ProjectSerializer.h" #include "ProjectSerializer.h"
#include "ProjectSettings.h" #include "ProjectSettings.h"
#include "SampleBlock.h"
#include "Tags.h" #include "Tags.h"
#include "ViewInfo.h" #include "ViewInfo.h"
#include "WaveClip.h"
#include "WaveTrack.h" #include "WaveTrack.h"
#include "widgets/AudacityMessageBox.h" #include "widgets/AudacityMessageBox.h"
#include "widgets/NumericTextCtrl.h" #include "widgets/NumericTextCtrl.h"
@ -43,10 +41,10 @@ static const int ProjectFileVersion = 1;
// to sampleblocks. // to sampleblocks.
static const char *ProjectFileSchema = static const char *ProjectFileSchema =
"PRAGMA <dbname>.application_id = %d;" "PRAGMA application_id = %d;"
"PRAGMA <dbname>.user_version = %d;" "PRAGMA user_version = %d;"
"PRAGMA <dbname>.journal_mode = WAL;" "PRAGMA journal_mode = WAL;"
"PRAGMA <dbname>.locking_mode = EXCLUSIVE;" "PRAGMA locking_mode = EXCLUSIVE;"
"" ""
// project is a binary representation of an XML file. // project is a binary representation of an XML file.
// it's in binary for speed. // it's in binary for speed.
@ -60,7 +58,7 @@ static const char *ProjectFileSchema =
// There is no limit to document blob size. // There is no limit to document blob size.
// dict will be smallish, with an entry for each // dict will be smallish, with an entry for each
// kind of field. // kind of field.
"CREATE TABLE IF NOT EXISTS <dbname>.project" "CREATE TABLE IF NOT EXISTS project"
"(" "("
" id INTEGER PRIMARY KEY," " id INTEGER PRIMARY KEY,"
" dict BLOB," " dict BLOB,"
@ -80,7 +78,7 @@ static const char *ProjectFileSchema =
// There is no limit to document blob size. // There is no limit to document blob size.
// dict will be smallish, with an entry for each // dict will be smallish, with an entry for each
// kind of field. // kind of field.
"CREATE TABLE IF NOT EXISTS <dbname>.autosave" "CREATE TABLE IF NOT EXISTS autosave"
"(" "("
" id INTEGER PRIMARY KEY," " id INTEGER PRIMARY KEY,"
" dict BLOB," " dict BLOB,"
@ -89,7 +87,7 @@ static const char *ProjectFileSchema =
"" ""
// CREATE SQL tags // CREATE SQL tags
// tags is not used (yet) // tags is not used (yet)
"CREATE TABLE IF NOT EXISTS <dbname>.tags" "CREATE TABLE IF NOT EXISTS tags"
"(" "("
" name TEXT," " name TEXT,"
" value BLOB" " value BLOB"
@ -106,7 +104,7 @@ static const char *ProjectFileSchema =
// blockID is a 64 bit number. // blockID is a 64 bit number.
// //
// summin to summary64K are summaries at 3 distance scales. // summin to summary64K are summaries at 3 distance scales.
"CREATE TABLE IF NOT EXISTS <dbname>.sampleblocks" "CREATE TABLE IF NOT EXISTS sampleblocks"
"(" "("
" blockid INTEGER PRIMARY KEY AUTOINCREMENT," " blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
" sampleformat INTEGER," " sampleformat INTEGER,"
@ -611,7 +609,7 @@ bool ProjectFileIO::CheckVersion()
// must be a new project file. // must be a new project file.
if (wxStrtol<char **>(result, nullptr, 10) == 0) if (wxStrtol<char **>(result, nullptr, 10) == 0)
{ {
return InstallSchema(db); return InstallSchema();
} }
// Check for our application ID // Check for our application ID
@ -655,15 +653,19 @@ bool ProjectFileIO::CheckVersion()
return true; return true;
} }
bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *dbname /* = "main" */) bool ProjectFileIO::InstallSchema()
{ {
auto db = DB();
int rc; int rc;
wxString sql; char sql[1024];
sql.Printf(ProjectFileSchema, ProjectFileID, ProjectFileVersion); sqlite3_snprintf(sizeof(sql),
sql.Replace("<dbname>", dbname); sql,
ProjectFileSchema,
ProjectFileID,
ProjectFileVersion);
rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
if (rc != SQLITE_OK) if (rc != SQLITE_OK)
{ {
SetDBError( SetDBError(
@ -686,12 +688,12 @@ bool ProjectFileIO::UpgradeSchema()
// An SQLite function that takes a blockid and looks it up in a set of // 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 // blockids captured during project load. If the blockid isn't found
// in the set, it will be deleted. // in the set, it will be deleted.
void ProjectFileIO::InSet(sqlite3_context *context, int argc, sqlite3_value **argv) void ProjectFileIO::isorphan(sqlite3_context *context, int argc, sqlite3_value **argv)
{ {
BlockIDs *blockids = (BlockIDs *) sqlite3_user_data(context); BlockIDs *blockids = (BlockIDs *) sqlite3_user_data(context);
SampleBlockID blockid = sqlite3_value_int64(argv[0]); 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) bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
@ -699,25 +701,18 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
auto db = DB(); auto db = DB();
int rc; int rc;
auto cleanup = finally([&] // 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);
// 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) if (rc != SQLITE_OK)
{ {
wxLogDebug(wxT("Unable to add 'inset' function")); //asdfasdf
return false; return false;
} }
// Delete all rows that are orphaned // Delete all rows that are orphaned
rc = sqlite3_exec(db, "DELETE FROM sampleblocks WHERE NOT inset(blockid);", nullptr, nullptr, nullptr); rc = sqlite3_exec(db, "DELETE FROM sampleblocks WHERE isorphan(blockid);", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK) if (rc != SQLITE_OK)
{ {
wxLogWarning(XO("Cleanup of orphan blocks failed").Translation());
return false; return false;
} }
@ -750,81 +745,32 @@ static int progress_callback(void *data)
sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath) sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
{ {
// 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<const WaveTrack>())
{
// 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(); auto db = DB();
sqlite3 *destdb = nullptr;
bool success = false;
int rc; int rc;
ProgressResult res = ProgressResult::Success; ProgressResult res = ProgressResult::Success;
// Cleanup in case things go awry sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&] auto cleanup = finally([&]
{ {
// Detach the destination database, whether it was successfully attached or not if (stmt)
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_close(destdb); sqlite3_finalize(stmt);
wxRemoveFile(destpath);
} }
}); });
// Add the function used to verify each rows blockid against the set of active blockids rc = sqlite3_prepare_v2(db, "VACUUM INTO ?;", -1, &stmt, 0);
rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &blockids, InSet, nullptr, nullptr);
if (rc != SQLITE_OK) if (rc != SQLITE_OK)
{ {
SetDBError( SetDBError(
XO("Unable to add 'inset' function") XO("Unable to prepare project file command")
); );
return nullptr; return nullptr;
} }
// Attach the destination database rc = sqlite3_bind_text(stmt, 1, destpath.mb_str().data(), destpath.mb_str().length(), SQLITE_STATIC);
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) if (rc != SQLITE_OK)
{ {
SetDBError( THROW_INCONSISTENCY_EXCEPTION;
XO("Unable to attach destination database")
);
return nullptr;
}
// Install our schema into the new database
if (!InstallSchema(db, "dest"))
{
// Message already set
return nullptr;
} }
{ {
@ -834,37 +780,27 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
sqlite3_progress_handler(db, 10, progress_callback, &progress); sqlite3_progress_handler(db, 10, progress_callback, &progress);
rc = sqlite3_exec(db, rc = sqlite3_step(stmt);
"INSERT INTO dest.tags"
" SELECT * FROM main.tags;",
nullptr,
nullptr,
nullptr);
if (rc == SQLITE_OK)
{
rc = sqlite3_exec(db,
"INSERT INTO dest.sampleblocks"
" SELECT * FROM main.sampleblocks"
" WHERE inset(blockid);",
nullptr,
nullptr,
nullptr);
}
sqlite3_progress_handler(db, 0, nullptr, nullptr); sqlite3_progress_handler(db, 0, nullptr, nullptr);
} }
// Copy failed sqlite3_finalize(stmt);
if (rc != SQLITE_OK) stmt = nullptr;
// VACUUMing failed
if (rc != SQLITE_DONE)
{ {
SetDBError( SetDBError(
XO("Failed to copy project file") XO("Project file copy failed")
); );
wxRemoveFile(destpath);
return nullptr; return nullptr;
} }
// Open the newly created database sqlite3 *destdb = nullptr;
rc = sqlite3_open(destpath, &destdb); rc = sqlite3_open(destpath, &destdb);
if (rc != SQLITE_OK) if (rc != SQLITE_OK)
{ {
@ -872,12 +808,13 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
XO("Failed to open copy of project file") XO("Failed to open copy of project file")
); );
sqlite3_close(destdb);
wxRemoveFile(destpath);
return nullptr; return nullptr;
} }
// Tell cleanup everything is good to go
success = true;
return destdb; return destdb;
} }
@ -1454,6 +1391,14 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName)
return false; 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 // Reaching this point defines success and all the rest are no-fail
// operations: // operations:
@ -1508,6 +1453,14 @@ bool ProjectFileIO::SaveCopy(const FilePath& fileName)
return false; 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 // Tell the finally block to behave
success = true; success = true;

View File

@ -150,17 +150,15 @@ private:
bool GetBlob(const char *sql, wxMemoryBuffer &buffer); bool GetBlob(const char *sql, wxMemoryBuffer &buffer);
bool CheckVersion(); bool CheckVersion();
bool InstallSchema(sqlite3 *db, const char *dbname = "main"); bool InstallSchema();
bool UpgradeSchema(); bool UpgradeSchema();
// Write project or autosave XML (binary) documents // 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, sqlite3 *db = nullptr);
// Application defined function to verify blockid exists is in set of blockids
using BlockIDs = std::set<SampleBlockID>;
static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv);
// Checks for orphan blocks. This will go away at a future date // Checks for orphan blocks. This will go away at a future date
using BlockIDs = std::set<SampleBlockID>;
static void isorphan(sqlite3_context *context, int argc, sqlite3_value **argv);
bool CheckForOrphans(BlockIDs &blockids); bool CheckForOrphans(BlockIDs &blockids);
// Return a database connection if successful, which caller must close // Return a database connection if successful, which caller must close

View File

@ -599,9 +599,10 @@ bool ProjectFileManager::SaveCopy(const FilePath &fileName /* = wxT("") */)
if (!projectFileIO.SaveCopy(filename.GetFullPath())) if (!projectFileIO.SaveCopy(filename.GetFullPath()))
{ {
// Overwrite disallowed. The destination project is open in another window.
AudacityMessageDialog m( AudacityMessageDialog m(
nullptr, nullptr,
XO("The project was not saved. See Help->Diagnostics->Show Log for more info."), XO("The project will not saved because the selected project is open in another window.\nPlease try again and select an original name."),
XO("Error Saving Project"), XO("Error Saving Project"),
wxOK | wxICON_ERROR); wxOK | wxICON_ERROR);

View File

@ -705,7 +705,7 @@ bool AUPImportFileHandle::HandleProject(XMLTagHandler *&handler)
if (projName.empty()) if (projName.empty())
{ {
AudacityMessageBox( AudacityMessageBox(
XO("Couldn't find the project data folder: \"%s\"").Format(value), XO("Couldn't find the project data folder: \"%s\"").Format(*value),
XO("Error Opening Project"), XO("Error Opening Project"),
wxOK | wxCENTRE, wxOK | wxCENTRE,
&window); &window);