mirror of
https://github.com/cookiengineer/audacity
synced 2025-09-17 16:50:26 +02:00
AUP3: Reworked CopyTo() yet again
This time it has the potential to produce much smaller output files since it ONLY copies the active blocks and not all of the blocks related to undo history.
This commit is contained in:
parent
5ffff72d35
commit
e3c5563d35
@ -19,8 +19,10 @@ 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"
|
||||||
@ -41,10 +43,10 @@ static const int ProjectFileVersion = 1;
|
|||||||
// to sampleblocks.
|
// to sampleblocks.
|
||||||
|
|
||||||
static const char *ProjectFileSchema =
|
static const char *ProjectFileSchema =
|
||||||
"PRAGMA application_id = %d;"
|
"PRAGMA <dbname>.application_id = %d;"
|
||||||
"PRAGMA user_version = %d;"
|
"PRAGMA <dbname>.user_version = %d;"
|
||||||
"PRAGMA journal_mode = WAL;"
|
"PRAGMA <dbname>.journal_mode = WAL;"
|
||||||
"PRAGMA locking_mode = EXCLUSIVE;"
|
"PRAGMA <dbname>.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.
|
||||||
@ -58,7 +60,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 project"
|
"CREATE TABLE IF NOT EXISTS <dbname>.project"
|
||||||
"("
|
"("
|
||||||
" id INTEGER PRIMARY KEY,"
|
" id INTEGER PRIMARY KEY,"
|
||||||
" dict BLOB,"
|
" dict BLOB,"
|
||||||
@ -78,7 +80,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 autosave"
|
"CREATE TABLE IF NOT EXISTS <dbname>.autosave"
|
||||||
"("
|
"("
|
||||||
" id INTEGER PRIMARY KEY,"
|
" id INTEGER PRIMARY KEY,"
|
||||||
" dict BLOB,"
|
" dict BLOB,"
|
||||||
@ -87,7 +89,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 tags"
|
"CREATE TABLE IF NOT EXISTS <dbname>.tags"
|
||||||
"("
|
"("
|
||||||
" name TEXT,"
|
" name TEXT,"
|
||||||
" value BLOB"
|
" value BLOB"
|
||||||
@ -104,7 +106,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 sampleblocks"
|
"CREATE TABLE IF NOT EXISTS <dbname>.sampleblocks"
|
||||||
"("
|
"("
|
||||||
" blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
|
" blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
" sampleformat INTEGER,"
|
" sampleformat INTEGER,"
|
||||||
@ -609,7 +611,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();
|
return InstallSchema(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for our application ID
|
// Check for our application ID
|
||||||
@ -653,19 +655,15 @@ bool ProjectFileIO::CheckVersion()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProjectFileIO::InstallSchema()
|
bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *dbname /* = "main" */)
|
||||||
{
|
{
|
||||||
auto db = DB();
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
char sql[1024];
|
wxString sql;
|
||||||
sqlite3_snprintf(sizeof(sql),
|
sql.Printf(ProjectFileSchema, ProjectFileID, ProjectFileVersion);
|
||||||
sql,
|
sql.Replace("<dbname>", dbname);
|
||||||
ProjectFileSchema,
|
|
||||||
ProjectFileID,
|
|
||||||
ProjectFileVersion);
|
|
||||||
|
|
||||||
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
|
rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr);
|
||||||
if (rc != SQLITE_OK)
|
if (rc != SQLITE_OK)
|
||||||
{
|
{
|
||||||
SetDBError(
|
SetDBError(
|
||||||
@ -688,12 +686,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::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);
|
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)
|
||||||
@ -701,18 +699,25 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
|
|||||||
auto db = DB();
|
auto db = DB();
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
// Add our function that will verify blockid against the set of valid blockids
|
auto cleanup = finally([&]
|
||||||
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)
|
||||||
{
|
{
|
||||||
//asdfasdf
|
wxLogDebug(wxT("Unable to add 'inset' function"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all rows that are orphaned
|
// 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)
|
if (rc != SQLITE_OK)
|
||||||
{
|
{
|
||||||
|
wxLogWarning(XO("Cleanup of orphan blocks failed").Translation());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,32 +750,81 @@ 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;
|
||||||
|
|
||||||
sqlite3_stmt *stmt = nullptr;
|
// Cleanup in case things go awry
|
||||||
auto cleanup = finally([&]
|
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)
|
if (rc != SQLITE_OK)
|
||||||
{
|
{
|
||||||
SetDBError(
|
SetDBError(
|
||||||
XO("Unable to prepare project file command")
|
XO("Unable to add 'inset' function")
|
||||||
);
|
);
|
||||||
return nullptr;
|
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)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -780,27 +834,37 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
|
|||||||
|
|
||||||
sqlite3_progress_handler(db, 10, progress_callback, &progress);
|
sqlite3_progress_handler(db, 10, progress_callback, &progress);
|
||||||
|
|
||||||
rc = sqlite3_step(stmt);
|
rc = sqlite3_exec(db,
|
||||||
|
"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);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
// Copy failed
|
||||||
stmt = nullptr;
|
if (rc != SQLITE_OK)
|
||||||
|
|
||||||
// VACUUMing failed
|
|
||||||
if (rc != SQLITE_DONE)
|
|
||||||
{
|
{
|
||||||
SetDBError(
|
SetDBError(
|
||||||
XO("Project file copy failed")
|
XO("Failed to copy project file")
|
||||||
);
|
);
|
||||||
|
|
||||||
wxRemoveFile(destpath);
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3 *destdb = nullptr;
|
// Open the newly created database
|
||||||
rc = sqlite3_open(destpath, &destdb);
|
rc = sqlite3_open(destpath, &destdb);
|
||||||
if (rc != SQLITE_OK)
|
if (rc != SQLITE_OK)
|
||||||
{
|
{
|
||||||
@ -808,13 +872,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1391,14 +1454,6 @@ 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:
|
||||||
|
|
||||||
@ -1453,14 +1508,6 @@ 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;
|
||||||
|
|
||||||
|
@ -150,15 +150,17 @@ private:
|
|||||||
bool GetBlob(const char *sql, wxMemoryBuffer &buffer);
|
bool GetBlob(const char *sql, wxMemoryBuffer &buffer);
|
||||||
|
|
||||||
bool CheckVersion();
|
bool CheckVersion();
|
||||||
bool InstallSchema();
|
bool InstallSchema(sqlite3 *db, const char *dbname = "main");
|
||||||
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);
|
||||||
|
|
||||||
// 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<SampleBlockID>;
|
using BlockIDs = std::set<SampleBlockID>;
|
||||||
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);
|
bool CheckForOrphans(BlockIDs &blockids);
|
||||||
|
|
||||||
// Return a database connection if successful, which caller must close
|
// Return a database connection if successful, which caller must close
|
||||||
|
@ -599,10 +599,9 @@ 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 will not saved because the selected project is open in another window.\nPlease try again and select an original name."),
|
XO("The project was not saved. See Help->Diagnostics->Show Log for more info."),
|
||||||
XO("Error Saving Project"),
|
XO("Error Saving Project"),
|
||||||
wxOK | wxICON_ERROR);
|
wxOK | wxICON_ERROR);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user