1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 15:49:41 +02:00

AUP3: Mostly rework of CopyTo()

It now uses VACUUM INTO instead of the SQLite backup API
in hopes that the copies will be smaller. And VACUUM INTO
is "supposed" to be faster, but time will tell.  It's easy
to put the backup API usage back in.

This also fixes a bit I missed with redoing the orphan block
handling that was reported by Paul.

And finally, it renames the AutoRecovery.cpp/.h files and AutoSaveFile
class to ProjectSerializer since the AutoSaveFile class is being
used for regular project documents now and it doesn't write to a
file anymore.

If anyone has a better idea for a name other than ProjectSerializer
feel free to change it.  I hate naming things.
This commit is contained in:
Leland Lucius 2020-07-10 00:50:52 -05:00
parent 7ad8849d32
commit 5b41115bd0
7 changed files with 164 additions and 182 deletions

View File

@ -28,7 +28,6 @@ list( APPEND DEFINES
SQLITE_LIKE_DOESNT_MATCH_BLOBS
SQLITE_MAX_EXPR_DEPTH=0
SQLITE_OMIT_DEPRECATED
SQLITE_OMIT_PROGRESS_CALLBACK
SQLITE_OMIT_SHARED_CACHE
SQLITE_USE_ALLOCA
SQLITE_OMIT_AUTOINIT

View File

@ -99,7 +99,6 @@ It handles initialization and termination by subclassing wxApp.
#include "Theme.h"
#include "PlatformCompatibility.h"
#include "FileNames.h"
#include "AutoRecovery.h"
#include "AutoRecoveryDialog.h"
#include "SplashDialog.h"
#include "FFT.h"

View File

@ -14,10 +14,10 @@ Paul Licameli split from AudacityProject.cpp
#include <wx/crt.h>
#include <wx/frame.h>
#include "AutoRecovery.h"
#include "FileNames.h"
#include "Project.h"
#include "ProjectFileIORegistry.h"
#include "ProjectSerializer.h"
#include "ProjectSettings.h"
#include "Tags.h"
#include "ViewInfo.h"
@ -727,88 +727,94 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
return true;
}
static int progress_callback(void *data)
{
ProgressDialog *progress = (ProgressDialog *) data;
// No way to know how much time will be spent, so turn the
// ProgressDialog in to an elapsed timer.
//
// Would be nice if our ProgressDialog has a Cylon mode.
if (progress->Update(100000) != ProgressResult::Success)
{
return SQLITE_ABORT;
}
return SQLITE_OK;
}
sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath)
{
auto db = DB();
int rc;
ProgressResult res = ProgressResult::Success;
sqlite3 *destdb = nullptr;
/* Open the database file identified by zFilename. */
rc = sqlite3_open(destpath, &destdb);
if (rc == SQLITE_OK)
sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&]
{
bool success = true;
sqlite3_backup *backup = sqlite3_backup_init(destdb, "main", db, "main");
if (backup)
if (stmt)
{
/* i18n-hint: This title appears on a dialog that indicates the progress
in doing something.*/
ProgressDialog progress(XO("Progress"), XO("Saving project"));
do
{
// These two calls always return zero before any sqlite3_backup_step
// but that doesn't matter
int remaining = sqlite3_backup_remaining(backup);
int total = sqlite3_backup_pagecount(backup);
if (progress.Update(total - remaining, total) != ProgressResult::Success)
{
SetError(
XO("Copy process cancelled.")
);
success = false;
break;
}
rc = sqlite3_backup_step(backup, 12);
} while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);
// The return code from finish() will reflect errors from step()
rc = sqlite3_backup_finish(backup);
if (rc != SQLITE_OK)
{
SetDBError(
XO("The copy process failed for:\n\n%s").Format(destpath)
);
success = false;
}
}
else
{
SetDBError(
XO("Unable to initiate the backup process.")
);
success = false;
sqlite3_finalize(stmt);
}
});
if (!success)
{
// Don't give this DB connection back to the caller
rc = sqlite3_close(destdb);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to successfully close the destination project file:\n\n%s")
);
}
wxRemoveFile(destpath);
return nullptr;
}
}
else
rc = sqlite3_prepare_v2(db, "VACUUM INTO ?;", -1, &stmt, 0);
if (rc != SQLITE_OK)
{
// sqlite3 docs say you should close anyway to avoid leaks
sqlite3_close( destdb );
SetDBError(
XO("Unable to open the destination project file:\n\n%s").Format(destpath)
XO("Unable to prepare project file command")
);
return nullptr;
}
// Let the caller use this connection and close it later
rc = sqlite3_bind_text(stmt, 1, destpath.mb_str().data(), destpath.mb_str().length(), SQLITE_STATIC);
if (rc != SQLITE_OK)
{
THROW_INCONSISTENCY_EXCEPTION;
}
{
/* i18n-hint: This title appears on a dialog that indicates the progress
in doing something.*/
ProgressDialog progress(XO("Progress"), XO("Saving project"));
sqlite3_progress_handler(db, 10, progress_callback, &progress);
rc = sqlite3_step(stmt);
sqlite3_progress_handler(db, 0, nullptr, nullptr);
}
sqlite3_finalize(stmt);
stmt = nullptr;
// VACUUMing failed
if (rc != SQLITE_DONE)
{
SetDBError(
XO("Project file copy failed")
);
wxRemoveFile(destpath);
return nullptr;
}
sqlite3 *destdb = nullptr;
rc = sqlite3_open(destpath, &destdb);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to open copy of project file")
);
sqlite3_close(destdb);
wxRemoveFile(destpath);
return nullptr;
}
return destdb;
}
@ -1108,7 +1114,7 @@ void ProjectFileIO::WriteXML(XMLWriter &xmlFile, const WaveTrackArray *tracks)
bool ProjectFileIO::AutoSave(const WaveTrackArray *tracks)
{
AutoSaveFile autosave;
ProjectSerializer autosave;
WriteXMLHeader(autosave);
WriteXML(autosave, tracks);
@ -1121,11 +1127,15 @@ bool ProjectFileIO::AutoSave(const WaveTrackArray *tracks)
return false;
}
bool ProjectFileIO::AutoSaveDelete()
bool ProjectFileIO::AutoSaveDelete(sqlite3 *db /* = nullptr */)
{
auto db = DB();
int rc;
if (!db)
{
db = DB();
}
rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
@ -1138,20 +1148,25 @@ bool ProjectFileIO::AutoSaveDelete()
return true;
}
bool ProjectFileIO::WriteDoc(const char *table, const AutoSaveFile &autosave)
bool ProjectFileIO::WriteDoc(const char *table,
const ProjectSerializer &autosave,
sqlite3 *db /* = nullptr */)
{
auto db = DB();
int rc;
if (!db)
{
db = DB();
}
// For now, we always use an ID of 1. This will replace the previously
// writen row every time.
char sql[256];
sqlite3_snprintf(sizeof(sql),
sql,
"INSERT INTO %s(id, dict, doc) VALUES(1, ?1, ?2)"
" ON CONFLICT(id) DO UPDATE SET %sdoc = ?2;",
table,
autosave.DictChanged() ? "dict = ?1, " : "");
" ON CONFLICT(id) DO UPDATE SET dict = ?1, doc = ?2;",
table);
sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&]
@ -1233,7 +1248,7 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
}
if (buffer.GetDataLen() > 0)
{
project = AutoSaveFile::Decode(buffer, blockids);
project = ProjectSerializer::Decode(buffer, blockids);
}
if (!GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer))
@ -1243,7 +1258,7 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
}
if (buffer.GetDataLen() > 0)
{
autosave = AutoSaveFile::Decode(buffer, blockids);
autosave = ProjectSerializer::Decode(buffer, blockids);
}
// Should this be an error???
@ -1362,10 +1377,7 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName)
UseConnection( newDB, fileName );
}
auto db = DB();
int rc;
AutoSaveFile doc;
ProjectSerializer doc;
WriteXMLHeader(doc);
WriteXML(doc);
@ -1411,7 +1423,6 @@ bool ProjectFileIO::SaveCopy(const FilePath& fileName)
return false;
}
int rc;
bool success = false;
auto cleanup = finally([&]
@ -1427,64 +1438,21 @@ bool ProjectFileIO::SaveCopy(const FilePath& fileName)
}
});
XMLStringWriter doc;
ProjectSerializer doc;
WriteXMLHeader(doc);
WriteXML(doc);
// Always use an ID of 1. This will replace any existing row.
char sql[256];
sqlite3_snprintf(sizeof(sql),
sql,
"INSERT INTO project(id, doc) VALUES(1, ?1)"
" ON CONFLICT(id) DO UPDATE SET doc = ?1;");
// Write the project doc to the new DB
if (!WriteDoc("project", doc, db))
{
sqlite3_stmt* stmt = nullptr;
auto finalize = finally([&]
{
if (stmt)
{
// This will free the statement
sqlite3_finalize(stmt);
}
});
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to prepare project file command:\n\n%s").Format(sql)
);
return false;
}
// BIND SQL project
rc = sqlite3_bind_text(stmt, 1, doc, -1, SQLITE_STATIC);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to bind to project file document.")
);
return false;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
SetDBError(
XO("Failed to save project file information.")
);
return false;
}
return false;
}
rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
// 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))
{
SetDBError(
XO("Failed to remove the autosave information from the project file.")
);
return false;
}

