From 2faa24c96bf39e1bf15c80a2b66b79eb18d65242 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 14 May 2019 13:24:14 -0400 Subject: [PATCH] Move ProjectFSCK out of DirManager... ... which makes DirManager.cpp not depend on MissingAliasFileDialog.cpp --- src/Dependencies.cpp | 2 +- src/DirManager.cpp | 388 +----------------------- src/DirManager.h | 40 ++- src/Project.cpp | 7 +- src/ProjectFSCK.cpp | 391 +++++++++++++++++++++++++ src/ProjectFSCK.h | 32 ++ src/blockfile/LegacyAliasBlockFile.cpp | 2 +- src/blockfile/LegacyBlockFile.cpp | 2 +- src/blockfile/ODDecodeBlockFile.cpp | 2 +- src/blockfile/ODPCMAliasBlockFile.cpp | 2 +- src/blockfile/PCMAliasBlockFile.cpp | 2 +- src/blockfile/SilentBlockFile.cpp | 2 +- src/blockfile/SimpleBlockFile.cpp | 2 +- 13 files changed, 475 insertions(+), 399 deletions(-) diff --git a/src/Dependencies.cpp b/src/Dependencies.cpp index b4952980b..e3e340182 100644 --- a/src/Dependencies.cpp +++ b/src/Dependencies.cpp @@ -124,7 +124,7 @@ void FindDependencies(AudacityProject *project, auto aliasBlockFile = static_cast( &*f ); const wxFileName &fileName = aliasBlockFile->GetAliasedFileName(); - // In DirManager::ProjectFSCK(), if the user has chosen to + // In ProjectFSCK(), if the user has chosen to // "Replace missing audio with silence", the code there puts in an empty wxFileName. // Don't count those in dependencies. if (!fileName.IsOk()) diff --git a/src/DirManager.cpp b/src/DirManager.cpp index 19932ed45..df5a8a3b7 100644 --- a/src/DirManager.cpp +++ b/src/DirManager.cpp @@ -93,12 +93,10 @@ #include "blockfile/ODPCMAliasBlockFile.h" #include "blockfile/ODDecodeBlockFile.h" #include "InconsistencyException.h" -#include "MissingAliasFileDialog.h" #include "Project.h" #include "Prefs.h" #include "Sequence.h" #include "widgets/Warning.h" -#include "widgets/MultiDialog.h" #include "widgets/ErrorDialog.h" #include "widgets/ProgressDialog.h" @@ -152,14 +150,14 @@ wxMemorySize GetFreeMemory() // JKC: Using flag wxDIR_NO_FOLLOW to NOT follow symbolic links. // Directories and files inside a project should never be symbolic // links, so if we find one, do not follow it. -static int RecursivelyEnumerate(const FilePath &dirPath, +int DirManager::RecursivelyEnumerate(const FilePath &dirPath, FilePaths& filePathArray, // output: all files in dirPath tree wxString dirspec, wxString filespec, bool bFiles, bool bDirs, - int progress_count = 0, - int progress_bias = 0, - ProgressDialog* progress = NULL) + int progress_count, + int progress_bias, + ProgressDialog* progress) { int count=0; bool cont; @@ -206,7 +204,7 @@ static int RecursivelyEnumerate(const FilePath &dirPath, return count; } -static int RecursivelyEnumerateWithProgress(const FilePath &dirPath, +int DirManager::RecursivelyEnumerateWithProgress(const FilePath &dirPath, FilePaths& filePathArray, // output: all files in dirPath tree wxString dirspec, wxString filespec, @@ -228,7 +226,7 @@ static int RecursivelyEnumerateWithProgress(const FilePath &dirPath, return count; } -static int RecursivelyCountSubdirs( const FilePath &dirPath ) +int DirManager::RecursivelyCountSubdirs( const FilePath &dirPath ) { bool bContinue; int nCount = 0; @@ -248,9 +246,9 @@ static int RecursivelyCountSubdirs( const FilePath &dirPath ) return nCount; } -static int RecursivelyRemoveEmptyDirs(const FilePath &dirPath, - int nDirCount = 0, - ProgressDialog* pProgress = NULL) +int DirManager::RecursivelyRemoveEmptyDirs(const FilePath &dirPath, + int nDirCount, + ProgressDialog* pProgress) { bool bContinue; wxDir dir(dirPath); @@ -293,8 +291,8 @@ static int RecursivelyRemoveEmptyDirs(const FilePath &dirPath, return nCount; } -static void RecursivelyRemove(const FilePaths& filePathArray, int count, int bias, - int flags, const wxChar* message = NULL) +void DirManager::RecursivelyRemove(const FilePaths& filePathArray, int count, int bias, + int flags, const wxChar* message) { bool bFiles= (flags & kCleanFiles) != 0; bool bDirs = (flags & kCleanDirs) != 0; @@ -1645,370 +1643,6 @@ bool DirManager::EnsureSafeFilename(const wxFileName &fName) return true; } -// Check the BlockFiles against the disk state. -// Missing Blockfile data can be regenerated if possible or replaced with silence. -// Orphan blockfiles can be deleted. -// Note that even BlockFiles not referenced by the current savefile (but locked -// by history) will be reflected in the mBlockFileHash, and that's a -// good thing; this is one reason why we use the hash and not the most -// recent savefile. -int DirManager::ProjectFSCK(const bool bForceError, const bool bAutoRecoverMode) -{ - // In earlier versions of this method, enumerations of errors were - // all done in sequence, then the user was prompted for each type of error. - // The enumerations are now interleaved with prompting, because, for example, - // user choosing to replace missing aliased block files with silence - // needs to put in SilentBlockFiles and DELETE the corresponding auf files, - // so those would then not be cumulated in missingAUFHash. - // We still do the FindX methods outside the conditionals, - // so the log always shows all found errors. - - int action; // choice of action for each type of error - int nResult = 0; - - if (bForceError && !bAutoRecoverMode) - { - // TODO: Replace with more user friendly error message? - /* i18n-hint: The audacity project file is XML and has 'tags' in it, - rather like html tags some stuff. - This error message is about the tags that hold the sequence information. - The error message is confusing to users in English, and could just say - "Found problems with when checking project file." */ - wxString msg = _("Project check read faulty Sequence tags."); - const wxChar *buttons[] = - {_("Close project immediately with no changes"), - _("Continue with repairs noted in log, and check for more errors. This will save the project in its current state, unless you \"Close project immediately\" on further error alerts."), - NULL}; - wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. - action = ShowMultiDialog(msg, _("Warning - Problems Reading Sequence Tags"), buttons); - if (action == 0) - nResult = FSCKstatus_CLOSE_REQ; - else - nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; - } - - FilePaths filePathArray; // *all* files in the project directory/subdirectories - auto dirPath = (!projFull.empty() ? projFull : mytemp); - RecursivelyEnumerateWithProgress( - dirPath, - filePathArray, // output: all files in project directory tree - wxEmptyString, // All dirs - wxEmptyString, // All files - true, false, - mBlockFileHash.size(), // rough guess of how many BlockFiles will be found/processed, for progress - _("Inspecting project file data")); - - // - // MISSING ALIASED AUDIO FILES - // - MissingAliasFilesDialog::SetShouldShow(false); - BlockHash missingAliasFilesAUFHash; // (.auf) AliasBlockFiles whose aliased files are missing - BlockHash missingAliasFilesPathHash; // full paths of missing aliased files - this->FindMissingAliasFiles(missingAliasFilesAUFHash, missingAliasFilesPathHash); - - if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAliasFilesAUFHash.empty()) - { - // In auto-recover mode, we always create silent blocks, and do not ask user. - // This makes sure the project is complete next time we open it. - if (bAutoRecoverMode) - action = 2; - else - { - wxString msgA = -_("Project check of \"%s\" folder \ -\ndetected %lld missing external audio file(s) \ -\n('aliased files'). There is no way for Audacity \ -\nto recover these files automatically. \ -\n\nIf you choose the first or second option below, \ -\nyou can try to find and restore the missing files \ -\nto their previous location. \ -\n\nNote that for the second option, the waveform \ -\nmay not show silence. \ -\n\nIf you choose the third option, this will save the \ -\nproject in its current state, unless you \"Close \ -\nproject immediately\" on further error alerts."); - wxString msg; - msg.Printf(msgA, this->projName, (long long) missingAliasFilesPathHash.size()); - const wxChar *buttons[] = - {_("Close project immediately with no changes"), - _("Treat missing audio as silence (this session only)"), - _("Replace missing audio with silence (permanent immediately)."), - NULL}; - wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. - action = ShowMultiDialog(msg, _("Warning - Missing Aliased File(s)"), buttons); - } - - if (action == 0) - nResult = FSCKstatus_CLOSE_REQ; - else - { - // LL: A progress dialog should probably be used here - BlockHash::iterator iter = missingAliasFilesAUFHash.begin(); - while (iter != missingAliasFilesAUFHash.end()) - { - // This type cast is safe. We checked that it's an alias block file earlier. - BlockFilePtr b = iter->second.lock(); - wxASSERT(b); - if (b) { - auto ab = static_cast< AliasBlockFile * > ( &*b ); - - if (action == 2) - { - // silence the blockfiles by yanking the filename - // This is done, eventually, in PCMAliasBlockFile::ReadData() - // and ODPCMAliasBlockFile::ReadData, in the stack of b->Recover(). - // There, if the mAliasedFileName is bad, it zeroes the data. - wxFileNameWrapper dummy; - dummy.Clear(); - ab->ChangeAliasedFileName(std::move(dummy)); - - // If recovery fails for one file, silence it, - // and don't try to recover other files but - // silence them too. GuardedCall will cause an appropriate - // error message for the user. - GuardedCall( - [&] { ab->Recover(); }, - [&] (AudacityException*) { action = 1; } - ); - - nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; - } - - if (action == 1) - // Silence error logging for this block in this session. - ab->SilenceAliasLog(); - } - ++iter; - } - if ((action == 2) && bAutoRecoverMode) - wxLogWarning(_(" Project check replaced missing aliased file(s) with silence.")); - } - } - - // - // MISSING ALIAS (.AUF) AliasBlockFiles - // - // Alias summary regeneration must happen after checking missing aliased files. - // - BlockHash missingAUFHash; // missing (.auf) AliasBlockFiles - this->FindMissingAUFs(missingAUFHash); - if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUFHash.empty()) - { - // In auto-recover mode, we just recreate the alias files, and do not ask user. - // This makes sure the project is complete next time we open it. - if (bAutoRecoverMode) - action = 0; - else - { - wxString msgA = -_("Project check of \"%s\" folder \ -\ndetected %lld missing alias (.auf) blockfile(s). \ -\nAudacity can fully regenerate these files \ -\nfrom the current audio in the project."); - wxString msg; - msg.Printf(msgA, this->projName, (long long) missingAUFHash.size()); - const wxChar *buttons[] = {_("Regenerate alias summary files (safe and recommended)"), - _("Fill in silence for missing display data (this session only)"), - _("Close project immediately with no further changes"), - NULL}; - wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. - action = ShowMultiDialog(msg, _("Warning - Missing Alias Summary File(s)"), buttons); - } - - if (action == 2) - nResult = FSCKstatus_CLOSE_REQ; - else - { - // LL: A progress dialog should probably be used here - BlockHash::iterator iter = missingAUFHash.begin(); - while (iter != missingAUFHash.end()) - { - BlockFilePtr b = iter->second.lock(); - wxASSERT(b); - if (b) { - if(action==0) { - //regenerate from data - // If recovery fails for one file, silence it, - // and don't try to recover other files but - // silence them too. GuardedCall will cause an appropriate - // error message for the user. - GuardedCall( - [&] { - b->Recover(); - nResult |= FSCKstatus_CHANGED; - }, - [&] (AudacityException*) { action = 1; } - ); - } - - if (action==1){ - // Silence error logging for this block in this session. - b->SilenceLog(); - } - } - ++iter; - } - if ((action == 0) && bAutoRecoverMode) - wxLogWarning(_(" Project check regenerated missing alias summary file(s).")); - } - } - - // - // MISSING (.AU) SimpleBlockFiles - // - BlockHash missingAUHash; // missing data (.au) blockfiles - this->FindMissingAUs(missingAUHash); - if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUHash.empty()) - { - // In auto-recover mode, we just always create silent blocks. - // This makes sure the project is complete next time we open it. - if (bAutoRecoverMode) - action = 2; - else - { - wxString msgA = -_("Project check of \"%s\" folder \ -\ndetected %lld missing audio data (.au) blockfile(s), \ -\nprobably due to a bug, system crash, or accidental \ -\ndeletion. There is no way for Audacity to recover \ -\nthese missing files automatically. \ -\n\nIf you choose the first or second option below, \ -\nyou can try to find and restore the missing files \ -\nto their previous location. \ -\n\nNote that for the second option, the waveform \ -\nmay not show silence."); - wxString msg; - msg.Printf(msgA, this->projName, (long long) missingAUHash.size()); - const wxChar *buttons[] = - {_("Close project immediately with no further changes"), - _("Treat missing audio as silence (this session only)"), - _("Replace missing audio with silence (permanent immediately)"), - NULL}; - wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. - action = ShowMultiDialog(msg, _("Warning - Missing Audio Data Block File(s)"), buttons); - } - - if (action == 0) - nResult = FSCKstatus_CLOSE_REQ; - else - { - // LL: A progress dialog should probably be used here - BlockHash::iterator iter = missingAUHash.begin(); - while (iter != missingAUHash.end()) - { - BlockFilePtr b = iter->second.lock(); - wxASSERT(b); - if (b) { - if (action == 2) - { - //regenerate from data - // If recovery fails for one file, silence it, - // and don't try to recover other files but - // silence them too. GuardedCall will cause an appropriate - // error message for the user. - GuardedCall( - [&] { - //regenerate with zeroes - b->Recover(); - nResult |= FSCKstatus_CHANGED; - }, - [&] (AudacityException*) { action = 1; } - ); - } - - if (action == 1) - b->SilenceLog(); - } - ++iter; - } - if ((action == 2) && bAutoRecoverMode) - wxLogWarning(_(" Project check replaced missing audio data block file(s) with silence.")); - } - } - - // - // ORPHAN BLOCKFILES (.au and .auf files that are not in the project.) - // - FilePaths orphanFilePathArray; // orphan .au and .auf files - this->FindOrphanBlockFiles(filePathArray, orphanFilePathArray); - - if ((nResult != FSCKstatus_CLOSE_REQ) && !orphanFilePathArray.empty()) - { - // In auto-recover mode, leave orphan blockfiles alone. - // They will be deleted when project is saved the first time. - if (bAutoRecoverMode) - { - wxLogWarning(_(" Project check ignored orphan block file(s). They will be deleted when project is saved.")); - action = 1; - } - else - { - wxString msgA = -_("Project check of \"%s\" folder \ -\nfound %d orphan block file(s). These files are \ -\nunused by this project, but might belong to \ -other projects. \ -\nThey are doing no harm and are small."); - wxString msg; - msg.Printf(msgA, this->projName, (int)orphanFilePathArray.size()); - - const wxChar *buttons[] = - {_("Continue without deleting; ignore the extra files this session"), - _("Close project immediately with no further changes"), - _("Delete orphan files (permanent immediately)"), - NULL}; - wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. - action = ShowMultiDialog(msg, _("Warning - Orphan Block File(s)"), buttons); - } - - if (action == 1) - nResult = FSCKstatus_CLOSE_REQ; - // Nothing is done if (action == 0). - else if (action == 2) - { - // FSCKstatus_CHANGED was bogus here. - // The files are deleted, so "Undo Project Repair" could not do anything. - // Plus they affect none of the valid tracks, so incorrect to mark them changed, - // and no need for refresh. - // nResult |= FSCKstatus_CHANGED; - for ( const auto &orphan : orphanFilePathArray ) - wxRemoveFile(orphan); - } - } - - if ((nResult != FSCKstatus_CLOSE_REQ) && !ODManager::HasLoadedODFlag()) - { - // Remove any empty directories. - ProgressDialog pProgress - (_("Progress"), - _("Cleaning up unused directories in project data")); - // nDirCount is for updating pProgress. +1 because we may DELETE dirPath. - int nDirCount = RecursivelyCountSubdirs(dirPath) + 1; - RecursivelyRemoveEmptyDirs(dirPath, nDirCount, &pProgress); - } - - // Summarize and flush the log. - if (bForceError || - !missingAliasFilesAUFHash.empty() || - !missingAUFHash.empty() || - !missingAUHash.empty() || - !orphanFilePathArray.empty()) - { - wxLogWarning(_("Project check found file inconsistencies inspecting the loaded project data.")); - wxLog::FlushActive(); // Flush is modal and will clear the log (both desired). - - // In auto-recover mode, we didn't do any ShowMultiDialog calls above, so put up an alert. - if (bAutoRecoverMode) - ::AudacityMessageBox( - _("Project check found file inconsistencies during automatic recovery.\n\nSelect 'Show Log...' in the Help menu to see details."), - _("Warning: Problems in Automatic Recovery"), - wxOK | wxICON_EXCLAMATION); - } - - MissingAliasFilesDialog::SetShouldShow(true); - return nResult; -} - void DirManager::FindMissingAliasFiles( BlockHash& missingAliasFilesAUFHash, // output: (.auf) AliasBlockFiles whose aliased files are missing BlockHash& missingAliasFilesPathHash) // output: full paths of missing aliased files diff --git a/src/DirManager.h b/src/DirManager.h index 0979e1f92..71d30576d 100644 --- a/src/DirManager.h +++ b/src/DirManager.h @@ -19,10 +19,7 @@ class wxFileNameWrapper; class BlockArray; class BlockFile; - -#define FSCKstatus_CLOSE_REQ 0x1 -#define FSCKstatus_CHANGED 0x2 -#define FSCKstatus_SAVE_AUP 0x4 // used in combination with FSCKstatus_CHANGED +class ProgressDialog; using DirHash = std::unordered_map; @@ -44,11 +41,39 @@ enum { class PROFILE_DLL_API DirManager final : public XMLTagHandler { public: + static int RecursivelyEnumerate(const FilePath &dirPath, + FilePaths& filePathArray, // output: all files in dirPath tree + wxString dirspec, + wxString filespec, + bool bFiles, bool bDirs, + int progress_count = 0, + int progress_bias = 0, + ProgressDialog* progress = nullptr); + + static int RecursivelyEnumerateWithProgress(const FilePath &dirPath, + FilePaths& filePathArray, // output: all files in dirPath tree + wxString dirspec, + wxString filespec, + bool bFiles, bool bDirs, + int progress_count, + const wxChar* message); + + static int RecursivelyCountSubdirs( const FilePath &dirPath ); + + static int RecursivelyRemoveEmptyDirs(const FilePath &dirPath, + int nDirCount = 0, + ProgressDialog* pProgress = nullptr); + + static void RecursivelyRemove(const FilePaths& filePathArray, int count, int bias, + int flags, const wxChar* message = nullptr); + // MM: Construct DirManager DirManager(); virtual ~DirManager(); + size_t NumBlockFiles() const { return mBlockFileHash.size(); } + static void SetTempDir(const wxString &_temp) { globaltemp = _temp; } class ProjectSetter @@ -152,13 +177,6 @@ class PROFILE_DLL_API DirManager final : public XMLTagHandler { const wxString &msg, int flags = 0); - // Check the project for errors and possibly prompt user - // bForceError: Always show log error alert even if no errors are found here. - // Important when you know that there are already errors in the log. - // bAutoRecoverMode: Do not show any option dialogs for how to deal with errors found here. - // Too complicated during auto-recover. Just correct problems the "safest" way. - int ProjectFSCK(const bool bForceError, const bool bAutoRecoverMode); - void FindMissingAliasFiles( BlockHash& missingAliasFilesAUFHash, // output: (.auf) AliasBlockFiles whose aliased files are missing BlockHash& missingAliasFilesPathHash); // output: full paths of missing aliased files diff --git a/src/Project.cpp b/src/Project.cpp index 21421ea28..2cc2733d7 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -116,6 +116,7 @@ scroll information. It also has some status flags. #include "Mix.h" #include "NoteTrack.h" #include "Prefs.h" +#include "ProjectFSCK.h" #include "Sequence.h" #include "Snap.h" #include "Tags.h" @@ -3274,7 +3275,7 @@ void AudacityProject::OpenFile(const FilePath &fileNameArg, bool addtohistory) // at this point mFileName != fileName, because when opening a // recovered file mFileName is faked to point to the original file // which has been recovered, not the one in the auto-save folder. - GetDirManager()->ProjectFSCK(err, true); // Correct problems in auto-recover mode. + ::ProjectFSCK(*GetDirManager(), err, true); // Correct problems in auto-recover mode. // PushState calls AutoSave(), so no longer need to do so here. this->PushState(_("Project was recovered"), _("Recover")); @@ -3286,7 +3287,7 @@ void AudacityProject::OpenFile(const FilePath &fileNameArg, bool addtohistory) else { // This is a regular project, check it and ask user - int status = GetDirManager()->ProjectFSCK(err, false); + int status = ::ProjectFSCK(*GetDirManager(), err, false); if (status & FSCKstatus_CLOSE_REQ) { // Vaughan, 2010-08-23: Note this did not do a real close. @@ -3318,7 +3319,7 @@ void AudacityProject::OpenFile(const FilePath &fileNameArg, bool addtohistory) mTrackPanel->Refresh(true); - // Vaughan, 2010-08-20: This was bogus, as all the actions in DirManager::ProjectFSCK + // Vaughan, 2010-08-20: This was bogus, as all the actions in ProjectFSCK // that return FSCKstatus_CHANGED cannot be undone. // this->PushState(_("Project checker repaired file"), _("Project Repair")); diff --git a/src/ProjectFSCK.cpp b/src/ProjectFSCK.cpp index e69de29bb..a8fc187ec 100644 --- a/src/ProjectFSCK.cpp +++ b/src/ProjectFSCK.cpp @@ -0,0 +1,391 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + ProjectFSCK.cpp + + A function that performs consistency checks on the tree of block files + + Paul Licameli split this out of DirManager.cpp + +**********************************************************************/ + +#include "ProjectFSCK.h" + +#include +#include + +#include "BlockFile.h" +#include "DirManager.h" +#include "widgets/ErrorDialog.h" +#include "Internat.h" +#include "MemoryX.h" +#include "MissingAliasFileDialog.h" +#include "ondemand/ODManager.h" +#include "widgets/MultiDialog.h" +#include "widgets/ProgressDialog.h" + +// Check the BlockFiles against the disk state. +// Missing Blockfile data can be regenerated if possible or replaced with silence. +// Orphan blockfiles can be deleted. +// Note that even BlockFiles not referenced by the current savefile (but locked +// by history) will be reflected in the mBlockFileHash, and that's a +// good thing; this is one reason why we use the hash and not the most +// recent savefile. +int ProjectFSCK( + DirManager &dm, const bool bForceError, const bool bAutoRecoverMode) +{ + // In earlier versions of this method, enumerations of errors were + // all done in sequence, then the user was prompted for each type of error. + // The enumerations are now interleaved with prompting, because, for example, + // user choosing to replace missing aliased block files with silence + // needs to put in SilentBlockFiles and DELETE the corresponding auf files, + // so those would then not be cumulated in missingAUFHash. + // We still do the FindX methods outside the conditionals, + // so the log always shows all found errors. + + int action; // choice of action for each type of error + int nResult = 0; + + if (bForceError && !bAutoRecoverMode) + { + // TODO: Replace with more user friendly error message? + /* i18n-hint: The audacity project file is XML and has 'tags' in it, + rather like html tags some stuff. + This error message is about the tags that hold the sequence information. + The error message is confusing to users in English, and could just say + "Found problems with when checking project file." */ + wxString msg = _("Project check read faulty Sequence tags."); + const wxChar *buttons[] = + {_("Close project immediately with no changes"), + _("Continue with repairs noted in log, and check for more errors. This will save the project in its current state, unless you \"Close project immediately\" on further error alerts."), + NULL}; + wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. + action = ShowMultiDialog(msg, _("Warning - Problems Reading Sequence Tags"), buttons); + if (action == 0) + nResult = FSCKstatus_CLOSE_REQ; + else + nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; + } + + FilePaths filePathArray; // *all* files in the project directory/subdirectories + auto dirPath = ( dm.GetDataFilesDir() ); + DirManager::RecursivelyEnumerateWithProgress( + dirPath, + filePathArray, // output: all files in project directory tree + wxEmptyString, // All dirs + wxEmptyString, // All files + true, false, + dm.NumBlockFiles(), // rough guess of how many BlockFiles will be found/processed, for progress + _("Inspecting project file data")); + + // + // MISSING ALIASED AUDIO FILES + // + MissingAliasFilesDialog::SetShouldShow(false); + BlockHash missingAliasFilesAUFHash; // (.auf) AliasBlockFiles whose aliased files are missing + BlockHash missingAliasFilesPathHash; // full paths of missing aliased files + dm.FindMissingAliasFiles(missingAliasFilesAUFHash, missingAliasFilesPathHash); + + if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAliasFilesAUFHash.empty()) + { + // In auto-recover mode, we always create silent blocks, and do not ask user. + // This makes sure the project is complete next time we open it. + if (bAutoRecoverMode) + action = 2; + else + { + wxString msgA = +_("Project check of \"%s\" folder \ +\ndetected %lld missing external audio file(s) \ +\n('aliased files'). There is no way for Audacity \ +\nto recover these files automatically. \ +\n\nIf you choose the first or second option below, \ +\nyou can try to find and restore the missing files \ +\nto their previous location. \ +\n\nNote that for the second option, the waveform \ +\nmay not show silence. \ +\n\nIf you choose the third option, this will save the \ +\nproject in its current state, unless you \"Close \ +\nproject immediately\" on further error alerts."); + wxString msg; + msg.Printf(msgA, dm.GetProjectName(), (long long) missingAliasFilesPathHash.size()); + const wxChar *buttons[] = + {_("Close project immediately with no changes"), + _("Treat missing audio as silence (this session only)"), + _("Replace missing audio with silence (permanent immediately)."), + NULL}; + wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. + action = ShowMultiDialog(msg, _("Warning - Missing Aliased File(s)"), buttons); + } + + if (action == 0) + nResult = FSCKstatus_CLOSE_REQ; + else + { + // LL: A progress dialog should probably be used here + BlockHash::iterator iter = missingAliasFilesAUFHash.begin(); + while (iter != missingAliasFilesAUFHash.end()) + { + // This type cast is safe. We checked that it's an alias block file earlier. + BlockFilePtr b = iter->second.lock(); + wxASSERT(b); + if (b) { + auto ab = static_cast< AliasBlockFile * > ( &*b ); + + if (action == 2) + { + // silence the blockfiles by yanking the filename + // This is done, eventually, in PCMAliasBlockFile::ReadData() + // and ODPCMAliasBlockFile::ReadData, in the stack of b->Recover(). + // There, if the mAliasedFileName is bad, it zeroes the data. + wxFileNameWrapper dummy; + dummy.Clear(); + ab->ChangeAliasedFileName(std::move(dummy)); + + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { ab->Recover(); }, + [&] (AudacityException*) { action = 1; } + ); + + nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; + } + + if (action == 1) + // Silence error logging for this block in this session. + ab->SilenceAliasLog(); + } + ++iter; + } + if ((action == 2) && bAutoRecoverMode) + wxLogWarning(_(" Project check replaced missing aliased file(s) with silence.")); + } + } + + // + // MISSING ALIAS (.AUF) AliasBlockFiles + // + // Alias summary regeneration must happen after checking missing aliased files. + // + BlockHash missingAUFHash; // missing (.auf) AliasBlockFiles + dm.FindMissingAUFs(missingAUFHash); + if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUFHash.empty()) + { + // In auto-recover mode, we just recreate the alias files, and do not ask user. + // This makes sure the project is complete next time we open it. + if (bAutoRecoverMode) + action = 0; + else + { + wxString msgA = +_("Project check of \"%s\" folder \ +\ndetected %lld missing alias (.auf) blockfile(s). \ +\nAudacity can fully regenerate these files \ +\nfrom the current audio in the project."); + wxString msg; + msg.Printf(msgA, dm.GetProjectName(), (long long) missingAUFHash.size()); + const wxChar *buttons[] = {_("Regenerate alias summary files (safe and recommended)"), + _("Fill in silence for missing display data (this session only)"), + _("Close project immediately with no further changes"), + NULL}; + wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. + action = ShowMultiDialog(msg, _("Warning - Missing Alias Summary File(s)"), buttons); + } + + if (action == 2) + nResult = FSCKstatus_CLOSE_REQ; + else + { + // LL: A progress dialog should probably be used here + BlockHash::iterator iter = missingAUFHash.begin(); + while (iter != missingAUFHash.end()) + { + BlockFilePtr b = iter->second.lock(); + wxASSERT(b); + if (b) { + if(action==0) { + //regenerate from data + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { + b->Recover(); + nResult |= FSCKstatus_CHANGED; + }, + [&] (AudacityException*) { action = 1; } + ); + } + + if (action==1){ + // Silence error logging for this block in this session. + b->SilenceLog(); + } + } + ++iter; + } + if ((action == 0) && bAutoRecoverMode) + wxLogWarning(_(" Project check regenerated missing alias summary file(s).")); + } + } + + // + // MISSING (.AU) SimpleBlockFiles + // + BlockHash missingAUHash; // missing data (.au) blockfiles + dm.FindMissingAUs(missingAUHash); + if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUHash.empty()) + { + // In auto-recover mode, we just always create silent blocks. + // This makes sure the project is complete next time we open it. + if (bAutoRecoverMode) + action = 2; + else + { + wxString msgA = +_("Project check of \"%s\" folder \ +\ndetected %lld missing audio data (.au) blockfile(s), \ +\nprobably due to a bug, system crash, or accidental \ +\ndeletion. There is no way for Audacity to recover \ +\nthese missing files automatically. \ +\n\nIf you choose the first or second option below, \ +\nyou can try to find and restore the missing files \ +\nto their previous location. \ +\n\nNote that for the second option, the waveform \ +\nmay not show silence."); + wxString msg; + msg.Printf(msgA, dm.GetProjectName(), (long long) missingAUHash.size()); + const wxChar *buttons[] = + {_("Close project immediately with no further changes"), + _("Treat missing audio as silence (this session only)"), + _("Replace missing audio with silence (permanent immediately)"), + NULL}; + wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. + action = ShowMultiDialog(msg, _("Warning - Missing Audio Data Block File(s)"), buttons); + } + + if (action == 0) + nResult = FSCKstatus_CLOSE_REQ; + else + { + // LL: A progress dialog should probably be used here + BlockHash::iterator iter = missingAUHash.begin(); + while (iter != missingAUHash.end()) + { + BlockFilePtr b = iter->second.lock(); + wxASSERT(b); + if (b) { + if (action == 2) + { + //regenerate from data + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { + //regenerate with zeroes + b->Recover(); + nResult |= FSCKstatus_CHANGED; + }, + [&] (AudacityException*) { action = 1; } + ); + } + + if (action == 1) + b->SilenceLog(); + } + ++iter; + } + if ((action == 2) && bAutoRecoverMode) + wxLogWarning(_(" Project check replaced missing audio data block file(s) with silence.")); + } + } + + // + // ORPHAN BLOCKFILES (.au and .auf files that are not in the project.) + // + FilePaths orphanFilePathArray; // orphan .au and .auf files + dm.FindOrphanBlockFiles(filePathArray, orphanFilePathArray); + + if ((nResult != FSCKstatus_CLOSE_REQ) && !orphanFilePathArray.empty()) + { + // In auto-recover mode, leave orphan blockfiles alone. + // They will be deleted when project is saved the first time. + if (bAutoRecoverMode) + { + wxLogWarning(_(" Project check ignored orphan block file(s). They will be deleted when project is saved.")); + action = 1; + } + else + { + wxString msgA = +_("Project check of \"%s\" folder \ +\nfound %d orphan block file(s). These files are \ +\nunused by this project, but might belong to \ +other projects. \ +\nThey are doing no harm and are small."); + wxString msg; + msg.Printf(msgA, dm.GetProjectName(), (int)orphanFilePathArray.size()); + + const wxChar *buttons[] = + {_("Continue without deleting; ignore the extra files this session"), + _("Close project immediately with no further changes"), + _("Delete orphan files (permanent immediately)"), + NULL}; + wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current. + action = ShowMultiDialog(msg, _("Warning - Orphan Block File(s)"), buttons); + } + + if (action == 1) + nResult = FSCKstatus_CLOSE_REQ; + // Nothing is done if (action == 0). + else if (action == 2) + { + // FSCKstatus_CHANGED was bogus here. + // The files are deleted, so "Undo Project Repair" could not do anything. + // Plus they affect none of the valid tracks, so incorrect to mark them changed, + // and no need for refresh. + // nResult |= FSCKstatus_CHANGED; + for ( const auto &orphan : orphanFilePathArray ) + wxRemoveFile(orphan); + } + } + + if ((nResult != FSCKstatus_CLOSE_REQ) && !ODManager::HasLoadedODFlag()) + { + // Remove any empty directories. + ProgressDialog pProgress + (_("Progress"), + _("Cleaning up unused directories in project data")); + // nDirCount is for updating pProgress. +1 because we may DELETE dirPath. + int nDirCount = DirManager::RecursivelyCountSubdirs(dirPath) + 1; + DirManager::RecursivelyRemoveEmptyDirs(dirPath, nDirCount, &pProgress); + } + + // Summarize and flush the log. + if (bForceError || + !missingAliasFilesAUFHash.empty() || + !missingAUFHash.empty() || + !missingAUHash.empty() || + !orphanFilePathArray.empty()) + { + wxLogWarning(_("Project check found file inconsistencies inspecting the loaded project data.")); + wxLog::FlushActive(); // Flush is modal and will clear the log (both desired). + + // In auto-recover mode, we didn't do any ShowMultiDialog calls above, so put up an alert. + if (bAutoRecoverMode) + ::AudacityMessageBox( + _("Project check found file inconsistencies during automatic recovery.\n\nSelect 'Show Log...' in the Help menu to see details."), + _("Warning: Problems in Automatic Recovery"), + wxOK | wxICON_EXCLAMATION); + } + + MissingAliasFilesDialog::SetShouldShow(true); + return nResult; +} diff --git a/src/ProjectFSCK.h b/src/ProjectFSCK.h index e69de29bb..8c349a3ca 100644 --- a/src/ProjectFSCK.h +++ b/src/ProjectFSCK.h @@ -0,0 +1,32 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + ProjectFSCK.h + + A function that performs consistency checks on the tree of block files + + Paul Licameli split this out of DirManager.h + +**********************************************************************/ + +#ifndef __AUDACITY_PROJECT_FSCK__ +#define __AUDACITY_PROJECT_FSCK__ + +class DirManager; + +enum : unsigned { + FSCKstatus_CLOSE_REQ = 0x1, + FSCKstatus_CHANGED = 0x2, + FSCKstatus_SAVE_AUP = 0x4, // used in combination with FSCKstatus_CHANGED +}; + +// Check the project for errors and possibly prompt user +// bForceError: Always show log error alert even if no errors are found here. +// Important when you know that there are already errors in the log. +// bAutoRecoverMode: Do not show any option dialogs for how to deal with errors found here. +// Too complicated during auto-recover. Just correct problems the "safest" way. +int ProjectFSCK( + DirManager &dm, const bool bForceError, const bool bAutoRecoverMode); + +#endif diff --git a/src/blockfile/LegacyAliasBlockFile.cpp b/src/blockfile/LegacyAliasBlockFile.cpp index b08534895..98260a336 100644 --- a/src/blockfile/LegacyAliasBlockFile.cpp +++ b/src/blockfile/LegacyAliasBlockFile.cpp @@ -82,7 +82,7 @@ void LegacyAliasBlockFile::SaveXML(XMLWriter &xmlFile) // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). BlockFilePtr LegacyAliasBlockFile::BuildFromXML(const FilePath &projDir, const wxChar **attrs) { wxFileNameWrapper summaryFileName; diff --git a/src/blockfile/LegacyBlockFile.cpp b/src/blockfile/LegacyBlockFile.cpp index 489b50d51..32975958a 100644 --- a/src/blockfile/LegacyBlockFile.cpp +++ b/src/blockfile/LegacyBlockFile.cpp @@ -218,7 +218,7 @@ void LegacyBlockFile::SaveXML(XMLWriter &xmlFile) // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). /// static BlockFilePtr LegacyBlockFile::BuildFromXML(const FilePath &projDir, const wxChar **attrs, size_t len, sampleFormat format) diff --git a/src/blockfile/ODDecodeBlockFile.cpp b/src/blockfile/ODDecodeBlockFile.cpp index 48ea2b441..e2d498585 100644 --- a/src/blockfile/ODDecodeBlockFile.cpp +++ b/src/blockfile/ODDecodeBlockFile.cpp @@ -237,7 +237,7 @@ void ODDecodeBlockFile::SaveXML(XMLWriter &xmlFile) /// Also schedules the ODDecodeBlockFile for OD loading. // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). BlockFilePtr ODDecodeBlockFile::BuildFromXML(DirManager &dm, const wxChar **attrs) { wxFileNameWrapper summaryFileName; diff --git a/src/blockfile/ODPCMAliasBlockFile.cpp b/src/blockfile/ODPCMAliasBlockFile.cpp index b0ce135bb..7b698a40e 100644 --- a/src/blockfile/ODPCMAliasBlockFile.cpp +++ b/src/blockfile/ODPCMAliasBlockFile.cpp @@ -271,7 +271,7 @@ void ODPCMAliasBlockFile::SaveXML(XMLWriter &xmlFile) /// Does not schedule the ODPCMAliasBlockFile for OD loading. Client code must do this. // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). BlockFilePtr ODPCMAliasBlockFile::BuildFromXML(DirManager &dm, const wxChar **attrs) { wxFileNameWrapper summaryFileName; diff --git a/src/blockfile/PCMAliasBlockFile.cpp b/src/blockfile/PCMAliasBlockFile.cpp index 7761e5429..2fca965bd 100644 --- a/src/blockfile/PCMAliasBlockFile.cpp +++ b/src/blockfile/PCMAliasBlockFile.cpp @@ -114,7 +114,7 @@ void PCMAliasBlockFile::SaveXML(XMLWriter &xmlFile) // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). BlockFilePtr PCMAliasBlockFile::BuildFromXML(DirManager &dm, const wxChar **attrs) { wxFileNameWrapper summaryFileName; diff --git a/src/blockfile/SilentBlockFile.cpp b/src/blockfile/SilentBlockFile.cpp index df9c721c2..6ace01f3a 100644 --- a/src/blockfile/SilentBlockFile.cpp +++ b/src/blockfile/SilentBlockFile.cpp @@ -53,7 +53,7 @@ void SilentBlockFile::SaveXML(XMLWriter &xmlFile) // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). /// static BlockFilePtr SilentBlockFile::BuildFromXML(DirManager & WXUNUSED(dm), const wxChar **attrs) { diff --git a/src/blockfile/SimpleBlockFile.cpp b/src/blockfile/SimpleBlockFile.cpp index 9c2b9253c..b5e284051 100644 --- a/src/blockfile/SimpleBlockFile.cpp +++ b/src/blockfile/SimpleBlockFile.cpp @@ -433,7 +433,7 @@ void SimpleBlockFile::SaveXML(XMLWriter &xmlFile) // BuildFromXML methods should always return a BlockFile, not NULL, // even if the result is flawed (e.g., refers to nonexistent file), -// as testing will be done in DirManager::ProjectFSCK(). +// as testing will be done in ProjectFSCK(). /// static BlockFilePtr SimpleBlockFile::BuildFromXML(DirManager &dm, const wxChar **attrs) {