AUP3: Rework Compact Project menu item and AutoRecoverDialog

Plus a couple of fixes that prevent leaving temporary files
after a project is loaded.
This commit is contained in:
Leland Lucius 2020-07-26 02:01:37 -05:00
parent 674cfe68c9
commit 6fef14dd08
7 changed files with 160 additions and 114 deletions

View File

@ -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

View File

@ -24,12 +24,11 @@ Paul Licameli split from AutoRecovery.cpp
#include <wx/filename.h>
#include <wx/listctrl.h>
#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;

View File

@ -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<TrackList> &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<TrackList> &tracks)
void ProjectFileIO::Vacuum(const std::shared_ptr<TrackList> &tracks, bool force /* = false */)
{
// Haven't vacuumed yet
mWasVacuumed = false;
@ -996,18 +1001,21 @@ void ProjectFileIO::Vacuum(const std::shared_ptr<TrackList> &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
{

View File

@ -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<TrackList> &tracks);
void Vacuum(const std::shared_ptr<TrackList> &tracks, bool force = false);
// The last vacuum check did actually vacuum the project file if true
bool WasVacuumed();

View File

@ -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);

View File

@ -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

View File

@ -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",