From 6fef14dd08189453f6d4a68ae10908e673489c80 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Sun, 26 Jul 2020 02:01:37 -0500 Subject: [PATCH] AUP3: Rework Compact Project menu item and AutoRecoverDialog Plus a couple of fixes that prevent leaving temporary files after a project is loaded. --- cmake-proxies/sqlite/CMakeLists.txt | 5 +- src/AutoRecoveryDialog.cpp | 147 +++++++++++++++++----------- src/ProjectFileIO.cpp | 62 ++++++------ src/ProjectFileIO.h | 4 +- src/ProjectSettings.cpp | 1 - src/ProjectSettings.h | 5 - src/menus/FileMenus.cpp | 50 +++++++--- 7 files changed, 160 insertions(+), 114 deletions(-) diff --git a/cmake-proxies/sqlite/CMakeLists.txt b/cmake-proxies/sqlite/CMakeLists.txt index 1ef374a56..0de801f3f 100644 --- a/cmake-proxies/sqlite/CMakeLists.txt +++ b/cmake-proxies/sqlite/CMakeLists.txt @@ -20,10 +20,9 @@ list( APPEND INCLUDES list( APPEND DEFINES PRIVATE # - # Connection based settings. Persistent settings are done in the - # schema. + # We need the dbstats table for space calculations. # -# SQLITE_DEFAULT_LOCKING_MODE=0 + SQLITE_ENABLE_DBSTAT_VTAB=1 # Can't be set after a WAL mode database is initialized, so change # the default here to ensure all project files get the same page diff --git a/src/AutoRecoveryDialog.cpp b/src/AutoRecoveryDialog.cpp index a23394833..a3f0accb0 100644 --- a/src/AutoRecoveryDialog.cpp +++ b/src/AutoRecoveryDialog.cpp @@ -24,12 +24,11 @@ Paul Licameli split from AutoRecovery.cpp #include #include -#define USE_CHECKBOXES - enum { ID_QUIT_AUDACITY = 10000, ID_DISCARD_SELECTED, ID_RECOVER_SELECTED, + ID_SKIP, ID_FILE_LIST }; @@ -44,10 +43,13 @@ public: private: void PopulateOrExchange(ShuttleGui &S); void PopulateList(); + bool HaveChecked(); void OnQuitAudacity(wxCommandEvent &evt); void OnDiscardSelected(wxCommandEvent &evt); void OnRecoverSelected(wxCommandEvent &evt); + void OnSkip(wxCommandEvent &evt); + void OnItemActivated(wxListEvent &evt); FilePaths mFiles; wxListCtrl *mFileList; @@ -61,6 +63,8 @@ BEGIN_EVENT_TABLE(AutoRecoveryDialog, wxDialogWrapper) EVT_BUTTON(ID_QUIT_AUDACITY, AutoRecoveryDialog::OnQuitAudacity) EVT_BUTTON(ID_DISCARD_SELECTED, AutoRecoveryDialog::OnDiscardSelected) EVT_BUTTON(ID_RECOVER_SELECTED, AutoRecoveryDialog::OnRecoverSelected) + EVT_BUTTON(ID_SKIP, AutoRecoveryDialog::OnSkip) + EVT_LIST_ITEM_ACTIVATED(ID_FILE_LIST, AutoRecoveryDialog::OnItemActivated) END_EVENT_TABLE() AutoRecoveryDialog::AutoRecoveryDialog(AudacityProject *project) @@ -97,16 +101,12 @@ void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui &S) { mFileList = S.Id(ID_FILE_LIST).AddListControlReportMode( { -#if defined(USE_CHECKBOXES) /*i18n-hint: (verb). It instruct the user to select items.*/ XO("Select"), -#endif /*i18n-hint: (noun). It's the name of the project to recover.*/ XO("Name") }); -#if defined(USE_CHECKBOXES) mFileList->EnableCheckBoxes(); -#endif PopulateList(); } S.EndStatic(); @@ -120,6 +120,7 @@ void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui &S) S.Id(ID_QUIT_AUDACITY).AddButton(XXO("Quit Audacity")); S.Id(ID_DISCARD_SELECTED).AddButton(XXO("Discard Selected")); S.Id(ID_RECOVER_SELECTED).AddButton(XXO("Recover Selected")); + S.Id(ID_SKIP).AddButton(XXO("Skip")); } S.EndHorizontalLay(); } @@ -141,6 +142,23 @@ void AutoRecoveryDialog::PopulateList() wxString pattern = wxT("*.") + FileNames::UnsavedProjectExtension(); FilePaths files; + wxDir::GetAllFiles(tempdir, &files, pattern, wxDIR_FILES); + + FilePaths active = ActiveProjects::GetAll(); + + for (auto file : active) + { + wxFileName fn = file; + if (fn.FileExists()) + { + FilePath fullPath = fn.GetFullPath(); + if (files.Index(fullPath) == wxNOT_FOUND) + { + files.push_back(fullPath); + } + } + } + FilePath activeFile; if (mProject) { @@ -148,58 +166,63 @@ void AutoRecoveryDialog::PopulateList() activeFile = projectFileIO.GetFileName(); } -// wxDir::GetAllFiles(tempdir, &files, pattern, wxDIR_FILES); - - FilePaths active = ActiveProjects::GetAll(); - + mFiles.clear(); mFileList->DeleteAllItems(); - long item = 0; - for (auto file : active) - { - wxFileName fn = file; - if (fn != activeFile) - { - if (fn.FileExists()) - { - FilePath fullPath = fn.GetFullPath(); - if (files.Index(fullPath) == wxNOT_FOUND) - { - files.push_back(fullPath); - } - } - } - } for (auto file : files) { wxFileName fn = file; - - mFiles.push_back(fn.GetFullPath()); - mFileList->InsertItem(item, wxT("")); - mFileList->SetItem(item, 1, fn.GetName()); - item++; + if (fn != activeFile) + { + mFiles.push_back(fn.GetFullPath()); + mFileList->InsertItem(item, wxT("")); + mFileList->SetItem(item, 1, fn.GetName()); + item++; + } } -#if defined(USE_CHECKBOXES) mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER); - mFileList->SetColumnWidth(1, wxLIST_AUTOSIZE); -#else - mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE); -#endif + mFileList->SetColumnWidth(1, 500); } -void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent & WXUNUSED(event)) +bool AutoRecoveryDialog::HaveChecked() +{ + long item = -1; + while (true) + { + item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE); + if (item == wxNOT_FOUND) + { + break; + } + if (mFileList->IsItemChecked(item)) + { + return true; + } + } + + AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery")); + + return false; +} + +void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent &WXUNUSED(evt)) { EndModal(ID_QUIT_AUDACITY); } -void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent & WXUNUSED(event)) +void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent &WXUNUSED(evt)) { + if (!HaveChecked()) + { + return; + } + int ret = AudacityMessageBox( XO("Are you sure you want to discard the selected projects?\n\n" "Choosing \"Yes\" permanently deletes the selected projects immediately."), - XO("Confirm Discard Projects"), + XO("Automatic Crash Recovery"), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, this); if (ret == wxNO) @@ -207,27 +230,18 @@ void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent & WXUNUSED(event)) return; } -#define USE_CHECKBOXES -#if defined(USE_CHECKBOXES) -#define state wxLIST_STATE_DONTCARE -#else -#define state wxLIST_STATE_SELECTED -#endif - long item = -1; while (true) { - item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, state); + item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE); if (item == wxNOT_FOUND) { break; } -#if defined(USE_CHECKBOXES) if (!mFileList->IsItemChecked(item)) { continue; } -#endif FilePath file = mFiles[item]; if (wxRemoveFile(file)) @@ -259,31 +273,34 @@ void AutoRecoveryDialog::OnDiscardSelected(wxCommandEvent & WXUNUSED(event)) } } -void AutoRecoveryDialog::OnRecoverSelected(wxCommandEvent & WXUNUSED(event)) +void AutoRecoveryDialog::OnRecoverSelected(wxCommandEvent &WXUNUSED(evt)) { -#define USE_CHECKBOXES -#if defined(USE_CHECKBOXES) -#define state wxLIST_STATE_DONTCARE -#else -#define state wxLIST_STATE_SELECTED -#endif + if (!HaveChecked()) + { + return; + } FilePaths files; + bool selected = false; long item = -1; while (true) { - item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, state); + item = mFileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE); if (item == wxNOT_FOUND) { + if (!selected) + { + AudacityMessageBox(XO("No projects selected"), XO("Automatic Crash Recovery")); + } break; } -#if defined(USE_CHECKBOXES) + selected = true; + if (!mFileList->IsItemChecked(item)) { continue; } -#endif files.push_back(mFiles[item]); } @@ -293,6 +310,17 @@ void AutoRecoveryDialog::OnRecoverSelected(wxCommandEvent & WXUNUSED(event)) EndModal(ID_RECOVER_SELECTED); } +void AutoRecoveryDialog::OnSkip(wxCommandEvent &WXUNUSED(evt)) +{ + EndModal(ID_SKIP); +} + +void AutoRecoveryDialog::OnItemActivated(wxListEvent &evt) +{ + long item = evt.GetIndex(); + mFileList->CheckItem(item, !mFileList->IsItemChecked(item)); +} + //////////////////////////////////////////////////////////////////////////// static bool RecoverAllProjects(const FilePaths &files, @@ -352,6 +380,7 @@ bool ShowAutoRecoveryDialogIfNeeded(AudacityProject **pproj, bool *didRecoverAny switch (ret) { + case ID_SKIP: case ID_DISCARD_SELECTED: success = true; break; diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index 98e215663..d2959f8dd 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -354,9 +354,21 @@ void ProjectFileIO::DiscardConnection() { // Store an error message SetDBError( - XO("Failed to successfully close the source project file") + XO("Failed to discard connection") ); } + + // If this is a temporary project, we no longer want to keep the + // project file. + if (mPrevTemporary) + { + // This is just a safety check. + wxFileName temp(FileNames::TempDir()); + if (temp == wxPathOnly(mPrevFileName)) + { + wxRemoveFile(mPrevFileName); + } + } mPrevConn = nullptr; mPrevFileName.clear(); } @@ -372,7 +384,7 @@ void ProjectFileIO::RestoreConnection() { // Store an error message SetDBError( - XO("Failed to successfully close the destination project file") + XO("Failed to restore connection") ); } } @@ -938,21 +950,14 @@ bool ProjectFileIO::ShouldVacuum(const std::shared_ptr &tracks) unsigned long long blockcount = 0; unsigned long long total = 0; - auto cb = - [&blockcount, &total](int cols, char **vals, char **){ - if ( cols != 2 ) - // Should have two exactly! - return 1; - if ( total > 0 ) { - // Should not have multiple rows! - total = 0; - return 1; - } + auto cb = [&blockcount, &total](int cols, char **vals, char **) + { // Convert - wxString{ vals[0] }.ToULongLong( &blockcount ); - wxString{ vals[1] }.ToULongLong( &total ); + wxString(vals[0]).ToULongLong(&blockcount); + wxString(vals[1]).ToULongLong(&total); return 0; }; + if (!Query("SELECT Count(*), " "Sum(Length(summary256)) + Sum(Length(summary64k)) + Sum(Length(samples)) " "FROM sampleblocks;", cb) @@ -985,7 +990,7 @@ Connection &ProjectFileIO::CurrConn() return connectionPtr.mpConnection; } -void ProjectFileIO::Vacuum(const std::shared_ptr &tracks) +void ProjectFileIO::Vacuum(const std::shared_ptr &tracks, bool force /* = false */) { // Haven't vacuumed yet mWasVacuumed = false; @@ -996,18 +1001,21 @@ void ProjectFileIO::Vacuum(const std::shared_ptr &tracks) // Don't vacuum if this is a temporary project or if it's determined there are not // enough unused blocks to make it worthwhile - if (IsTemporary() || !ShouldVacuum(tracks)) + if (!force) { - // Delete the AutoSave doc it if exists - if (IsModified()) + if (IsTemporary() || !ShouldVacuum(tracks)) { - // PRL: not clear what to do if the following fails, but the worst should - // be, the project may reopen in its present state as a recovery file, not - // at the last saved state. - (void) AutoSaveDelete(); - } + // Delete the AutoSave doc it if exists + if (IsModified()) + { + // PRL: not clear what to do if the following fails, but the worst should + // be, the project may reopen in its present state as a recovery file, not + // at the last saved state. + (void) AutoSaveDelete(); + } - return; + return; + } } // Create the project doc @@ -1932,12 +1940,6 @@ bool ProjectFileIO::SaveProject(const FilePath &fileName) // The Save was successful, so now it is safe to abandon the // original connection DiscardConnection(); - - // And also remove the original file if it was a temporary file - if (wasTemp) - { - wxRemoveFile(origName); - } } else { diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 084f90d3a..e4d508ff3 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -64,8 +64,8 @@ public: // on what is discovered while opening the file, such as whether it is a // recovery file void SetProjectTitle(int number = -1); - // Should be empty or a fully qualified file name + // Should be empty or a fully qualified file name const FilePath &GetFileName() const; void SetFileName( const FilePath &fileName ); @@ -106,7 +106,7 @@ public: void SetBypass(); // Remove all unused space within a project file - void Vacuum(const std::shared_ptr &tracks); + void Vacuum(const std::shared_ptr &tracks, bool force = false); // The last vacuum check did actually vacuum the project file if true bool WasVacuumed(); diff --git a/src/ProjectSettings.cpp b/src/ProjectSettings.cpp index 97daa7503..630123ffd 100644 --- a/src/ProjectSettings.cpp +++ b/src/ProjectSettings.cpp @@ -91,7 +91,6 @@ ProjectSettings::ProjectSettings(AudacityProject &project) void ProjectSettings::UpdatePrefs() { - gPrefs->Read(wxT("/GUI/CompactAtClose"), &mCompactAtClose, true); gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &mShowId3Dialog, true); gPrefs->Read(wxT("/GUI/EmptyCanBeDirty"), &mEmptyCanBeDirty, true); gPrefs->Read(wxT("/GUI/ShowSplashScreen"), &mShowSplashScreen, true); diff --git a/src/ProjectSettings.h b/src/ProjectSettings.h index 1fa33fc85..0aa7928aa 100644 --- a/src/ProjectSettings.h +++ b/src/ProjectSettings.h @@ -118,10 +118,6 @@ public: bool GetShowSplashScreen() const { return mShowSplashScreen; } - // Compact at close - void SetCompactAtClose(bool compact) { mCompactAtClose = compact; }; - bool GetCompactAtClose() const { return mCompactAtClose; } - private: void UpdatePrefs() override; @@ -149,7 +145,6 @@ private: bool mIsSyncLocked{ false }; bool mEmptyCanBeDirty; bool mShowSplashScreen; - bool mCompactAtClose; }; #endif diff --git a/src/menus/FileMenus.cpp b/src/menus/FileMenus.cpp index b7238f6cb..370effbed 100644 --- a/src/menus/FileMenus.cpp +++ b/src/menus/FileMenus.cpp @@ -2,6 +2,7 @@ #include "../Experimental.h" #include "../BatchCommands.h" +#include "../Clipboard.h" #include "../CommonCommandFlags.h" #include "../FileNames.h" #include "../LabelTrack.h" @@ -142,19 +143,41 @@ void OnClose(const CommandContext &context ) window.Close(); } -void OnCompact(const CommandContext &context ) +void OnCompact(const CommandContext &context) { + int id = AudacityMessageBox( + XO("Compacting this project will free up disk space by removing unused bytes within the file.\n\n" + "NOTE: If you proceed, the current Undo History and clipboard contents will be discarded.\n\n" + "Do you want to continue?"), + XO("Compact Project"), + wxYES_NO); + + if (id == wxNO) + { + return; + } + auto &project = context.project; - auto &settings = ProjectSettings::Get( project ); + auto &undoManager = UndoManager::Get(project); - bool compact; - gPrefs->Read(wxT("/GUI/CompactAtClose"), &compact, true); + ProjectHistory::Get(project) + .PushState(XO("Compacted project file"), XO("Compact"), UndoPush::CONSOLIDATE); - compact = !compact; - gPrefs->Write(wxT("/GUI/CompactAtClose"), compact); - gPrefs->Flush(); + auto numStates = undoManager.GetNumStates(); + undoManager.RemoveStates(numStates - 1); - settings.SetCompactAtClose(compact); + auto &clipboard = Clipboard::Get(); + clipboard.Clear(); + + auto currentTracks = TrackList::Create( nullptr ); + auto &tracks = TrackList::Get( project ); + for (auto t : tracks.Any()) + { + currentTracks->Add(t->Duplicate()); + } + + auto &projectFileIO = ProjectFileIO::Get(project); + projectFileIO.Vacuum(currentTracks, true); } void OnSave(const CommandContext &context ) @@ -612,11 +635,7 @@ BaseItemSharedPtr FileMenu() ///////////////////////////////////////////////////////////////////////////// Command( wxT("Close"), XXO("&Close"), FN(OnClose), - AudioIONotBusyFlag(), wxT("Ctrl+W") ), - - Command( wxT("Compact"), XXO("Com&pact at close (on/off)"), - FN(OnCompact), AlwaysEnabledFlag, - Options{}.CheckTest( wxT("/GUI/CompactAtClose"), true ) ) + AudioIONotBusyFlag(), wxT("Ctrl+W") ) ), Section( "Save", @@ -627,7 +646,10 @@ BaseItemSharedPtr FileMenu() AudioIONotBusyFlag() ), Command( wxT("SaveCopy"), XXO("&Backup Project..."), FN(OnSaveCopy), AudioIONotBusyFlag() ) - ) + ), + + Command( wxT("Compact"), XXO("Com&pact project"), FN(OnCompact), + AudioIONotBusyFlag() ) ), Section( "Import-Export",