audacia/src/ProjectFileIO.cpp

1304 lines
30 KiB
C++
Raw Normal View History

2019-05-29 15:45:19 +00:00
/**********************************************************************
Audacity: A Digital Audio Editor
ProjectFileIO.cpp
Paul Licameli split from AudacityProject.cpp
**********************************************************************/
#include "ProjectFileIO.h"
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
#include <sqlite3.h>
#include <wx/crt.h>
#include <wx/frame.h>
2019-05-29 15:45:19 +00:00
#include "AutoRecovery.h"
#include "FileNames.h"
#include "Project.h"
#include "ProjectFileIORegistry.h"
#include "ProjectSettings.h"
#include "Tags.h"
#include "ViewInfo.h"
#include "WaveTrack.h"
#include "widgets/AudacityMessageBox.h"
#include "widgets/NumericTextCtrl.h"
#include "widgets/ProgressDialog.h"
#include "xml/XMLFileReader.h"
2019-05-29 15:45:19 +00:00
wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent);
static const int ProjectFileID = ('A' << 24 | 'U' << 16 | 'D' << 8 | 'Y');
static const int ProjectFileVersion = 1;
static const int ProjectFilePageSize = 4096;
2020-07-01 13:55:06 +00:00
// Navigation:
//
// Bindings are marked out in the code by, e.g.
// BIND SQL sampleblocks
// A search for "BIND SQL" will find all bindings.
// A search for "SQL sampleblocks" will find all SQL related
// to sampleblocks.
static const char *ProjectFileSchema =
"PRAGMA application_id = %d;"
"PRAGMA user_version = %d;"
"PRAGMA page_size = %d;"
"PRAGMA journal_mode = DELETE;"
""
2020-07-01 13:55:06 +00:00
// CREATE SQL project
// doc is a variable sized XML text string.
// it is the former Audacity .aup file
// One instance only.
"CREATE TABLE IF NOT EXISTS project"
"("
" id INTEGER PRIMARY KEY,"
" doc TEXT"
");"
""
2020-07-01 13:55:06 +00:00
// CREATE SQL autosave
// autosave is a binary representation of an XML file.
// it's in binary for speed.
// One instance only. id is always 1.
// dict is a dictionary of fieldnames.
// doc is the binary representation of the XML
// in the doc, fieldnames are replaced by 2 byte dictionary
// index numbers.
// This is all opaque to SQLite. It just sees two
// big binary blobs.
// 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"
"("
" id INTEGER PRIMARY KEY,"
" dict BLOB,"
" doc BLOB"
");"
""
2020-07-01 13:55:06 +00:00
// CREATE SQL tags
// tags is not used (yet)
"CREATE TABLE IF NOT EXISTS tags"
"("
" name TEXT,"
" value BLOB"
");"
""
2020-07-01 13:55:06 +00:00
// CREATE SQL sampleblocks
// 'samples' are fixed size blocks of int16, int32 or float32 numbers.
2020-07-01 13:55:06 +00:00
// The blocks may be partially empty.
// The quantity of valid data in the blocks is
// provided in the project XML.
//
// sampleformat specifies the format of the samples stored.
2020-07-01 13:55:06 +00:00
//
// blockID is a 64 bit number.
//
// summin to summary64K are summaries at 3 distance scales.
"CREATE TABLE IF NOT EXISTS sampleblocks"
"("
" blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
" sampleformat INTEGER,"
" summin REAL,"
" summax REAL,"
" sumrms REAL,"
" summary256 BLOB,"
" summary64k BLOB,"
" samples BLOB"
");";
// This singleton handles initialization/shutdown of the SQLite library.
// It is needed because our local SQLite is built with SQLITE_OMIT_AUTOINIT
// defined.
//
// It's safe to use even if a system version of SQLite is used that didn't
// have SQLITE_OMIT_AUTOINIT defined.
class SQLiteIniter
{
public:
SQLiteIniter()
{
sqlite3_initialize();
}
~SQLiteIniter()
{
sqlite3_shutdown();
}
};
static SQLiteIniter sqliteIniter;
static void RefreshAllTitles(bool bShowProjectNumbers )
{
for ( auto pProject : AllProjects{} ) {
if ( !GetProjectFrame( *pProject ).IsIconized() ) {
ProjectFileIO::Get( *pProject ).SetProjectTitle(
bShowProjectNumbers ? pProject->GetProjectNumber() : -1 );
}
}
}
TitleRestorer::TitleRestorer(
wxTopLevelWindow &window, AudacityProject &project )
{
if( window.IsIconized() )
window.Restore();
window.Raise(); // May help identifying the window on Mac
// Construct this project's name and number.
sProjName = project.GetProjectName();
if ( sProjName.empty() ) {
sProjName = _("<untitled>");
UnnamedCount = std::count_if(
AllProjects{}.begin(), AllProjects{}.end(),
[]( const AllProjects::value_type &ptr ){
return ptr->GetProjectName().empty();
}
);
if ( UnnamedCount > 1 ) {
sProjNumber.Printf(
2020-04-14 14:48:08 +00:00
_("[Project %02i] "), project.GetProjectNumber() + 1 );
RefreshAllTitles( true );
}
}
else
UnnamedCount = 0;
}
TitleRestorer::~TitleRestorer() {
if( UnnamedCount > 1 )
RefreshAllTitles( false );
}
2019-05-29 15:45:19 +00:00
static const AudacityProject::AttachedObjects::RegisteredFactory sFileIOKey{
[]( AudacityProject &parent ){
auto result = std::make_shared< ProjectFileIO >( parent );
return result;
2019-05-29 15:45:19 +00:00
}
};
ProjectFileIO &ProjectFileIO::Get( AudacityProject &project )
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto &result = project.AttachedObjects::Get< ProjectFileIO >( sFileIOKey );
return result;
2019-05-29 15:45:19 +00:00
}
const ProjectFileIO &ProjectFileIO::Get( const AudacityProject &project )
{
return Get( const_cast< AudacityProject & >( project ) );
}
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
ProjectFileIO::ProjectFileIO(AudacityProject &)
2019-05-29 15:45:19 +00:00
{
mDB = nullptr;
mRecovered = false;
mModified = false;
mTemporary = true;
mBypass = false;
2019-05-29 15:45:19 +00:00
UpdatePrefs();
}
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
void ProjectFileIO::Init( AudacityProject &project )
{
// This step can't happen in the ctor of ProjectFileIO because ctor of
// AudacityProject wasn't complete
mpProject = project.shared_from_this();
}
ProjectFileIO::~ProjectFileIO()
{
if (mDB)
{
// Save the filename since CloseDB() will clear it
wxString filename = mFileName;
// Not much we can do if this fails. The user will simply get
// the recovery dialog upon next restart.
if (CloseDB())
{
// Always remove the journal now that the DB is closed
wxRemoveFile(filename + wxT("-journal"));
// At this point, we are shutting down cleanly and if the project file is
// still in the temp directory it means that the user has chosen not to
// save it. So, delete it.
if (mTemporary)
{
wxFileName temp(FileNames::TempDir());
if (temp == wxPathOnly(filename))
{
wxRemoveFile(filename);
}
}
}
}
}
sqlite3 *ProjectFileIO::DB()
{
if (mDB)
{
return mDB;
}
return OpenDB();
}
sqlite3 *ProjectFileIO::OpenDB(FilePath fileName)
{
wxASSERT(mDB == nullptr);
bool temp = false;
if (fileName.empty())
{
fileName = GetFileName();
if (fileName.empty())
{
fileName = FileNames::UnsavedProjectFileName();
temp = true;
}
else
{
temp = false;
}
}
int rc = sqlite3_open(fileName, &mDB);
if (rc != SQLITE_OK)
{
SetDBError(XO("Failed to open project file"));
return nullptr;
}
if (!CheckVersion())
{
CloseDB();
return nullptr;
}
mTemporary = temp;
SetFileName(fileName);
return mDB;
}
bool ProjectFileIO::CloseDB()
{
int rc;
if (mDB)
{
rc = sqlite3_close(mDB);
if (rc != SQLITE_OK)
{
SetDBError(XO("Failed to close the project file"));
}
else
{
wxRemoveFile(mFileName + wxT("-journal"));
}
mDB = nullptr;
SetFileName({});
}
return true;
}
bool ProjectFileIO::DeleteDB()
{
wxASSERT(mDB == nullptr);
if (mTemporary && !mFileName.empty())
{
wxFileName temp(FileNames::TempDir());
if (temp == wxPathOnly(mFileName))
{
if (!wxRemoveFile(mFileName))
{
SetError(XO("Failed to close the project file"));
return false;
}
wxRemoveFile(mFileName + wxT("-journal"));
}
}
return true;
}
bool ProjectFileIO::CleanDB()
{
auto db = DB();
int rc;
AutoSave();
wxString destpath = wxFileName::CreateTempFileName(mFileName + ".xxxxx");
if (CopyTo(destpath))
{
if (CloseDB())
{
// This can be removed even if we fail below since the DB is closed
wxRemoveFile(mFileName + wxT("-journal"));
wxString tmppath = wxFileName::CreateTempFileName(mFileName + ".xxxxx");
if (wxRename(mFileName, tmppath) == 0)
{
if (wxRename(destpath, mFileName) == 0)
{
wxRemoveFile(tmppath);
// Success
return true;
}
if (wxRename(tmppath, mFileName) == 0)
{
wxRemoveFile(destpath);
}
else
{
SetError(XO("Could not rename %s back to %s during cleaning").Format(tmppath, mFileName));
}
}
else
{
SetError(XO("Could not rename %s during cleaning").Format(mFileName));
}
}
}
// AUD3 TODO COMPILAIN
return false;
}
/* static */
int ProjectFileIO::ExecCallback(void *data, int cols, char **vals, char **names)
{
ExecParm *parms = static_cast<ExecParm *>(data);
return parms->func(parms->result, cols, vals, names);
}
int ProjectFileIO::Exec(const char *query, ExecCB callback, wxString *result)
{
char *errmsg = nullptr;
ExecParm ep = {callback, result};
int rc = sqlite3_exec(DB(), query, ExecCallback, &ep, &errmsg);
if (errmsg)
{
SetDBError(
XO("Failed to execute a project file command:\n\n%s").Format(query)
);
mLibraryError = Verbatim(errmsg);
sqlite3_free(errmsg);
}
return rc;
}
wxString ProjectFileIO::GetValue(const char *sql)
{
auto getresult = [&](wxString *result, int cols, char **vals, char **names)
{
if (cols == 1 && vals[0])
{
result->append(vals[0]);
return SQLITE_OK;
}
return SQLITE_ABORT;
};
wxString value;
int rc = Exec(sql, getresult, &value);
if (rc != SQLITE_OK)
{
// Message already captured
}
return value;
}
bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
{
auto db = DB();
int rc;
sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&]
{
if (stmt)
{
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;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW)
{
SetDBError(
XO("Failed to retrieve data from the project file.\nThe following command failed:\n\n%s").Format(sql)
);
// AUD TODO handle error
return false;
}
const void *blob = sqlite3_column_blob(stmt, 0);
int size = sqlite3_column_bytes(stmt, 0);
buffer.AppendData(blob, size);
return true;
}
bool ProjectFileIO::CheckVersion()
{
auto db = DB();
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)
{
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)
{
return InstallSchema();
}
// Check for our application ID
long appid = -1;
GetValue("PRAGMA application_ID;").ToLong(&appid);
if (appid == -1)
{
return false;
}
// It's a database that SQLite recognizes, but it's not one of ours
if (appid != 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)
{
return false;
}
// Project file version is higher than ours. We will refuse to
// process it since we can't trust anything about it.
if (version > ProjectFileVersion)
{
SetError(
XO("This project was created with a newer version of Audacity:\n\nYou will need to upgrade to process it")
);
return false;
}
// Project file is older than ours, ask the user if it's okay to
// upgrade.
if (version < ProjectFileVersion)
{
return UpgradeSchema();
}
return true;
}
bool ProjectFileIO::InstallSchema()
{
auto db = DB();
int rc;
char sql[1024];
sqlite3_snprintf(sizeof(sql),
sql,
ProjectFileSchema,
ProjectFileID,
ProjectFileVersion,
ProjectFilePageSize);
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Unable to initialize the project file")
);
return false;
}
return true;
}
bool ProjectFileIO::UpgradeSchema()
{
return true;
}
bool ProjectFileIO::CopyTo(const FilePath &destpath)
{
auto db = DB();
int rc;
bool success = true;
ProgressResult res = ProgressResult::Success;
sqlite3 *destdb = nullptr;
/* Open the database file identified by zFilename. */
rc = sqlite3_open(destpath, &destdb);
if (rc == SQLITE_OK)
{
sqlite3_backup *backup = sqlite3_backup_init(destdb, "main", db, "main");
if (backup)
{
/* i18n-hint: This title appears on a dialog that indicates the progress
in doing something.*/
ProgressDialog progress(XO("Progress"), XO("Saving project"));
do
{
int remaining = sqlite3_backup_remaining(backup);
int total = sqlite3_backup_pagecount(backup);
if (progress.Update(total - remaining, total) != ProgressResult::Success)
{
SetError(
XO("Copy processs 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.")
);
}
// Close the DB
rc = sqlite3_close(destdb);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to successfully close the destination project file:\n\n%s")
);
success = false;
}
// Always delete the journal
wxRemoveFile(destpath + wxT("-journal"));
if (!success)
{
wxRemoveFile(destpath);
}
}
else
{
SetDBError(
XO("Unable to open the destination project file:\n\n%s").Format(destpath)
);
success = false;
}
return success;
}
2019-05-29 15:45:19 +00:00
void ProjectFileIO::UpdatePrefs()
{
SetProjectTitle();
}
// Pass a number in to show project number, or -1 not to.
void ProjectFileIO::SetProjectTitle(int number)
2019-05-29 15:45:19 +00:00
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto pProject = mpProject.lock();
if (! pProject )
return;
auto &project = *pProject;
auto pWindow = project.GetFrame();
if (!pWindow)
{
return;
}
auto &window = *pWindow;
2019-05-29 15:45:19 +00:00
wxString name = project.GetProjectName();
// If we are showing project numbers, then we also explicitly show "<untitled>" if there
// is none.
if (number >= 0)
{
2019-05-29 15:45:19 +00:00
/* i18n-hint: The %02i is the project number, the %s is the project name.*/
name = wxString::Format(_("[Project %02i] Audacity \"%s\""), number + 1,
name.empty() ? "<untitled>" : (const char *)name);
2019-05-29 15:45:19 +00:00
}
// If we are not showing numbers, then <untitled> shows as 'Audacity'.
else if (name.empty())
2019-05-29 15:45:19 +00:00
{
name = _TS("Audacity");
}
if (mRecovered)
2019-05-29 15:45:19 +00:00
{
name += wxT(" ");
/* i18n-hint: E.g this is recovered audio that had been lost.*/
name += _("(Recovered)");
}
if (name != window.GetTitle())
{
window.SetTitle( name );
window.SetName(name); // to make the nvda screen reader read the correct title
project.QueueEvent(
safenew wxCommandEvent{ EVT_PROJECT_TITLE_CHANGE } );
}
2019-05-29 15:45:19 +00:00
}
const FilePath &ProjectFileIO::GetFileName() const
{
return mFileName;
}
void ProjectFileIO::SetFileName(const FilePath &fileName)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto pProject = mpProject.lock();
if (! pProject )
return;
auto &project = *pProject;
mFileName = fileName;
if (mTemporary)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
project.SetProjectName({});
}
else
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
project.SetProjectName(wxFileName(mFileName).GetName());
}
SetProjectTitle();
}
2019-05-29 15:45:19 +00:00
bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto pProject = mpProject.lock();
if (! pProject )
return false;
auto &project = *pProject;
auto &window = GetProjectFrame(project);
auto &viewInfo = ViewInfo::Get(project);
auto &settings = ProjectSettings::Get(project);
wxString fileVersion;
wxString audacityVersion;
2019-05-29 15:45:19 +00:00
int requiredTags = 0;
long longVpos = 0;
// loop through attrs, which is a null-terminated list of
// attribute-value pairs
while (*attrs)
{
2019-05-29 15:45:19 +00:00
const wxChar *attr = *attrs++;
const wxChar *value = *attrs++;
if (!value || !XMLValueChecker::IsGoodString(value))
{
2019-05-29 15:45:19 +00:00
break;
}
2019-05-29 15:45:19 +00:00
if (viewInfo.ReadXMLAttribute(attr, value))
{
2019-05-29 15:45:19 +00:00
// We need to save vpos now and restore it below
longVpos = std::max(longVpos, long(viewInfo.vpos));
continue;
}
else if (!wxStrcmp(attr, wxT("version")))
{
fileVersion = value;
requiredTags++;
}
else if (!wxStrcmp(attr, wxT("audacityversion")))
{
2019-05-29 15:45:19 +00:00
audacityVersion = value;
requiredTags++;
}
else if (!wxStrcmp(attr, wxT("rate")))
{
2019-05-29 15:45:19 +00:00
double rate;
Internat::CompatibleToDouble(value, &rate);
settings.SetRate( rate );
}
else if (!wxStrcmp(attr, wxT("snapto")))
{
2019-05-29 15:45:19 +00:00
settings.SetSnapTo(wxString(value) == wxT("on") ? true : false);
}
else if (!wxStrcmp(attr, wxT("selectionformat")))
{
2019-05-29 15:45:19 +00:00
settings.SetSelectionFormat(
NumericConverter::LookupFormat( NumericConverter::TIME, value) );
}
2019-05-29 15:45:19 +00:00
else if (!wxStrcmp(attr, wxT("audiotimeformat")))
{
settings.SetAudioTimeFormat(
NumericConverter::LookupFormat( NumericConverter::TIME, value) );
}
2019-05-29 15:45:19 +00:00
else if (!wxStrcmp(attr, wxT("frequencyformat")))
{
2019-05-29 15:45:19 +00:00
settings.SetFrequencySelectionFormatName(
NumericConverter::LookupFormat( NumericConverter::FREQUENCY, value ) );
}
2019-05-29 15:45:19 +00:00
else if (!wxStrcmp(attr, wxT("bandwidthformat")))
{
2019-05-29 15:45:19 +00:00
settings.SetBandwidthSelectionFormatName(
NumericConverter::LookupFormat( NumericConverter::BANDWIDTH, value ) );
}
2019-05-29 15:45:19 +00:00
} // while
if (longVpos != 0)
{
2019-05-29 15:45:19 +00:00
// PRL: It seems this must happen after SetSnapTo
viewInfo.vpos = longVpos;
2019-05-29 15:45:19 +00:00
}
if (requiredTags < 2)
{
return false;
}
2019-05-29 15:45:19 +00:00
// Parse the file version from the project
int fver;
int frel;
int frev;
if (!wxSscanf(fileVersion, wxT("%i.%i.%i"), &fver, &frel, &frev))
{
return false;
}
// Parse the file version Audacity was build with
int cver;
int crel;
int crev;
wxSscanf(wxT(AUDACITY_FILE_FORMAT_VERSION), wxT("%i.%i.%i"), &cver, &crel, &crev);
if (cver < fver || crel < frel || crev < frev)
2019-05-29 15:45:19 +00:00
{
/* i18n-hint: %s will be replaced by the version number.*/
auto msg = XO("This file was saved using Audacity %s.\nYou are using Audacity %s. You may need to upgrade to a newer version to open this file.")
.Format(audacityVersion, AUDACITY_VERSION_STRING);
AudacityMessageBox(
msg,
XO("Can't open project file"),
wxOK | wxICON_EXCLAMATION | wxCENTRE,
&window);
2019-05-29 15:45:19 +00:00
return false;
2019-05-29 15:45:19 +00:00
}
if (wxStrcmp(tag, wxT("project")))
{
2019-05-29 15:45:19 +00:00
return false;
}
// All other tests passed, so we succeed
return true;
}
XMLTagHandler *ProjectFileIO::HandleXMLChild(const wxChar *tag)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto pProject = mpProject.lock();
if (! pProject )
return nullptr;
auto &project = *pProject;
auto fn = ProjectFileIORegistry::Lookup(tag);
2019-05-29 15:45:19 +00:00
if (fn)
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
return fn(project);
}
2019-05-29 15:45:19 +00:00
return nullptr;
}
void ProjectFileIO::WriteXMLHeader(XMLWriter &xmlFile) const
{
xmlFile.Write(wxT("<?xml "));
xmlFile.Write(wxT("version=\"1.0\" "));
xmlFile.Write(wxT("standalone=\"no\" "));
xmlFile.Write(wxT("?>\n"));
xmlFile.Write(wxT("<!DOCTYPE "));
xmlFile.Write(wxT("project "));
xmlFile.Write(wxT("PUBLIC "));
xmlFile.Write(wxT("\"-//audacityproject-1.3.0//DTD//EN\" "));
xmlFile.Write(wxT("\"http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd\" "));
xmlFile.Write(wxT(">\n"));
}
void ProjectFileIO::WriteXML(XMLWriter &xmlFile, const WaveTrackArray *tracks)
2019-05-29 15:45:19 +00:00
// may throw
{
Unitary changes (#599) * Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
2020-07-02 23:11:38 +00:00
auto pProject = mpProject.lock();
if (! pProject )
THROW_INCONSISTENCY_EXCEPTION;
auto &proj = *pProject;
auto &tracklist = TrackList::Get(proj);
auto &viewInfo = ViewInfo::Get(proj);
auto &tags = Tags::Get(proj);
const auto &settings = ProjectSettings::Get(proj);
2019-05-29 15:45:19 +00:00
//TIMER_START( "AudacityProject::WriteXML", xml_writer_timer );
xmlFile.StartTag(wxT("project"));
xmlFile.WriteAttr(wxT("xmlns"), wxT("http://audacity.sourceforge.net/xml/"));
xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION));
xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
viewInfo.WriteXMLAttributes(xmlFile);
xmlFile.WriteAttr(wxT("rate"), settings.GetRate());
xmlFile.WriteAttr(wxT("snapto"), settings.GetSnapTo() ? wxT("on") : wxT("off"));
xmlFile.WriteAttr(wxT("selectionformat"),
settings.GetSelectionFormat().Internal());
xmlFile.WriteAttr(wxT("frequencyformat"),
settings.GetFrequencySelectionFormatName().Internal());
xmlFile.WriteAttr(wxT("bandwidthformat"),
settings.GetBandwidthSelectionFormatName().Internal());
tags.WriteXML(xmlFile);
unsigned int ndx = 0;
if (tracks)
{
for (auto track : *tracks)
{
track->WriteXML(xmlFile);
2019-05-29 15:45:19 +00:00
}
}
else
2019-05-29 15:45:19 +00:00
{
tracklist.Any().Visit([&](Track *t)
{
t->WriteXML(xmlFile);
});
2019-05-29 15:45:19 +00:00
}
xmlFile.EndTag(wxT("project"));
//TIMER_STOP( xml_writer_timer );
2019-05-29 15:45:19 +00:00
}
bool ProjectFileIO::AutoSave(const WaveTrackArray *tracks)
2019-05-29 15:45:19 +00:00
{
AutoSaveFile autosave;
WriteXMLHeader(autosave);
WriteXML(autosave, tracks);
return AutoSave(autosave);
2019-05-29 15:45:19 +00:00
}
bool ProjectFileIO::AutoSave(const AutoSaveFile &autosave)
2019-05-29 15:45:19 +00:00
{
auto db = DB();
int rc;
mModified = true;
// 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 autosave(id, dict, doc) VALUES(1, ?1, ?2)"
" ON CONFLICT(id) DO UPDATE SET %sdoc = ?2;",
autosave.DictChanged() ? "dict = ?1, " : "");
sqlite3_stmt *stmt = nullptr;
auto cleanup = finally([&]
2019-05-29 15:45:19 +00:00
{
if (stmt)
{
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;
2019-05-29 15:45:19 +00:00
}
const wxMemoryBuffer &dict = autosave.GetDict();
const wxMemoryBuffer &data = autosave.GetData();
2020-07-01 13:55:06 +00:00
// BIND SQL autosave
sqlite3_bind_blob(stmt, 1, dict.GetData(), dict.GetDataLen(), SQLITE_STATIC);
sqlite3_bind_blob(stmt, 2, data.GetData(), data.GetDataLen(), SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
2019-05-29 15:45:19 +00:00
{
SetDBError(
XO("Failed to update the project file.\nThe following command failed:\n\n%s").Format(sql)
);
return false;
2019-05-29 15:45:19 +00:00
}
return true;
}
bool ProjectFileIO::AutoSaveDelete()
2019-05-29 15:45:19 +00:00
{
auto db = DB();
int rc;
rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr);
if (rc != SQLITE_OK)
{
SetDBError(
XO("Failed to remove the autosave information from the project file.")
);
return false;
}
2019-05-29 15:45:19 +00:00
return true;
}
2019-05-29 15:45:19 +00:00
bool ProjectFileIO::LoadProject(const FilePath &fileName)
{
bool success = false;
// OpenDB() will change mFileName if the file opens/verifies successfully,
// so we must set it back to what it was if any errors are encountered here.
wxString oldFilename = mFileName;
auto cleanup = finally([&]
{
if (!success)
{
CloseDB();
SetFileName(oldFilename);
}
});
// Open the project file
if (!OpenDB(fileName))
{
return false;
}
2019-05-29 15:45:19 +00:00
// Get the XML document...either from the project or autosave
wxString doc;
2019-05-29 15:45:19 +00:00
// Get the autosave doc, if any
bool wasAutosave = false;
wxMemoryBuffer buffer;
if (GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer))
2019-05-29 15:45:19 +00:00
{
doc = AutoSaveFile::Decode(buffer);
wasAutosave = true;
}
// Otherwise, get the project doc
else
{
doc = GetValue("SELECT doc FROM project;");
}
2019-05-29 15:45:19 +00:00
if (doc.empty())
{
return false;
}
2019-05-29 15:45:19 +00:00
XMLFileReader xmlFile;
2019-05-29 15:45:19 +00:00
success = xmlFile.ParseString(this, doc);
2019-05-29 15:45:19 +00:00
if (!success)
{
SetError(
XO("Unable to parse project information.")
);
mLibraryError = xmlFile.GetErrorStr();
return false;
}
2019-05-29 15:45:19 +00:00
// Remember if it was recovered or not
mRecovered = wasAutosave;
if (mRecovered)
2019-05-29 15:45:19 +00:00
{
mModified = true;
2019-05-29 15:45:19 +00:00
}
// 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);
SetFileName(fileName);
return true;
2019-05-29 15:45:19 +00:00
}
bool ProjectFileIO::SaveProject(const FilePath &fileName)
2019-05-29 15:45:19 +00:00
{
wxString origName;
bool wasTemp = false;
bool success = false;
// Should probably simply all of the by using renames. But, one benefit
// of using CopyTo() for new file saves, is that it will be VACUUMED at
// the same time.
auto restore = finally([&]
{
if (!origName.empty())
{
if (success)
{
// The Save was successful, so remove the original file if
// it was a temporary file
if (wasTemp)
{
wxRemoveFile(origName);
}
}
else
{
// Close the new database
CloseDB();
// Reopen the original database
if (OpenDB(origName))
{
// And delete the new database
wxRemoveFile(fileName);
}
}
}
});
// If we're saving to a different file than the current one, then copy the
// current to the new file and make it the active file.
if (mFileName != fileName)
{
if (!CopyTo(fileName))
{
return false;
}
// Remember the original project filename and temporary status. Only do
// this after a successful copy so the "finally" block above doesn't monkey
// with the files.
origName = mFileName;
wasTemp = mTemporary;
// Close the original project file and open the new one
if (!CloseDB() || !OpenDB(fileName))
{
return false;
}
}
auto db = DB();
int rc;
XMLStringWriter 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;");
sqlite3_stmt *stmt = nullptr;
2019-05-29 15:45:19 +00:00
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;
2019-05-29 15:45:19 +00:00
}
2020-07-01 13:55:06 +00:00
// 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);
// This will free the statement
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
SetDBError(
XO("Failed to save project file information.")
);
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.
AutoSaveDelete();
// No longer modified
mModified = false;
// No longer recovered
mRecovered = false;
// No longer a temporary project
mTemporary = false;
// Adjust the title
SetProjectTitle();
// Tell the finally block to behave
success = true;
return true;
2019-05-29 15:45:19 +00:00
}
bool ProjectFileIO::IsModified() const
{
return mModified;
}
bool ProjectFileIO::IsTemporary() const
{
return mTemporary;
}
bool ProjectFileIO::IsRecovered() const
{
return mRecovered;
2019-05-29 15:45:19 +00:00
}
void ProjectFileIO::Reset()
{
wxASSERT_MSG(mDB == nullptr, wxT("Resetting project with open project file"));
mModified = false;
mRecovered = false;
SetFileName({});
2019-05-29 15:45:19 +00:00
}
wxLongLong ProjectFileIO::GetFreeDiskSpace()
{
// make sure it's open and the path is defined
auto db = DB();
wxLongLong freeSpace;
if (wxGetDiskSpace(wxPathOnly(mFileName), NULL, &freeSpace))
{
return freeSpace;
}
return -1;
}
const TranslatableString & ProjectFileIO::GetLastError() const
{
return mLastError;
}
const TranslatableString & ProjectFileIO::GetLibraryError() const
{
return mLibraryError;
}
void ProjectFileIO::SetError(const TranslatableString &msg)
{
mLastError = msg;
mLibraryError = {};
}
void ProjectFileIO::SetDBError(const TranslatableString &msg)
{
mLastError = msg;
if (mDB)
{
mLibraryError = Verbatim(sqlite3_errmsg(mDB));
}
}
void ProjectFileIO::Bypass(bool bypass)
{
mBypass = bypass;
}
bool ProjectFileIO::ShouldBypass()
{
return mBypass;
}