From c04ed76b6b2e7504da0aa498a7ad4fba534dff0b Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Fri, 3 Jul 2020 14:38:57 -0500 Subject: [PATCH] AUP3: Fixes a couple of reported issues Add the missing "File -> Save Project -> Backup Project..." menu item Allows "File -> Save Project -> Save Project" to save unmodified projects. --- src/ProjectFileIO.cpp | 163 ++++++++++++++++++++++++++++++------- src/ProjectFileIO.h | 1 + src/ProjectFileManager.cpp | 99 ++++++++++++++++++++-- src/ProjectFileManager.h | 1 + src/menus/FileMenus.cpp | 11 ++- 5 files changed, 239 insertions(+), 36 deletions(-) diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index bb9f26f10..bd53fee61 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -1219,38 +1219,45 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName) " ON CONFLICT(id) DO UPDATE SET doc = ?1;"); - sqlite3_stmt *stmt = nullptr; - - 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; - } + sqlite3_stmt* stmt = nullptr; - // 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; - } + 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); - - // This will free the statement - sqlite3_finalize(stmt); - - if (rc != SQLITE_DONE) - { - SetDBError( - XO("Failed to save project file information.") - ); - return false; + rc = sqlite3_step(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 @@ -1276,6 +1283,104 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName) return true; } +bool ProjectFileIO::SaveCopy(const FilePath& fileName) +{ + if (!CopyTo(fileName)) + { + return false; + } + + sqlite3 *db = nullptr; + int rc; + bool success = false; + + auto cleanup = finally([&] + { + if (db) + { + sqlite3_close(db); + } + + if (!success) + { + wxRemoveFile(fileName); + } + }); + + rc = sqlite3_open(fileName, &db); + if (rc != SQLITE_OK) + { + SetDBError(XO("Failed to open backup file")); + return false; + } + + 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; + + 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; + } + } + + 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; + } + + // Tell the finally block to behave + success = true; + + return true; +} + bool ProjectFileIO::IsModified() const { return mModified; diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index edafba9cd..2634b508f 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -68,6 +68,7 @@ public: bool LoadProject(const FilePath &fileName); bool SaveProject(const FilePath &fileName); + bool SaveCopy(const FilePath& fileName); XMLTagHandler *HandleXMLChild(const wxChar *tag) override; void WriteXMLHeader(XMLWriter &xmlFile) const; diff --git a/src/ProjectFileManager.cpp b/src/ProjectFileManager.cpp index 54822c1a2..4a9176ca5 100644 --- a/src/ProjectFileManager.cpp +++ b/src/ProjectFileManager.cpp @@ -335,13 +335,13 @@ bool ProjectFileManager::DoSave(const FilePath & fileName, const bool fromSaveAs return true; } +// This version of SaveAs is invoked only from scripting and does not +// prompt for a file name bool ProjectFileManager::SaveAs(const wxString & newFileName, bool addToHistory /*= true*/) { auto &project = mProject; auto &projectFileIO = ProjectFileIO::Get( project ); - // This version of SaveAs is invoked only from scripting and does not - // prompt for a file name auto oldFileName = projectFileIO.GetFileName(); bool bOwnsNewName = !projectFileIO.IsTemporary() && (oldFileName == newFileName); @@ -382,7 +382,7 @@ bool ProjectFileManager::SaveAs() filename = projectFileIO.GetFileName(); } - // Bug 1304: Set a default file path if none was given. For Save/SaveAs + // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy if( !FileNames::IsPathAvailable( filename.GetPath( wxPATH_GET_VOLUME| wxPATH_GET_SEPARATOR) ) ){ bHasPath = false; filename.SetPath(FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path")).GetPath()); @@ -400,7 +400,7 @@ For an audio file that will open in other apps, use 'Export'.\n"); } bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty()); - wxString fName; + FilePath fName; if (bPrompt) { // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking @@ -425,7 +425,7 @@ For an audio file that will open in other apps, use 'Export'.\n"); filename.SetExt(wxT("aup3")); fName = filename.GetFullPath(); - if (!bPrompt && filename.FileExists()) { + if (bPrompt && filename.FileExists()) { // Saving a copy of the project should never overwrite an existing project. AudacityMessageDialog m( nullptr, @@ -481,7 +481,7 @@ will be irreversibly overwritten.").Format( fName, fName ); // Overwrite disallowed. The destination project is open in another window. AudacityMessageDialog m( 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 because the selected project is open in another window.\nPlease try again and select an original name."), XO("Error Saving Project"), wxOK|wxICON_ERROR ); m.ShowModal(); @@ -502,6 +502,93 @@ will be irreversibly overwritten.").Format( fName, fName ); return(success); } +bool ProjectFileManager::SaveCopy() +{ + auto &project = mProject; + auto &projectFileIO = ProjectFileIO::Get(project); + auto &window = GetProjectFrame(project); + TitleRestorer Restorer(window, project); // RAII + wxFileName filename; + + if (projectFileIO.IsTemporary()) + { + filename = FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path")); + } + else + { + filename = projectFileIO.GetFileName(); + } + + // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy + if (!FileNames::IsPathAvailable(filename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR))) + { + filename.SetPath(FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path")).GetPath()); + } + + TranslatableString title = + XO("%sSave Copy of Project \"%s\" As...").Format(Restorer.sProjNumber, Restorer.sProjName); + + bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty()); + FilePath fName; + + do + { + if (bPrompt) + { + // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking + // for overwrite ourselves later, and we disallow it. + // We disallow overwrite because we would have to DELETE the many + // smaller files too, or prompt to move them. + fName = FileNames::SelectFile(FileNames::Operation::Export, + title, + filename.GetPath(), + filename.GetFullName(), + wxT("aup3"), + { FileNames::AudacityProjects }, + wxFD_SAVE | wxRESIZE_BORDER, + &window); + + if (fName.empty()) + { + return false; + } + + filename = fName; + }; + + filename.SetExt(wxT("aup3")); + fName = filename.GetFullPath(); + + if (bPrompt && filename.FileExists()) + { + // Saving a copy of the project should never overwrite an existing project. + AudacityMessageDialog m(nullptr, + XO("Saving a copy must not overwrite an existing saved project.\nPlease try again and select an original name."), + XO("Error Saving Copy of Project"), + wxOK | wxICON_ERROR); + m.ShowModal(); + + continue; + } + + break; + } while (bPrompt); + + if (!projectFileIO.SaveCopy(filename.GetFullPath())) + { + // Overwrite disallowed. The destination project is open in another window. + AudacityMessageDialog m( + 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("Error Saving Project"), + wxOK | wxICON_ERROR); + m.ShowModal(); + return false; + } + + return true; +} + void ProjectFileManager::Reset() { // mLastSavedTrack code copied from OnCloseWindow. diff --git a/src/ProjectFileManager.h b/src/ProjectFileManager.h index 7acdc9f82..8670eec96 100644 --- a/src/ProjectFileManager.h +++ b/src/ProjectFileManager.h @@ -59,6 +59,7 @@ public: bool SaveAs(const wxString & newFileName, bool addToHistory = true); // strProjectPathName is full path for aup except extension bool SaveFromTimerRecording( wxFileName fnFile ); + bool SaveCopy(); void Reset(); diff --git a/src/menus/FileMenus.cpp b/src/menus/FileMenus.cpp index 5c2c89c63..b43f310b7 100644 --- a/src/menus/FileMenus.cpp +++ b/src/menus/FileMenus.cpp @@ -155,6 +155,13 @@ void OnSaveAs(const CommandContext &context ) projectFileManager.SaveAs(); } +void OnSaveCopy(const CommandContext &context ) +{ + auto &project = context.project; + auto &projectFileManager = ProjectFileManager::Get( project ); + projectFileManager.SaveCopy(); +} + void OnExportMp3(const CommandContext &context) { auto &project = context.project; @@ -595,8 +602,10 @@ BaseItemSharedPtr FileMenu() Section( "Save", Menu( wxT("Save"), XXO("&Save Project"), Command( wxT("Save"), XXO("&Save Project"), FN(OnSave), - AudioIONotBusyFlag() | UnsavedChangesFlag(), wxT("Ctrl+S") ), + AudioIONotBusyFlag(), wxT("Ctrl+S") ), Command( wxT("SaveAs"), XXO("Save Project &As..."), FN(OnSaveAs), + AudioIONotBusyFlag() ), + Command( wxT("SaveCopy"), XXO("&Backup Project..."), FN(OnSaveCopy), AudioIONotBusyFlag() ) ) ),