View File

@ -23,7 +23,7 @@ struct sqlite3_value;
class AudacityProject;
class AutoCommitTransaction;
class AutoSaveFile;
class ProjectSerializer;
class SqliteSampleBlock;
class WaveTrack;
@ -73,7 +73,7 @@ public:
void Reset();
bool AutoSave(const WaveTrackArray *tracks = nullptr);
bool AutoSaveDelete();
bool AutoSaveDelete(sqlite3 *db = nullptr);
bool LoadProject(const FilePath &fileName);
bool SaveProject(const FilePath &fileName);
@ -118,6 +118,8 @@ private:
static int ExecCallback(void *data, int cols, char **vals, char **names);
int Exec(const char *query, ExecCB callback, wxString *result);
static int ProgressCallback(void *data);
// The opening of the database may be delayed until demanded.
// Returns a non-null pointer to an open database, or throws an exception
// if opening fails.
@ -152,7 +154,7 @@ private:
bool UpgradeSchema();
// Write project or autosave XML (binary) documents
bool WriteDoc(const char *table, const AutoSaveFile &autosave);
bool WriteDoc(const char *table, const ProjectSerializer &autosave, sqlite3 *db = nullptr);
// Checks for orphan blocks. This will go away at a future date
using BlockIDs = std::set<SampleBlockID>;

