Audacity 3.2.0
ProjectFSCK.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ProjectFSCK.cpp
6
7 A function that performs consistency checks on the tree of block files
8
9 Paul Licameli split this out of DirManager.cpp
10
11**********************************************************************/
12
13#include "ProjectFSCK.h"
14
15#include <wx/log.h>
16
17#include "BlockFile.h"
18#include "DirManager.h"
19#include "widgets/AudacityMessageBox.h"
20#include "Internat.h"
21#include "MemoryX.h"
22#include "widgets/MultiDialog.h"
23#include "widgets/ProgressDialog.h"
24
25// Check the BlockFiles against the disk state.
26// Missing Blockfile data can be regenerated if possible or replaced with silence.
27// Orphan blockfiles can be deleted.
28// Note that even BlockFiles not referenced by the current savefile (but locked
29// by history) will be reflected in the mBlockFileHash, and that's a
30// good thing; this is one reason why we use the hash and not the most
31// recent savefile.
33 DirManager &dm, const bool bForceError, const bool bAutoRecoverMode)
34{
35#pragma message( "====================================================================")
36#pragma message( "Don\'t forget to redo ProjectFSCK")
37#pragma message( "====================================================================")
38 // In earlier versions of this method, enumerations of errors were
39 // all done in sequence, then the user was prompted for each type of error.
40 // The enumerations are now interleaved with prompting, because, for example,
41 // user choosing to replace missing aliased block files with silence
42 // needs to put in SilentBlockFiles and DELETE the corresponding auf files,
43 // so those would then not be cumulated in missingAUFHash.
44 // We still do the FindX methods outside the conditionals,
45 // so the log always shows all found errors.
46
47 int action; // choice of action for each type of error
48 int nResult = 0;
49
50 if (bForceError && !bAutoRecoverMode)
51 {
52 // TODO: Replace with more user friendly error message?
53 /* i18n-hint: The audacity project file is XML and has 'tags' in it,
54 rather like html tags <something>some stuff</something>.
55 This error message is about the tags that hold the sequence information.
56 The error message is confusing to users in English, and could just say
57 "Found problems with <sequence> when checking project file." */
58 auto msg = XO("Project check read faulty Sequence tags.");
59 const TranslatableStrings buttons{
60 XO("Close project immediately with no changes"),
61 XO("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.")
62 };
63 wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
64 action = ShowMultiDialog(msg,
65 XO("Warning - Problems Reading Sequence Tags"),
66 buttons,"");
67 if (action == 0)
68 nResult = FSCKstatus_CLOSE_REQ;
69 else
71 }
72#if 0
73 FilePaths filePathArray; // *all* files in the project directory/subdirectories
74 auto dirPath = ( dm.GetDataFilesDir() );
75 DirManager::RecursivelyEnumerateWithProgress(
76 dirPath,
77 filePathArray, // output: all files in project directory tree
78 wxEmptyString, // All dirs
79 wxEmptyString, // All files
80 true, false,
81 dm.NumBlockFiles(), // rough guess of how many BlockFiles will be found/processed, for progress
82 XO("Inspecting project file data"));
83
84 //
85 // MISSING ALIASED AUDIO FILES
86 //
87 MissingAliasFilesDialog::SetShouldShow(false);
88 BlockHash missingAliasFilesAUFHash; // (.auf) AliasBlockFiles whose aliased files are missing
89 BlockHash missingAliasFilesPathHash; // full paths of missing aliased files
90 dm.FindMissingAliasFiles(missingAliasFilesAUFHash, missingAliasFilesPathHash);
91
92 if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAliasFilesAUFHash.empty())
93 {
94 // In auto-recover mode, we always create silent blocks, and do not ask user.
95 // This makes sure the project is complete next time we open it.
96 if (bAutoRecoverMode)
97 action = 2;
98 else
99 {
100 auto msg =
101XO("Project check of \"%s\" folder \
102\ndetected %lld missing external audio file(s) \
103\n('aliased files'). There is no way for Audacity \
104\nto recover these files automatically. \
105\n\nIf you choose the first or second option below, \
106\nyou can try to find and restore the missing files \
107\nto their previous location. \
108\n\nNote that for the second option, the waveform \
109\nmay not show silence. \
110\n\nIf you choose the third option, this will save the \
111\nproject in its current state, unless you \"Close \
112\nproject immediately\" on further error alerts.")
113 .Format(
114 dm.GetProjectName(),
115 (long long) missingAliasFilesPathHash.size() );
116 const TranslatableStrings buttons{
117 XO("Close project immediately with no changes"),
118 XO("Treat missing audio as silence (this session only)"),
119 XO("Replace missing audio with silence (permanent immediately)."),
120 };
121 wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
122 action = ShowMultiDialog(msg,
123 XO("Warning - Missing Aliased File(s)"),
124 buttons,
125 "");
126 }
127
128 if (action == 0)
129 nResult = FSCKstatus_CLOSE_REQ;
130 else
131 {
132 // LL: A progress dialog should probably be used here
133 BlockHash::iterator iter = missingAliasFilesAUFHash.begin();
134 while (iter != missingAliasFilesAUFHash.end())
135 {
136 // This type cast is safe. We checked that it's an alias block file earlier.
137 BlockFilePtr b = iter->second.lock();
138 wxASSERT(b);
139 if (b) {
140 auto ab = static_cast< AliasBlockFile * > ( &*b );
141
142 if (action == 2)
143 {
144 // silence the blockfiles by yanking the filename
145 // This is done, eventually, in PCMAliasBlockFile::ReadData(),
146 // in the stack of b->Recover().
147 // There, if the mAliasedFileName is bad, it zeroes the data.
148 wxFileNameWrapper dummy;
149 dummy.Clear();
150 ab->ChangeAliasedFileName(std::move(dummy));
151
152 // If recovery fails for one file, silence it,
153 // and don't try to recover other files but
154 // silence them too. GuardedCall will cause an appropriate
155 // error message for the user.
157 [&] { ab->Recover(); },
158 [&] (AudacityException*) { action = 1; }
159 );
160
162 }
163
164 if (action == 1)
165 // Silence error logging for this block in this session.
166 ab->SilenceAliasLog();
167 }
168 ++iter;
169 }
170 if ((action == 2) && bAutoRecoverMode)
171 wxLogWarning(wxT(" Project check replaced missing aliased file(s) with silence."));
172 }
173 }
174
175 //
176 // MISSING ALIAS (.AUF) AliasBlockFiles
177 //
178 // Alias summary regeneration must happen after checking missing aliased files.
179 //
180 BlockHash missingAUFHash; // missing (.auf) AliasBlockFiles
181 dm.FindMissingAUFs(missingAUFHash);
182 if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUFHash.empty())
183 {
184 // In auto-recover mode, we just recreate the alias files, and do not ask user.
185 // This makes sure the project is complete next time we open it.
186 if (bAutoRecoverMode)
187 action = 0;
188 else
189 {
190 auto msg =
191XO("Project check of \"%s\" folder \
192\ndetected %lld missing alias (.auf) blockfile(s). \
193\nAudacity can fully regenerate these files \
194\nfrom the current audio in the project.")
195 .Format(
196 dm.GetProjectName(), (long long) missingAUFHash.size() );
197 const TranslatableStrings buttons{
198 XO("Regenerate alias summary files (safe and recommended)"),
199 XO("Fill in silence for missing display data (this session only)"),
200 XO("Close project immediately with no further changes"),
201 };
202 wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
203 action = ShowMultiDialog(msg,
204 XO("Warning - Missing Alias Summary File(s)"),
205 buttons,
206 "");
207 }
208
209 if (action == 2)
210 nResult = FSCKstatus_CLOSE_REQ;
211 else
212 {
213 // LL: A progress dialog should probably be used here
214 BlockHash::iterator iter = missingAUFHash.begin();
215 while (iter != missingAUFHash.end())
216 {
217 BlockFilePtr b = iter->second.lock();
218 wxASSERT(b);
219 if (b) {
220 if(action==0) {
221 //regenerate from data
222 // If recovery fails for one file, silence it,
223 // and don't try to recover other files but
224 // silence them too. GuardedCall will cause an appropriate
225 // error message for the user.
227 [&] {
228 b->Recover();
229 nResult |= FSCKstatus_CHANGED;
230 },
231 [&] (AudacityException*) { action = 1; }
232 );
233 }
234
235 if (action==1){
236 // Silence error logging for this block in this session.
237 b->SilenceLog();
238 }
239 }
240 ++iter;
241 }
242 if ((action == 0) && bAutoRecoverMode)
243 wxLogWarning(wxT(" Project check regenerated missing alias summary file(s)."));
244 }
245 }
246
247 //
248 // MISSING (.AU) SimpleBlockFiles
249 //
250 BlockHash missingAUHash; // missing data (.au) blockfiles
251 dm.FindMissingAUs(missingAUHash);
252 if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUHash.empty())
253 {
254 // In auto-recover mode, we just always create silent blocks.
255 // This makes sure the project is complete next time we open it.
256 if (bAutoRecoverMode)
257 action = 2;
258 else
259 {
260 auto msg =
261XO("Project check of \"%s\" folder \
262\ndetected %lld missing audio data (.au) blockfile(s), \
263\nprobably due to a bug, system crash, or accidental \
264\ndeletion. There is no way for Audacity to recover \
265\nthese missing files automatically. \
266\n\nIf you choose the first or second option below, \
267\nyou can try to find and restore the missing files \
268\nto their previous location. \
269\n\nNote that for the second option, the waveform \
270\nmay not show silence.")
271 .Format(
272 dm.GetProjectName(), (long long) missingAUHash.size() );
273 const TranslatableStrings buttons{
274 XO("Close project immediately with no further changes"),
275 XO("Treat missing audio as silence (this session only)"),
276 XO("Replace missing audio with silence (permanent immediately)"),
277 };
278 wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
279 action = ShowMultiDialog(msg,
280 XO("Warning - Missing Audio Data Block File(s)"),
281 buttons,
282 "Warning_-_Missing_Audio_Data_Block_Files");
283 }
284
285 if (action == 0)
286 nResult = FSCKstatus_CLOSE_REQ;
287 else
288 {
289 // LL: A progress dialog should probably be used here
290 BlockHash::iterator iter = missingAUHash.begin();
291 while (iter != missingAUHash.end())
292 {
293 BlockFilePtr b = iter->second.lock();
294 wxASSERT(b);
295 if (b) {
296 if (action == 2)
297 {
298 //regenerate from data
299 // If recovery fails for one file, silence it,
300 // and don't try to recover other files but
301 // silence them too. GuardedCall will cause an appropriate
302 // error message for the user.
304 [&] {
305 //regenerate with zeroes
306 b->Recover();
307 nResult |= FSCKstatus_CHANGED;
308 },
309 [&] (AudacityException*) { action = 1; }
310 );
311 }
312
313 if (action == 1)
314 b->SilenceLog();
315 }
316 ++iter;
317 }
318 if ((action == 2) && bAutoRecoverMode)
319 wxLogWarning(wxT(" Project check replaced missing audio data block file(s) with silence."));
320 }
321 }
322
323 //
324 // ORPHAN BLOCKFILES (.au and .auf files that are not in the project.)
325 //
326 FilePaths orphanFilePathArray; // orphan .au and .auf files
327 dm.FindOrphanBlockFiles(filePathArray, orphanFilePathArray);
328
329 if ((nResult != FSCKstatus_CLOSE_REQ) && !orphanFilePathArray.empty())
330 {
331 // In auto-recover mode, leave orphan blockfiles alone.
332 // They will be deleted when project is saved the first time.
333 if (bAutoRecoverMode)
334 {
335 wxLogWarning(wxT(" Project check ignored orphan block file(s). They will be deleted when project is saved."));
336 action = 1;
337 }
338 else
339 {
340 auto msg =
341XO("Project check of \"%s\" folder \
342\nfound %d orphan block file(s). These files are \
343\nunused by this project, but might belong to \
344other projects. \
345\nThey are doing no harm and are small.")
346 .Format( dm.GetProjectName(), (int)orphanFilePathArray.size() );
347
348 const TranslatableStrings buttons{
349 XO("Continue without deleting; ignore the extra files this session"),
350 XO("Close project immediately with no further changes"),
351 XO("Delete orphan files (permanent immediately)"),
352 };
353 wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
354 action = ShowMultiDialog(msg,
355 XO("Warning - Orphan Block File(s)"),
356 buttons,
357 "Warning_-_Orphan_Block_Files"
358 );
359 }
360
361 if (action == 1)
362 nResult = FSCKstatus_CLOSE_REQ;
363 // Nothing is done if (action == 0).
364 else if (action == 2)
365 {
366 // FSCKstatus_CHANGED was bogus here.
367 // The files are deleted, so "Undo Project Repair" could not do anything.
368 // Plus they affect none of the valid tracks, so incorrect to mark them changed,
369 // and no need for refresh.
370 // nResult |= FSCKstatus_CHANGED;
371 for ( const auto &orphan : orphanFilePathArray )
372 wxRemoveFile(orphan);
373 }
374 }
375
376 if (nResult != FSCKstatus_CLOSE_REQ)
377 {
378 // Remove any empty directories.
379 ProgressDialog pProgress(
380 XO("Progress"),
381 XO("Cleaning up unused directories in project data"));
382 // nDirCount is for updating pProgress. +1 because we may DELETE dirPath.
383 int nDirCount = DirManager::RecursivelyCountSubdirs(dirPath) + 1;
384 DirManager::RecursivelyRemoveEmptyDirs(dirPath, nDirCount, &pProgress);
385 }
386
387 // Summarize and flush the log.
388 if (bForceError ||
389 !missingAliasFilesAUFHash.empty() ||
390 !missingAUFHash.empty() ||
391 !missingAUHash.empty() ||
392 !orphanFilePathArray.empty())
393 {
394 wxLogWarning(wxT("Project check found file inconsistencies inspecting the loaded project data."));
395 wxLog::FlushActive(); // Flush is modal and will clear the log (both desired).
396
397 // In auto-recover mode, we didn't do any ShowMultiDialog calls above, so put up an alert.
398 if (bAutoRecoverMode)
400 XO(
401"Project check found file inconsistencies during automatic recovery.\n\nSelect 'Help > Diagnostics > Show Log...' to see details."),
402 XO("Warning: Problems in Automatic Recovery"),
403 wxOK | wxICON_EXCLAMATION);
404 }
405
406 MissingAliasFilesDialog::SetShouldShow(true);
407#endif
408 return nResult;
409}
wxT("CloseDown"))
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
XO("Cut/Copy/Paste")
int ProjectFSCK(DirManager &dm, const bool bForceError, const bool bAutoRecoverMode)
Definition: ProjectFSCK.cpp:32
@ FSCKstatus_CHANGED
Definition: ProjectFSCK.h:20
@ FSCKstatus_CLOSE_REQ
Definition: ProjectFSCK.h:19
@ FSCKstatus_SAVE_AUP
Definition: ProjectFSCK.h:21
std::vector< TranslatableString > TranslatableStrings
Base class for exceptions specially processed by the application.
ProgressDialog Class.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
int ShowMultiDialog(const TranslatableString &message, const TranslatableString &title, const TranslatableStrings &buttons, const ManualPageID &helpPage, const TranslatableString &boxMsg, bool log)
Display a dialog with radio buttons.
Definition: BasicUI.h:367