View File

@ -4,26 +4,25 @@
Audacity(R) is copyright (c) 1999-2010 Audacity Team.
License: GPL v2. See License.txt.
AutoRecovery.cpp
ProjectSerializer.cpp
*******************************************************************//**
\class AutoSaveFile
\brief a class wrapping reading and writing of arbitrary data in
text or binary format to a file.
\class ProjectSerializer
\brief a class used to (de)serialize the project catalog
*//********************************************************************/
#include "Audacity.h"
#include "AutoRecovery.h"
#include "ProjectSerializer.h"
#include <wx/ustring.h>
///
/// AutoSaveFile class
/// ProjectSerializer class
///
// Simple "binary xml" format used exclusively for autosave documents.
// Simple "binary xml" format used exclusively for project documents.
//
// It is not intended that the user view or modify the file.
//
@ -75,17 +74,17 @@ enum FieldTypes
// is writen and then the envelope is later removed, the dict will still
// contain the envelope name, but that's not a problem.
NameMap AutoSaveFile::mNames;
wxMemoryBuffer AutoSaveFile::mDict;
NameMap ProjectSerializer::mNames;
wxMemoryBuffer ProjectSerializer::mDict;
TranslatableString AutoSaveFile::FailureMessage( const FilePath &/*filePath*/ )
TranslatableString ProjectSerializer::FailureMessage( const FilePath &/*filePath*/ )
{
return
XO("This recovery file was saved by Audacity 2.3.0 or before.\n"
"You need to run that version of Audacity to recover the project." );
}
AutoSaveFile::AutoSaveFile(size_t allocSize)
ProjectSerializer::ProjectSerializer(size_t allocSize)
{
mDict.SetBufSize(allocSize);
mBuffer.SetBufSize(allocSize);
@ -99,28 +98,28 @@ AutoSaveFile::AutoSaveFile(size_t allocSize)
mDictChanged = false;
}
AutoSaveFile::~AutoSaveFile()
ProjectSerializer::~ProjectSerializer()
{
}
void AutoSaveFile::StartTag(const wxString & name)
void ProjectSerializer::StartTag(const wxString & name)
{
mBuffer.AppendByte(FT_StartTag);
WriteName(name);
}
void AutoSaveFile::EndTag(const wxString & name)
void ProjectSerializer::EndTag(const wxString & name)
{
mBuffer.AppendByte(FT_EndTag);
WriteName(name);
}
void AutoSaveFile::WriteAttr(const wxString & name, const wxChar *value)
void ProjectSerializer::WriteAttr(const wxString & name, const wxChar *value)
{
WriteAttr(name, wxString(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, const wxString & value)
void ProjectSerializer::WriteAttr(const wxString & name, const wxString & value)
{
mBuffer.AppendByte(FT_String);
WriteName(name);
@ -131,7 +130,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, const wxString & value)
mBuffer.AppendData(value.wx_str(), len);
}
void AutoSaveFile::WriteAttr(const wxString & name, int value)
void ProjectSerializer::WriteAttr(const wxString & name, int value)
{
mBuffer.AppendByte(FT_Int);
WriteName(name);
@ -139,7 +138,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, int value)
mBuffer.AppendData(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, bool value)
void ProjectSerializer::WriteAttr(const wxString & name, bool value)
{
mBuffer.AppendByte(FT_Bool);
WriteName(name);
@ -147,7 +146,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, bool value)
mBuffer.AppendData(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, long value)
void ProjectSerializer::WriteAttr(const wxString & name, long value)
{
mBuffer.AppendByte(FT_Long);
WriteName(name);
@ -155,7 +154,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, long value)
mBuffer.AppendData(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, long long value)
void ProjectSerializer::WriteAttr(const wxString & name, long long value)
{
mBuffer.AppendByte(FT_LongLong);
WriteName(name);
@ -163,7 +162,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, long long value)
mBuffer.AppendData(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, size_t value)
void ProjectSerializer::WriteAttr(const wxString & name, size_t value)
{
mBuffer.AppendByte(FT_SizeT);
WriteName(name);
@ -171,7 +170,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, size_t value)
mBuffer.AppendData(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, float value, int digits)
void ProjectSerializer::WriteAttr(const wxString & name, float value, int digits)
{
mBuffer.AppendByte(FT_Float);
WriteName(name);
@ -180,7 +179,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, float value, int digits)
mBuffer.AppendData(&digits, sizeof(digits));
}
void AutoSaveFile::WriteAttr(const wxString & name, double value, int digits)
void ProjectSerializer::WriteAttr(const wxString & name, double value, int digits)
{
mBuffer.AppendByte(FT_Double);
WriteName(name);
@ -189,7 +188,7 @@ void AutoSaveFile::WriteAttr(const wxString & name, double value, int digits)
mBuffer.AppendData(&digits, sizeof(digits));
}
void AutoSaveFile::WriteData(const wxString & value)
void ProjectSerializer::WriteData(const wxString & value)
{
mBuffer.AppendByte(FT_Data);
@ -199,7 +198,7 @@ void AutoSaveFile::WriteData(const wxString & value)
mBuffer.AppendData(value.wx_str(), len);
}
void AutoSaveFile::Write(const wxString & value)
void ProjectSerializer::Write(const wxString & value)
{
mBuffer.AppendByte(FT_Raw);
@ -209,7 +208,7 @@ void AutoSaveFile::Write(const wxString & value)
mBuffer.AppendData(value.wx_str(), len);
}
void AutoSaveFile::WriteSubTree(const AutoSaveFile & value)
void ProjectSerializer::WriteSubTree(const ProjectSerializer & value)
{
mBuffer.AppendByte(FT_Push);
@ -219,7 +218,7 @@ void AutoSaveFile::WriteSubTree(const AutoSaveFile & value)
mBuffer.AppendByte(FT_Pop);
}
void AutoSaveFile::WriteName(const wxString & name)
void ProjectSerializer::WriteName(const wxString & name)
{
wxASSERT(name.length() * sizeof(wxChar) <= SHRT_MAX);
short id;
@ -247,28 +246,28 @@ void AutoSaveFile::WriteName(const wxString & name)
mBuffer.AppendData(&id, sizeof(id));
}
const wxMemoryBuffer &AutoSaveFile::GetDict() const
const wxMemoryBuffer &ProjectSerializer::GetDict() const
{
return mDict;
}
const wxMemoryBuffer &AutoSaveFile::GetData() const
const wxMemoryBuffer &ProjectSerializer::GetData() const
{
return mBuffer;
}
bool AutoSaveFile::IsEmpty() const
bool ProjectSerializer::IsEmpty() const
{
return mBuffer.GetDataLen() == 0;
}
bool AutoSaveFile::DictChanged() const
bool ProjectSerializer::DictChanged() const
{
return mDictChanged;
}
// See ProjectFileIO::CheckForOrphans() for explanation of the blockids arg
wxString AutoSaveFile::Decode(const wxMemoryBuffer &buffer, BlockIDs &blockids)
wxString ProjectSerializer::Decode(const wxMemoryBuffer &buffer, BlockIDs &blockids)
{
wxMemoryInputStream in(buffer.GetData(), buffer.GetDataLen());
@ -449,8 +448,8 @@ wxString AutoSaveFile::Decode(const wxMemoryBuffer &buffer, BlockIDs &blockids)
in.Read(&val, sizeof(val));
// Look for and save the "blockid" values to support orphan
// block checking. This should be removed once autosave and
// related blocks become part of the same transaction.
// block checking. This should be removed once serialization
// and related blocks become part of the same transaction.
const wxString &name = Lookup(id);
if (name.IsSameAs(wxT("blockid")))
{
@ -510,7 +509,7 @@ wxString AutoSaveFile::Decode(const wxMemoryBuffer &buffer, BlockIDs &blockids)
}
catch( const Error& )
{
// Autosave was corrupt, or platform differences in size or endianness
// Document was corrupt, or platform differences in size or endianness
// were not well canonicalized
return {};
}

View File

@ -4,12 +4,12 @@
Audacity(R) is copyright (c) 1999-2010 Audacity Team.
License: GPL v2. See License.txt.
AutoRecovery.h
ProjectSerializer.h
*******************************************************************/
#ifndef __AUDACITY_AUTORECOVERY__
#define __AUDACITY_AUTORECOVERY__
#ifndef __AUDACITY_PROJECTSERIALIZER__
#define __AUDACITY_PROJECTSERIALIZER__
#include "xml/XMLTagHandler.h"
@ -26,21 +26,21 @@ using SampleBlockID = long long;
using BlockIDs = std::set<SampleBlockID>;
///
/// AutoSaveFile
/// ProjectSerializer
///
using NameMap = std::unordered_map<wxString, short>;
using IdMap = std::unordered_map<short, wxString>;
// This class's overrides do NOT throw AudacityException.
class AUDACITY_DLL_API AutoSaveFile final : public XMLWriter
class AUDACITY_DLL_API ProjectSerializer final : public XMLWriter
{
public:
static TranslatableString FailureMessage( const FilePath &filePath );
AutoSaveFile(size_t allocSize = 1024 * 1024);
virtual ~AutoSaveFile();
ProjectSerializer(size_t allocSize = 1024 * 1024);
virtual ~ProjectSerializer();
void StartTag(const wxString & name) override;
void EndTag(const wxString & name) override;
@ -60,7 +60,7 @@ public:
void Write(const wxString & data) override;
// Non-override functions
void WriteSubTree(const AutoSaveFile & value);
void WriteSubTree(const ProjectSerializer & value);
const wxMemoryBuffer &GetDict() const;
const wxMemoryBuffer &GetData() const;

View File

@ -31,6 +31,7 @@
#include "../Mix.h"
#include "../PluginManager.h"
#include "../ProjectAudioManager.h"
#include "../ProjectFileIO.h"
#include "../ProjectSettings.h"
#include "../ShuttleGui.h"
#include "../Shuttle.h"
@ -1316,7 +1317,21 @@ bool Effect::DoEffect(double projectRate,
};
auto vr = valueRestorer( mProgress, &progress );
returnVal = Process();
{
// This is for performance purposes only.
#if 0
auto &pProject = *const_cast<AudacityProject*>(FindProject()); // how to remove this const_cast?
auto &pIO = ProjectFileIO::Get(pProject);
AutoCommitTransaction trans(pIO, "Effect");
#endif
returnVal = Process();
#if 0
if (!returnVal)
{
trans.Rollback();
}
#endif
}
}
if (returnVal && (mT1 >= mT0 ))