Audacity  2.2.2
DirManager.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4  Audacity(R) is copyright (c) 1999-2008 Audacity Team.
5  License: GPL v2. See License.txt.
6 
7  DirManager.cpp
8 
9  Dominic Mazzoni
10  Matt Brubeck
11  Michael Chinen
12  James Crook
13  Al Dimond
14  Brian Gunlogson
15  Josh Haberman
16  Vaughan Johnson
17  Leland Lucius
18  Monty
19  Markus Meyer
20 
21 *******************************************************************//*******************************************************************/
62 
63 
64 #include "Audacity.h"
65 #include "DirManager.h"
66 #include "MemoryX.h"
67 
68 #include <time.h> // to use time() for srand()
69 
70 #include <wx/defs.h>
71 #include <wx/app.h>
72 #include <wx/dir.h>
73 #include <wx/log.h>
74 #include <wx/filefn.h>
75 #include <wx/hash.h>
76 #include <wx/progdlg.h>
77 #include <wx/timer.h>
78 #include <wx/intl.h>
79 #include <wx/file.h>
80 #include <wx/filename.h>
81 #include <wx/object.h>
82 
83 // chmod
84 #ifdef __UNIX__
85 #include <sys/types.h>
86 #include <sys/stat.h>
87 #endif
88 
89 #include "AudacityApp.h"
90 #include "AudacityException.h"
91 #include "BlockFile.h"
92 #include "FileException.h"
93 #include "FileNames.h"
101 #include "InconsistencyException.h"
102 #include "Internat.h"
103 #include "Project.h"
104 #include "Prefs.h"
105 #include "Sequence.h"
106 #include "widgets/Warning.h"
107 #include "widgets/MultiDialog.h"
108 #include "widgets/ErrorDialog.h"
109 
110 #include "ondemand/ODManager.h"
111 
112 #include "Track.h"
113 
114 #if defined(__WXMAC__)
115 #include <mach/mach.h>
116 #include <mach/vm_statistics.h>
117 #endif
118 
119 
120 wxMemorySize GetFreeMemory()
121 {
122  wxMemorySize avail;
123 
124 #if defined(__WXMAC__)
125  mach_port_t port = mach_host_self();
126  mach_msg_type_number_t cnt = HOST_VM_INFO_COUNT;
127  vm_statistics_data_t stats;
128  vm_size_t pagesize = 0;
129 
130  memset(&stats, 0, sizeof(stats));
131 
132  host_page_size(port, &pagesize);
133  host_statistics(port, HOST_VM_INFO, (host_info_t) &stats, &cnt);
134  avail = stats.free_count * pagesize;
135 #else
136  avail = wxGetFreeMemory();
137 #endif
138 
139  return avail;
140 }
141 
142 //
143 // local helper functions for subdirectory traversal
144 //
145 
146 // Behavior of RecursivelyEnumerate is tailored to our uses and not
147 // entirely straightforward. We use it only for recursing into
148 // Audacity projects, but beware that it may be applied to a directory
149 // that contains other things too, for example a temp directory.
150 // It recurses depth-first from the passed-
151 // in directory into its subdirs according to optional dirspec
152 // pattern, building a list of directories and (optionally) files
153 // in the listed order.
154 // The dirspec is not applied to subdirs of subdirs.
155 // The filespec is applied to all files in subdirectories.
156 // Files in the passed-in directory will not be
157 // enumerated. Also, the passed-in directory is the last entry added
158 // to the list.
159 // JKC: Using flag wxDIR_NO_FOLLOW to NOT follow symbolic links.
160 // Directories and files inside a project should never be symbolic
161 // links, so if we find one, do not follow it.
162 static int RecursivelyEnumerate(wxString dirPath,
163  wxArrayString& filePathArray, // output: all files in dirPath tree
164  wxString dirspec,
165  wxString filespec,
166  bool bFiles, bool bDirs,
167  int progress_count = 0,
168  int progress_bias = 0,
169  ProgressDialog* progress = NULL)
170 {
171  int count=0;
172  bool cont;
173 
174  wxDir dir(dirPath);
175  if(dir.IsOpened()){
176  wxString name;
177 
178  // Don't DELETE files from a selective top level, e.g. if handed "projects*" as the
179  // directory specifier.
180  if (bFiles && dirspec.IsEmpty() ){
181  cont= dir.GetFirst(&name, filespec, wxDIR_FILES | wxDIR_HIDDEN | wxDIR_NO_FOLLOW);
182  while ( cont ){
183  wxString filepath = dirPath + wxFILE_SEP_PATH + name;
184 
185  count++;
186  filePathArray.Add(filepath);
187 
188  cont = dir.GetNext(&name);
189 
190  if (progress)
191  progress->Update(count + progress_bias,
192  progress_count);
193  }
194  }
195 
196  cont= dir.GetFirst(&name, dirspec, wxDIR_DIRS | wxDIR_NO_FOLLOW);
197  while ( cont ){
198  wxString subdirPath = dirPath + wxFILE_SEP_PATH + name;
199  count += RecursivelyEnumerate(
200  subdirPath, filePathArray, wxEmptyString,filespec,
201  bFiles, bDirs,
202  progress_count, count + progress_bias,
203  progress);
204  cont = dir.GetNext(&name);
205  }
206  }
207 
208  if (bDirs) {
209  filePathArray.Add(dirPath);
210  count++;
211  }
212 
213  return count;
214 }
215 
216 static int RecursivelyEnumerateWithProgress(wxString dirPath,
217  wxArrayString& filePathArray, // output: all files in dirPath tree
218  wxString dirspec,
219  wxString filespec,
220  bool bFiles, bool bDirs,
221  int progress_count,
222  const wxChar* message)
223 {
224  Maybe<ProgressDialog> progress{};
225 
226  if (message)
227  progress.create( _("Progress"), message );
228 
229  int count = RecursivelyEnumerate(
230  dirPath, filePathArray, dirspec,filespec,
231  bFiles, bDirs,
232  progress_count, 0,
233  progress.get());
234 
235  return count;
236 }
237 
238 static int RecursivelyCountSubdirs(wxString dirPath)
239 {
240  bool bContinue;
241  int nCount = 0;
242  wxDir dir(dirPath);
243  if (dir.IsOpened() && dir.HasSubDirs())
244  {
245  wxString name;
246  bContinue = dir.GetFirst(&name, wxEmptyString, wxDIR_DIRS);
247  while (bContinue)
248  {
249  nCount++;
250  wxString subdirPath = dirPath + wxFILE_SEP_PATH + name;
251  nCount += RecursivelyCountSubdirs(subdirPath);
252  bContinue = dir.GetNext(&name);
253  }
254  }
255  return nCount;
256 }
257 
258 static int RecursivelyRemoveEmptyDirs(wxString dirPath,
259  int nDirCount = 0,
260  ProgressDialog* pProgress = NULL)
261 {
262  bool bContinue;
263  wxDir dir(dirPath);
264  int nCount = 0;
265  if (dir.IsOpened())
266  {
267  if (dir.HasSubDirs())
268  {
269  wxString name;
270  bContinue = dir.GetFirst(&name, wxEmptyString, wxDIR_DIRS);
271  while (bContinue)
272  {
273  wxString subdirPath = dirPath + wxFILE_SEP_PATH + name;
274  nCount += RecursivelyRemoveEmptyDirs(subdirPath, nDirCount, pProgress);
275  bContinue = dir.GetNext(&name);
276  }
277  }
278  // Have to recheck dir.HasSubDirs() again, in case they all were deleted in recursive calls.
279  if (!dir.HasSubDirs() && !dir.HasFiles() && (dirPath.Right(5) != wxT("_data")))
280  {
281  // No subdirs or files. It's empty so DELETE it.
282  // Vaughan, 2010-07-07:
283  // Note that, per http://src.chromium.org/svn/trunk/src/base/file_util_win.cc, among others,
284  // "Some versions of Windows return ERROR_FILE_NOT_FOUND (0x2) when deleting
285  // an empty directory..." Supposedly fixed in Vista and up.
286  // I discovered this on WinXP. I tried several other Windows SDK functions (e.g., _rmdir
287  // and RemoveDirectory), and they all give same results.
288  // I noticed dirs get deleted in RecursivelyRemove, maybe because it doesn't
289  // consider whether the path is a directory or a file and wxRemoveFile()'s it first.
290  // Tried it here on WinXP, but no joy. Leave the code in case it works on other Win systems.
291  #ifdef __WXMSW__
292  ::wxRemoveFile(dirPath);
293  #endif
294  ::wxRmdir(dirPath);
295  }
296  nCount++; // Count dirPath in progress.
297  if (pProgress)
298  pProgress->Update(nCount, nDirCount);
299  }
300  return nCount;
301 }
302 
303 static void RecursivelyRemove(wxArrayString& filePathArray, int count, int bias,
304  int flags, const wxChar* message = NULL)
305 {
306  bool bFiles= (flags & kCleanFiles) != 0;
307  bool bDirs = (flags & kCleanDirs) != 0;
308  bool bDirsMustBeEmpty = (flags & kCleanDirsOnlyIfEmpty) != 0;
309  Maybe<ProgressDialog> progress{};
310 
311 
312  if (message)
313  progress.create( _("Progress"), message );
314 
315  auto nn = filePathArray.size();
316  for (unsigned int i = 0; i < nn; i++) {
317  const wxChar *file = filePathArray[i];
318  if (bFiles)
319  ::wxRemoveFile(file);
320  if (bDirs) {
321  // continue will go to the next item, and skip
322  // attempting to delete the directory.
323  if( bDirsMustBeEmpty ){
324  wxDir dir( file );
325  if( !dir.IsOpened() )
326  continue;
327  if( dir.HasFiles() )
328  continue;
329  if( dir.HasSubDirs() )
330  continue;
331  }
332 
333 #ifdef __WXMSW__
334  if (!bFiles)
335  ::wxRemoveFile(file); // See note above about wxRmdir sometimes incorrectly failing on Windows.
336 #endif
337 
338  if (! ::wxRmdir(file) ) {
339  wxDir dir(file);
340  if(dir.IsOpened()) {
341  wxLogMessage(file + wxString(" still contains:"));
342  wxString name;
343  auto cont = dir.GetFirst(&name);
344  while ( cont ) {
345  wxLogMessage(file + wxString(wxFILE_SEP_PATH) + name );
346  cont = dir.GetNext(&name);
347  }
348  }
349  else
350  wxLogMessage(wxString("Can't enumerate directory ") + file);
351  }
352  }
353  if (progress)
354  progress->Update(i + bias, count);
355  }
356 }
357 
358 
359 //
360 // DirManager
361 //
362 
363 // Static class variables
364 wxString DirManager::globaltemp;
367 
368 
370 {
371  wxLogDebug(wxT("DirManager: Created new instance."));
372 
374 
375  // Seed the random number generator.
376  // this need not be strictly uniform or random, but it should give
377  // unclustered numbers
378  srand(time(NULL));
379 
380  // Set up local temp subdir
381  // Previously, Audacity just named project temp directories "project0",
382  // "project1" and so on. But with the advent of recovery code, we need a
383  // unique name even after a crash. So we create a random project index
384  // and make sure it is not used already. This will not pose any performance
385  // penalties as long as the number of open Audacity projects is much
386  // lower than RAND_MAX.
387  do {
388  mytemp = globaltemp + wxFILE_SEP_PATH +
389  wxString::Format(wxT("project%d"), rand());
390  } while (wxDirExists(mytemp));
391 
392  numDirManagers++;
393 
394  projPath = wxT("");
395  projName = wxT("");
396 
397  mLoadingTarget = NULL;
398  mLoadingTargetIdx = 0;
399  mMaxSamples = ~size_t(0);
400 
401  // toplevel pool hash is fully populated to begin
402  {
403  // We can bypass the accessor function while initializing
404  auto &balanceInfo = mBalanceInfo;
405  auto &dirTopPool = balanceInfo.dirTopPool;
406  for(int i = 0; i < 256; ++i)
407  dirTopPool[i] = 0;
408  }
409 
410  // Make sure there is plenty of space for temp files
411  wxLongLong freeSpace = 0;
412  if (wxGetDiskSpace(globaltemp, NULL, &freeSpace)) {
413  if (freeSpace < wxLongLong(wxLL(100 * 1048576))) {
414  ShowWarningDialog(NULL, wxT("DiskSpaceWarning"),
415  _("There is very little free disk space left on this volume.\nPlease select another temporary directory in Preferences."));
416  }
417  }
418 }
419 
421 {
422  numDirManagers--;
423  if (numDirManagers == 0) {
424  CleanTempDir();
425  //::wxRmdir(temp);
426  } else if( projFull.IsEmpty() && !mytemp.IsEmpty()) {
427  CleanDir(mytemp, wxEmptyString, ".DS_Store", _("Cleaning project temporary files"), kCleanTopDirToo | kCleanDirsOnlyIfEmpty );
428  }
429 }
430 
431 
432 // static
433 // This is quite a dangerous function. In the temp dir it will DELETE every directory
434 // recursively, that has 'project*' as the name - EVEN if it happens not to be an Audacity
435 // project but just something else called project.
437 {
438  // with default flags (none) this does not clean the top directory, and may remove non-empty
439  // directories.
440  CleanDir(globaltemp, wxT("project*"), wxEmptyString, _("Cleaning up temporary files"));
441 }
442 
443 // static
445  const wxString &path,
446  const wxString &dirSpec,
447  const wxString &fileSpec,
448  const wxString &msg,
449  int flags)
450 {
452  return; // do nothing
453 
454  wxArrayString filePathArray, dirPathArray;
455 
456  int countFiles =
457  RecursivelyEnumerate(path, filePathArray, dirSpec, fileSpec, true, false);
458  int countDirs =
459  RecursivelyEnumerate(path, dirPathArray, dirSpec, fileSpec, false, true);
460 
461  // Subtract 1 because we don't want to DELETE the global temp directory,
462  // which this will find and list last.
463  if ((flags & kCleanTopDirToo)==0) {
464  // Remove the globaltemp itself from the array so that it is not deleted.
465  --countDirs;
466  dirPathArray.resize(countDirs);
467  }
468 
469  auto count = countFiles + countDirs;
470  if (count == 0)
471  return;
472 
473  RecursivelyRemove(filePathArray, count, 0, flags | kCleanFiles, msg);
474  RecursivelyRemove(dirPathArray, count, countFiles, flags | kCleanDirs, msg);
475 }
476 
477 bool DirManager::SetProject(wxString& newProjPath, wxString& newProjName, const bool bCreate)
478 {
479  bool moving = true;
480  wxString oldPath = this->projPath;
481  wxString oldName = this->projName;
482  wxString oldFull = projFull;
483  wxString oldLoc = projFull;
484  if (oldLoc == wxT(""))
485  oldLoc = mytemp;
486 
487  if (newProjPath == wxT(""))
488  newProjPath = ::wxGetCwd();
489 
490  this->projPath = newProjPath;
491  this->projName = newProjName;
492  if (newProjPath.Last() == wxFILE_SEP_PATH)
493  this->projFull = newProjPath + newProjName;
494  else
495  this->projFull = newProjPath + wxFILE_SEP_PATH + newProjName;
496 
497  wxString cleanupLoc1=oldLoc;
498  wxString cleanupLoc2=projFull;
499 
500  bool created = false;
501 
502  if (bCreate) {
503  if (!wxDirExists(projFull)) {
504  if (!wxMkdir(projFull)) {
505  this->projFull = oldFull;
506  this->projPath = oldPath;
507  this->projName = oldName;
508  return false;
509  }
510  else
511  created = true;
512  }
513 
514  #ifdef __UNIX__
515  chmod(OSFILENAME(projFull), 0775);
516  #endif
517 
518  #ifdef __WXMAC__
519  chmod(OSFILENAME(projFull), 0775);
520  #endif
521 
522  } else {
523  if (!wxDirExists(projFull)) {
524  this->projFull = oldFull;
525  this->projPath = oldPath;
526  this->projName = oldName;
527  return false;
528  }
529  }
530 
531  /* Move all files into this NEW directory. Files which are
532  "locked" get copied instead of moved. (This happens when
533  we perform a Save As - the files which belonged to the last
534  saved version of the old project must not be moved,
535  otherwise the old project would not be safe.) */
536 
537  int trueTotal = 0;
538 
539  {
540  /*i18n-hint: This title appears on a dialog that indicates the progress in doing something.*/
541  ProgressDialog progress(_("Progress"),
542  _("Saving project data files"));
543 
544  int total = mBlockFileHash.size();
545 
546  bool success = true;
547  int count = 0;
548  wxArrayString newPaths;
549  for (const auto &pair : mBlockFileHash)
550  {
551  wxString newPath;
552  BlockFilePtr b = pair.second.lock();
553  if (b) {
554  // FIXME: TRAP_ERR
555  // JKC: The 'success' variable and recovery strategy looks
556  // broken/bogus to me. Would need to be using &= to catch
557  // failure in one of the copies/moves. Besides which,
558  // our temporary files are going to be deleted when we exit
559  // anyway, if saving from temporary to named project.
560 
561  if( progress.Update(count, total) != ProgressResult::Success )
562  success = false;
563  else {
564  moving = moving && !b->IsLocked();
565  auto result = CopyToNewProjectDirectory( &*b );
566  success = result.first;
567  newPath = result.second;
568  }
569 
570  if (success) {
571  count++;
572  }
573  else
574  break;
575  }
576 
577  newPaths.push_back( newPath );
578  }
579 
580  // in case there are any nulls
581  trueTotal = count;
582 
583  if (success) {
584  auto size = newPaths.size();
585  wxASSERT( size == mBlockFileHash.size() );
586 
587  // Commit changes to filenames in the BlockFile objects, and removal
588  // of files at old paths, ONLY NOW! This must be nothrow.
589 
590  // This copy-then-delete procedure is needed to make it safe to
591  // attempt save to another storage device, but fail.
592 
593  // It has the consequence that saving a project from one part of
594  // the device to another will not succeed unless there is sufficient
595  // space to hold originals and copies at the same time. Perhaps the
596  // extra cautions are not needed in that case, and the old procedure
597  // of renaming first, and reversing the renamings in case of failure,
598  // could still work safely.
599 
600  // But I don't know whether wxWidgets gives us a reliable means to
601  // distinguish that case.
602 
603  // I will err on the side of safety and simplicity and follow the
604  // same procedure in all cases.
605 
606  size_t ii = 0;
607  for (const auto &pair : mBlockFileHash)
608  {
609  BlockFilePtr b = pair.second.lock();
610 
611  if (b) {
612  if (moving || !b->IsLocked()) {
613  auto result = b->GetFileName();
614  auto oldPath = result.name.GetFullPath();
615  if (!oldPath.empty())
616  wxRemoveFile( oldPath );
617  }
618 
619  if (ii < size)
620  b->SetFileName(
621  wxFileNameWrapper{ wxFileName{ newPaths[ii] } } );
622  }
623 
624  ++ii;
625  }
626  }
627  else {
628  this->projFull = oldFull;
629  this->projPath = oldPath;
630  this->projName = oldName;
631 
632  if (created)
633  CleanDir(
634  cleanupLoc2,
635  wxEmptyString,
636  wxEmptyString,
637  _("Cleaning up after failed save"),
639 
640  return false;
641  }
642  }
643 
644  // Some subtlety; SetProject is used both to move a temp project
645  // into a permanent home as well as just set up path variables when
646  // loading a project; in this latter case, the movement code does
647  // nothing because SetProject is called before there are any
648  // blockfiles. Cleanup code trigger is the same
649 
650  // Do the cleanup of the temporary directory only if not saving-as, which we
651  // detect by having done copies rather than moves.
652  if (moving && trueTotal > 0) {
653  // Clean up after ourselves; boldly remove all files and directories
654  // in the tree. (Unlike what the earlier version of this comment said.)
655  // Because this is a relocation of the project, not the case of closing
656  // a persistent project.
657 
658  // You may think the loops above guarantee that all files we put in the
659  // folders have been moved away already, but:
660  // to fix bug1567 on Mac, we need to find the extraneous .DS_Store files
661  // that we didn't put there, but that Finder may insert into the folders,
662  // and mercilessly remove them, in addition to removing the directories.
663 
664  CleanDir(
665  cleanupLoc1,
666  wxEmptyString, // EmptyString => ALL directories.
667  // If the next line were wxEmptyString, ALL files would be removed.
668  ".DS_Store", // Other project files should already have been removed.
669  _("Cleaning up cache directories"),
671 
672  //This destroys the empty dirs of the OD block files, which are yet to come.
673  //Dont know if this will make the project dirty, but I doubt it. (mchinen)
674  // count += RecursivelyEnumerate(cleanupLoc2, dirlist, wxEmptyString, false, true);
675  }
676  return true;
677 }
678 
680 {
681  return projFull;
682 }
683 
685 {
686  return projName;
687 }
688 
690 {
691  wxLongLong freeSpace = -1;
692  wxFileName path;
693 
694  path.SetPath(projPath.IsEmpty() ? mytemp : projPath);
695 
696  // Use the parent directory if the project directory hasn't yet been created
697  if (!path.DirExists())
698  {
699  path.RemoveLastDir();
700  }
701 
702  if (!wxGetDiskSpace(path.GetFullPath(), NULL, &freeSpace))
703  {
704  freeSpace = -1;
705  }
706 
707  return freeSpace;
708 }
709 
711 {
712  return projFull != wxT("")? projFull: mytemp;
713 }
714 
715 void DirManager::SetLocalTempDir(const wxString &path)
716 {
717  mytemp = path;
718 }
719 
721 
722  wxFileNameWrapper dir;
723  dir.AssignDir(GetDataFilesDir());
724 
725  if(value.GetChar(0)==wxT('d')){
726  // this file is located in a subdirectory tree
727  int location=value.Find(wxT('b'));
728  wxString subdir=value.Mid(0,location);
729  dir.AppendDir(subdir);
730 
731  if(!dir.DirExists())
732  dir.Mkdir();
733  }
734 
735  if(value.GetChar(0)==wxT('e')){
736  // this file is located in a NEW style two-deep subdirectory tree
737  wxString topdir=value.Mid(0,3);
738  wxString middir=wxT("d");
739  middir.Append(value.Mid(3,2));
740 
741  dir.AppendDir(topdir);
742  dir.AppendDir(middir);
743 
744  if(!dir.DirExists() && !dir.Mkdir(0777,wxPATH_MKDIR_FULL))
745  { // need braces to avoid compiler warning about ambiguous else, see the macro
746  wxLogSysError(_("mkdir in DirManager::MakeBlockFilePath failed."));
747  }
748  }
749  return dir;
750 }
751 
753  const wxString &value,
754  bool diskcheck)
755 {
756  wxFileNameWrapper dir{ MakeBlockFilePath(value) };
757 
758  if(diskcheck){
759  // verify that there's no possible collision on disk. If there
760  // is, log the problem and return FALSE so that MakeBlockFileName
761  // can try again
762 
763  wxDir checkit(dir.GetFullPath());
764  if(!checkit.IsOpened()) return FALSE;
765 
766  // this code is only valid if 'value' has no extention; that
767  // means, effectively, AssignFile may be called with 'diskcheck'
768  // set to true only if called from MakeFileBlockName().
769 
770  wxString filespec;
771  filespec.Printf(wxT("%s.*"),value);
772  if(checkit.HasFiles(filespec)){
773  // collision with on-disk state!
774  wxString collision;
775  checkit.GetFirst(&collision,filespec);
776 
777  wxLogWarning(_("Audacity found an orphan block file: %s. \nPlease consider saving and reloading the project to perform a complete project check."),
778  collision);
779 
780  return FALSE;
781  }
782  }
783  fileName.Assign(dir.GetFullPath(),value);
784  return fileName.IsOk();
785 }
786 
787 static inline unsigned int hexchar_to_int(unsigned int x)
788 {
789  if(x<48U)return 0;
790  if(x<58U)return x-48U;
791  if(x<65U)return 10U;
792  if(x<71U)return x-55U;
793  if(x<97U)return 10U;
794  if(x<103U)return x-87U;
795  return 15U;
796 }
797 
798 int DirManager::BalanceMidAdd(int topnum, int midkey)
799 {
800  // enter the midlevel directory if it doesn't exist
801 
802  auto &balanceInfo = GetBalanceInfo();
803  auto &dirMidPool = balanceInfo.dirMidPool;
804  auto &dirMidFull = balanceInfo.dirMidFull;
805  auto &dirTopPool = balanceInfo.dirTopPool;
806  auto &dirTopFull = balanceInfo.dirTopFull;
807 
808  if(dirMidPool.find(midkey) == dirMidPool.end() &&
809  dirMidFull.find(midkey) == dirMidFull.end()){
810  dirMidPool[midkey]=0;
811 
812  // increment toplevel directory fill
813  dirTopPool[topnum]++;
814  if(dirTopPool[topnum]>=256){
815  // this toplevel is now full; move it to the full hash
816  dirTopPool.erase(topnum);
817  dirTopFull[topnum]=256;
818  }
819  return 1;
820  }
821  return 0;
822 }
823 
825 {
826  auto &balanceInfo = GetBalanceInfo();
827  auto &dirMidPool = balanceInfo.dirMidPool;
828  auto &dirMidFull = balanceInfo.dirMidFull;
829 
830  // increment the midlevel directory usage information
831  if(dirMidPool.find(midkey) != dirMidPool.end()){
832  dirMidPool[midkey]++;
833  if(dirMidPool[midkey]>=256){
834  // this middir is now full; move it to the full hash
835  dirMidPool.erase(midkey);
836  dirMidFull[midkey]=256;
837  }
838  }else{
839  // this case only triggers in absurdly large projects; we still
840  // need to track directory fill even if we're over 256/256/256
841  dirMidPool[midkey]++;
842  }
843 }
844 
845 void DirManager::BalanceInfoAdd(const wxString &file)
846 {
847  const wxChar *s=file;
848  if(s[0]==wxT('e')){
849  // this is one of the modern two-deep managed files
850  // convert filename to keys
851  unsigned int topnum = (hexchar_to_int(s[1]) << 4) |
852  hexchar_to_int(s[2]);
853  unsigned int midnum = (hexchar_to_int(s[3]) << 4) |
854  hexchar_to_int(s[4]);
855  unsigned int midkey=topnum<<8|midnum;
856 
857  BalanceMidAdd(topnum,midkey);
858  BalanceFileAdd(midkey);
859  }
860 }
861 
863 {
864  // Before returning the map,
865  // see whether any block files have disappeared,
866  // and if so update
867 
869  if ( mLastBlockFileDestructionCount != count ) {
870  auto it = mBlockFileHash.begin(), end = mBlockFileHash.end();
871  while (it != end)
872  {
873  BlockFilePtr ptr { it->second.lock() };
874  if (!ptr) {
875  auto name = it->first;
876  mBlockFileHash.erase( it++ );
877  BalanceInfoDel( name );
878  }
879  else
880  ++it;
881  }
882  }
883 
884  mLastBlockFileDestructionCount = count;
885 
886  return mBalanceInfo;
887 }
888 
889 // Note that this will try to clean up directories out from under even
890 // locked blockfiles; this is actually harmless as the rmdir will fail
891 // on non-empty directories.
892 void DirManager::BalanceInfoDel(const wxString &file)
893 {
894  // do not use GetBalanceInfo(),
895  // rather this function will be called from there.
896  auto &balanceInfo = mBalanceInfo;
897  auto &dirMidPool = balanceInfo.dirMidPool;
898  auto &dirMidFull = balanceInfo.dirMidFull;
899  auto &dirTopPool = balanceInfo.dirTopPool;
900  auto &dirTopFull = balanceInfo.dirTopFull;
901 
902  const wxChar *s=file;
903  if(s[0]==wxT('e')){
904  // this is one of the modern two-deep managed files
905 
906  unsigned int topnum = (hexchar_to_int(s[1]) << 4) |
907  hexchar_to_int(s[2]);
908  unsigned int midnum = (hexchar_to_int(s[3]) << 4) |
909  hexchar_to_int(s[4]);
910  unsigned int midkey=topnum<<8|midnum;
911 
912  // look for midkey in the mid pool
913  if(dirMidFull.find(midkey) != dirMidFull.end()){
914  // in the full pool
915 
916  if(--dirMidFull[midkey]<256){
917  // move out of full into available
918  dirMidPool[midkey]=dirMidFull[midkey];
919  dirMidFull.erase(midkey);
920  }
921  }else{
922  if(--dirMidPool[midkey]<1){
923  // erasing the key here is OK; we have provision to add it
924  // back if its needed (unlike the dirTopPool hash)
925  dirMidPool.erase(midkey);
926 
927  // DELETE the actual directory
928  wxString dir=(projFull != wxT("")? projFull: mytemp);
929  dir += wxFILE_SEP_PATH;
930  dir += file.Mid(0,3);
931  dir += wxFILE_SEP_PATH;
932  dir += wxT("d");
933  dir += file.Mid(3,2);
934  wxFileName::Rmdir(dir);
935 
936  // also need to remove from toplevel
937  if(dirTopFull.find(topnum) != dirTopFull.end()){
938  // in the full pool
939  if(--dirTopFull[topnum]<256){
940  // move out of full into available
941  dirTopPool[topnum]=dirTopFull[topnum];
942  dirTopFull.erase(topnum);
943  }
944  }else{
945  if(--dirTopPool[topnum]<1){
946  // do *not* erase the hash entry from dirTopPool
947  // *do* DELETE the actual directory
948  wxString dir=(projFull != wxT("")? projFull: mytemp);
949  dir += wxFILE_SEP_PATH;
950  dir += file.Mid(0,3);
951  wxFileName::Rmdir(dir);
952  }
953  }
954  }
955  }
956  }
957 }
958 
959 // only determines appropriate filename and subdir balance; does not
960 // perform maintainence
962 {
963  auto &balanceInfo = GetBalanceInfo();
964  auto &dirMidPool = balanceInfo.dirMidPool;
965  auto &dirTopPool = balanceInfo.dirTopPool;
966  auto &dirTopFull = balanceInfo.dirTopFull;
967 
968  wxFileNameWrapper ret;
969  wxString baseFileName;
970 
971  unsigned int filenum,midnum,topnum,midkey;
972 
973  while(1){
974 
975  /* blockfiles are divided up into heirarchical directories.
976  Each toplevel directory is represented by "e" + two unique
977  hexadecimal digits, for a total possible number of 256
978  toplevels. Each toplevel contains up to 256 subdirs named
979  "d" + two hex digits. Each subdir contains 'a number' of
980  files. */
981 
982  filenum=0;
983  midnum=0;
984  topnum=0;
985 
986  // first action: if there is no available two-level directory in
987  // the available pool, try to make one
988 
989  if(dirMidPool.empty()){
990 
991  // is there a toplevel directory with space for a NEW subdir?
992 
993  if(!dirTopPool.empty()){
994 
995  // there's still a toplevel with room for a subdir
996 
997  DirHash::iterator iter = dirTopPool.begin();
998  int newcount = 0;
999  topnum = iter->first;
1000 
1001 
1002  // search for unused midlevels; linear search adequate
1003  // add 32 NEW topnum/midnum dirs full of prospective filenames to midpool
1004  for(midnum=0;midnum<256;midnum++){
1005  midkey=(topnum<<8)+midnum;
1006  if(BalanceMidAdd(topnum,midkey)){
1007  newcount++;
1008  if(newcount>=32)break;
1009  }
1010  }
1011 
1012  if(dirMidPool.empty()){
1013  // all the midlevels in this toplevel are in use yet the
1014  // toplevel claims some are free; this implies multiple
1015  // internal logic faults, but simply giving up and going
1016  // into an infinite loop isn't acceptible. Just in case,
1017  // for some reason, we get here, dynamite this toplevel so
1018  // we don't just fail.
1019 
1020  // this is 'wrong', but the best we can do given that
1021  // something else is also wrong. It will contain the
1022  // problem so we can keep going without worry.
1023  dirTopPool.erase(topnum);
1024  dirTopFull[topnum]=256;
1025  }
1026  continue;
1027  }
1028  }
1029 
1030  if(dirMidPool.empty()){
1031  // still empty, thus an absurdly large project; all dirs are
1032  // full to 256/256/256; keep working, but fall back to 'big
1033  // filenames' and randomized placement
1034 
1035  filenum = rand();
1036  midnum = (int)(256.*rand()/(RAND_MAX+1.));
1037  topnum = (int)(256.*rand()/(RAND_MAX+1.));
1038  midkey=(topnum<<8)+midnum;
1039 
1040 
1041  }else{
1042 
1043  DirHash::iterator iter = dirMidPool.begin();
1044  midkey = iter->first;
1045 
1046  // split the retrieved 16 bit directory key into two 8 bit numbers
1047  topnum = midkey >> 8;
1048  midnum = midkey & 0xff;
1049  filenum = (int)(4096.*rand()/(RAND_MAX+1.));
1050 
1051  }
1052 
1053  baseFileName.Printf(wxT("e%02x%02x%03x"),topnum,midnum,filenum);
1054 
1055  if (!ContainsBlockFile(baseFileName)) {
1056  // not in the hash, good.
1057  if (!this->AssignFile(ret, baseFileName, true))
1058  {
1059  // this indicates an on-disk collision, likely due to an
1060  // orphan blockfile. We should try again, but first
1061  // alert the balancing info there's a phantom file here;
1062  // if the directory is nearly full of orphans we neither
1063  // want performance to suffer nor potentially get into an
1064  // infinite loop if all possible filenames are taken by
1065  // orphans (unlikely but possible)
1066  BalanceFileAdd(midkey);
1067 
1068  }else break;
1069  }
1070  }
1071  // FIXME: Might we get here without midkey having been set?
1072  // Seemed like a possible problem in these changes in .aup directory hierarchy.
1073  BalanceFileAdd(midkey);
1074  return ret;
1075 }
1076 
1078  samplePtr sampleData, size_t sampleLen,
1080  bool allowDeferredWrite)
1081 {
1082  wxFileNameWrapper filePath{ MakeBlockFileName() };
1083  const wxString fileName{ filePath.GetName() };
1084 
1085  auto newBlockFile = make_blockfile<SimpleBlockFile>
1086  (std::move(filePath), sampleData, sampleLen, format, allowDeferredWrite);
1087 
1088  mBlockFileHash[fileName] = newBlockFile;
1089 
1090  return newBlockFile;
1091 }
1092 
1094  const wxString &aliasedFile, sampleCount aliasStart,
1095  size_t aliasLen, int aliasChannel)
1096 {
1097  wxFileNameWrapper filePath{ MakeBlockFileName() };
1098  const wxString fileName = filePath.GetName();
1099 
1100  auto newBlockFile = make_blockfile<PCMAliasBlockFile>
1101  (std::move(filePath), wxFileNameWrapper{aliasedFile},
1102  aliasStart, aliasLen, aliasChannel);
1103 
1104  mBlockFileHash[fileName]=newBlockFile;
1105  aliasList.Add(aliasedFile);
1106 
1107  return newBlockFile;
1108 }
1109 
1111  const wxString &aliasedFile, sampleCount aliasStart,
1112  size_t aliasLen, int aliasChannel)
1113 {
1114  wxFileNameWrapper filePath{ MakeBlockFileName() };
1115  const wxString fileName{ filePath.GetName() };
1116 
1117  auto newBlockFile = make_blockfile<ODPCMAliasBlockFile>
1118  (std::move(filePath), wxFileNameWrapper{aliasedFile},
1119  aliasStart, aliasLen, aliasChannel);
1120 
1121  mBlockFileHash[fileName]=newBlockFile;
1122  aliasList.Add(aliasedFile);
1123 
1124  return newBlockFile;
1125 }
1126 
1128  const wxString &aliasedFile, sampleCount aliasStart,
1129  size_t aliasLen, int aliasChannel, int decodeType)
1130 {
1131  wxFileNameWrapper filePath{ MakeBlockFileName() };
1132  const wxString fileName{ filePath.GetName() };
1133 
1134  auto newBlockFile = make_blockfile<ODDecodeBlockFile>
1135  (std::move(filePath), wxFileNameWrapper{ aliasedFile },
1136  aliasStart, aliasLen, aliasChannel, decodeType);
1137 
1138  mBlockFileHash[fileName]=newBlockFile;
1139  aliasList.Add(aliasedFile); //OD TODO: check to see if we need to remove this when done decoding.
1140  //I don't immediately see a place where aliased files remove when a file is closed.
1141 
1142  return newBlockFile;
1143 }
1144 
1146 {
1147  if (!b)
1148  return false;
1149  auto result = b->GetFileName();
1150  BlockHash::const_iterator it = mBlockFileHash.find(result.name.GetName());
1151  if (it == mBlockFileHash.end())
1152  return false;
1153  BlockFilePtr ptr = it->second.lock();
1154  return ptr && (b == &*ptr);
1155 }
1156 
1157 bool DirManager::ContainsBlockFile(const wxString &filepath) const
1158 {
1159  // check what the hash returns in case the blockfile is from a different project
1160  BlockHash::const_iterator it = mBlockFileHash.find(filepath);
1161  return it != mBlockFileHash.end() &&
1162  BlockFilePtr{ it->second.lock() };
1163 }
1164 
1165 // Adds one to the reference count of the block file,
1166 // UNLESS it is "locked", then it makes a NEW copy of
1167 // the BlockFile.
1168 // This function returns non-NULL, or else throws
1170 {
1171  if (!b)
1173 
1174  auto result = b->GetFileName();
1175  const auto &fn = result.name;
1176 
1177  if (!b->IsLocked()) {
1178  //mchinen:July 13 2009 - not sure about this, but it needs to be added to the hash to be able to save if not locked.
1179  //note that this shouldn't hurt mBlockFileHash's that already contain the filename, since it should just overwrite.
1180  //but it's something to watch out for.
1181  //
1182  // LLL: Except for silent block files which have uninitialized filename.
1183  if (fn.IsOk())
1184  mBlockFileHash[fn.GetName()] = b;
1185  return b;
1186  }
1187 
1188  // Copy the blockfile
1189  BlockFilePtr b2;
1190  if (!fn.IsOk())
1191  // Block files with uninitialized filename (i.e. SilentBlockFile)
1192  // just need an in-memory copy.
1193  b2 = b->Copy(wxFileNameWrapper{});
1194  else
1195  {
1196  wxFileNameWrapper newFile{ MakeBlockFileName() };
1197  const wxString newName{newFile.GetName()};
1198  const wxString newPath{ newFile.GetFullPath() };
1199 
1200  // We assume that the NEW file should have the same extension
1201  // as the existing file
1202  newFile.SetExt(fn.GetExt());
1203 
1204  //some block files such as ODPCMAliasBlockFIle don't always have
1205  //a summary file, so we should check before we copy.
1206  if(b->IsSummaryAvailable())
1207  {
1208  if( !FileNames::CopyFile(fn.GetFullPath(),
1209  newFile.GetFullPath()) )
1210  // Disk space exhaustion, maybe
1211  throw FileException{
1212  FileException::Cause::Write, newFile };
1213  }
1214 
1215  // Done with fn
1216  result.mLocker.reset();
1217 
1218  b2 = b->Copy(std::move(newFile));
1219 
1220  mBlockFileHash[newName] = b2;
1221  aliasList.Add(newPath);
1222  }
1223 
1224  if (!b2)
1226 
1227  return b2;
1228 }
1229 
1230 bool DirManager::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
1231 {
1232  if( mLoadingTarget == NULL )
1233  return false;
1234 
1235  BlockFilePtr pBlockFile {};
1236 
1238 
1239  if (!wxStricmp(tag, wxT("silentblockfile"))) {
1240  // Silent blocks don't actually have a file associated, so
1241  // we don't need to worry about the hash table at all
1242  target = SilentBlockFile::BuildFromXML(*this, attrs);
1243  return true;
1244  }
1245  else if ( !wxStricmp(tag, wxT("simpleblockfile")) )
1246  pBlockFile = SimpleBlockFile::BuildFromXML(*this, attrs);
1247  else if( !wxStricmp(tag, wxT("pcmaliasblockfile")) )
1248  pBlockFile = PCMAliasBlockFile::BuildFromXML(*this, attrs);
1249  else if( !wxStricmp(tag, wxT("odpcmaliasblockfile")) )
1250  {
1251  pBlockFile = ODPCMAliasBlockFile::BuildFromXML(*this, attrs);
1252  //in the case of loading an OD file, we need to schedule the ODManager to begin OD computing of summary
1253  //However, because we don't have access to the track or even the Sequence from this call, we mark a flag
1254  //in the ODMan and check it later.
1256  }
1257  else if( !wxStricmp(tag, wxT("oddecodeblockfile")) )
1258  {
1259  pBlockFile = ODDecodeBlockFile::BuildFromXML(*this, attrs);
1261  }
1262  else if( !wxStricmp(tag, wxT("blockfile")) ||
1263  !wxStricmp(tag, wxT("legacyblockfile")) ) {
1264  // Support Audacity version 1.1.1 project files
1265 
1266  int i=0;
1267  bool alias = false;
1268 
1269  while(attrs[i]) {
1270  if (!wxStricmp(attrs[i], wxT("alias"))) {
1271  if (wxAtoi(attrs[i+1])==1)
1272  alias = true;
1273  }
1274  i++;
1275  if (attrs[i])
1276  i++;
1277  }
1278 
1279  if (alias)
1280  pBlockFile = LegacyAliasBlockFile::BuildFromXML(projFull, attrs);
1281  else
1282  pBlockFile = LegacyBlockFile::BuildFromXML(projFull, attrs,
1284  mLoadingFormat);
1285  }
1286  else
1287  return false;
1288 
1289  if (!pBlockFile)
1290  // BuildFromXML failed, or we didn't find a valid blockfile tag.
1291  return false;
1292 
1293  // Check the length here so we don't have to do it in each BuildFromXML method.
1294  if ((mMaxSamples != ~size_t(0)) && // is initialized
1295  (pBlockFile->GetLength() > mMaxSamples))
1296  {
1297  // See http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c13.
1298  // Lock pBlockFile so that the ~BlockFile() will not DELETE the file on disk.
1299  pBlockFile->Lock();
1300  return false;
1301  }
1302  else
1303  target = pBlockFile;
1304 
1305  //
1306  // If the block we loaded is already in the hash table, then the
1307  // object we just loaded is a duplicate, so we DELETE it and
1308  // return a reference to the existing object instead.
1309  //
1310 
1311  wxString name = target->GetFileName().name.GetName();
1312  auto &wRetrieved = mBlockFileHash[name];
1313  BlockFilePtr retrieved = wRetrieved.lock();
1314  if (retrieved) {
1315  // Lock it in order to DELETE it safely, i.e. without having
1316  // it DELETE the file, too...
1317  target->Lock();
1318 
1319  target = retrieved;
1320  return true;
1321  }
1322 
1323  // This is a NEW object
1324  wRetrieved = target;
1325  // MakeBlockFileName wasn't used so we must add the directory
1326  // balancing information
1327  BalanceInfoAdd(name);
1328 
1329  return true;
1330 }
1331 
1333 {
1334  wxString newPath;
1335  auto result = f->GetFileName();
1336  const auto &oldFileNameRef = result.name;
1337 
1338  // Check that this BlockFile corresponds to a file on disk
1339  //ANSWER-ME: Is this checking only for SilentBlockFiles, in which case
1340  // (!oldFileName.IsOk()) is a more correct check?
1341  if (oldFileNameRef.GetName().IsEmpty()) {
1342  return { true, newPath };
1343  }
1344 
1345  wxFileNameWrapper newFileName;
1346  if (!this->AssignFile(newFileName, oldFileNameRef.GetFullName(), false)
1347  // Another sanity check against blockfiles getting reassigned an empty
1348  // name, as apparently happened in
1349  // http://forum.audacityteam.org/viewtopic.php?f=47&t=97787 :
1350  || newFileName.GetFullName().empty() )
1351  return { false, {} };
1352 
1353  newPath = newFileName.GetFullPath();
1354 
1355  if (newFileName != oldFileNameRef) {
1356  //check to see that summary exists before we copy.
1357  bool summaryExisted = f->IsSummaryAvailable();
1358  auto oldPath = oldFileNameRef.GetFullPath();
1359  if (summaryExisted) {
1360  auto success = FileNames::CopyFile(oldPath, newPath);
1361  if (!success)
1362  return { false, {} };
1363  }
1364 
1365  if (!summaryExisted && (f->IsSummaryAvailable() || f->IsSummaryBeingComputed())) {
1366  // PRL: These steps apply only in case of "on-demand" files that have
1367  // not completed their asynchronous loading yet -- a very unusual
1368  // circumstance.
1369 
1370  // We will need to remember the old file name, so copy it
1371  wxFileName oldFileName{ oldFileNameRef };
1372 
1373  // Now we can free any lock (and should, if as the comment below says, we need
1374  // the other threads to progress)
1375  result.mLocker.reset();
1376 
1377  f->SetFileName(std::move(newFileName));
1378 
1379  //there is a small chance that the summary has begun to be computed on a different thread with the
1380  //original filename. we need to catch this case by waiting for it to finish and then copy.
1381 
1382  //block to make sure OD files don't get written while we are changing file names.
1383  //(It is important that OD files set this lock while computing their summary files.)
1384  while(f->IsSummaryBeingComputed() && !f->IsSummaryAvailable())
1385  ::wxMilliSleep(50);
1386 
1387  //check to make sure the oldfile exists.
1388  //if it doesn't, we can assume it was written to the NEW name, which is fine.
1389  if (oldFileName.FileExists())
1390  {
1391  bool ok = FileNames::CopyFile(oldPath, newPath);
1392  if (!ok)
1393  return { false, {} };
1394  }
1395  }
1396  }
1397 
1398  return { true, newPath };
1399 }
1400 
1401 bool DirManager::EnsureSafeFilename(const wxFileName &fName)
1402 {
1403  // Quick check: If it's not even in our alias list,
1404  // then the file name is A-OK.
1405 
1406  const wxString fullPath{fName.GetFullPath()};
1407  if (aliasList.Index(fullPath) == wxNOT_FOUND)
1408  return true;
1409 
1410  /* i18n-hint: 'old' is part of a filename used when a file is renamed. */
1411  // Figure out what the NEW name for the existing file would be.
1412  /* i18n-hint: e.g. Try to go from "mysong.wav" to "mysong-old1.wav". */
1413  // Keep trying until we find a filename that doesn't exist.
1414 
1415  wxFileNameWrapper renamedFileName{ fName };
1416  int i = 0;
1417  do {
1418  i++;
1419  /* i18n-hint: This is the pattern for filenames that are created
1420  * when a file needs to be backed up to a different name. For
1421  * example, mysong would become mysong-old1, mysong-old2, etc. */
1422  renamedFileName.SetName(wxString::Format(_("%s-old%d"), fName.GetName(), i));
1423  } while (renamedFileName.FileExists());
1424 
1425  // Test creating a file by that name to make sure it will
1426  // be possible to do the rename
1427 
1428  const wxString renamedFullPath{ renamedFileName.GetFullPath() };
1429  wxFile testFile(renamedFullPath, wxFile::write);
1430  if (!testFile.IsOpened()) {
1431  { // need braces to avoid compiler warning about ambiguous else, see the macro
1432  wxLogSysError(_("Unable to open/create test file."),
1433  renamedFullPath);
1434  }
1435  return false;
1436  }
1437 
1438  // Close the file prior to renaming.
1439  testFile.Close();
1440 
1441  if (!wxRemoveFile(renamedFullPath)) {
1442  /* i18n-hint: %s is the name of a file.*/
1443  { // need braces to avoid compiler warning about ambiguous else, see the macro
1444  wxLogSysError(_("Unable to remove '%s'."),
1445  renamedFullPath);
1446  }
1447  return false;
1448  }
1449 
1450  wxPrintf(_("Renamed file: %s\n"), renamedFullPath);
1451 
1452  // Go through our block files and see if any indeed point to
1453  // the file we're concerned about. If so, point the block file
1454  // to the renamed file and when we're done, perform the rename.
1455 
1456  bool needToRename = false;
1457  wxBusyCursor busy;
1458  BlockHash::iterator iter = mBlockFileHash.begin();
1459  std::vector< BlockFile::ReadLock > readLocks;
1460  while (iter != mBlockFileHash.end())
1461  {
1462  BlockFilePtr b = iter->second.lock();
1463  if (b) {
1464  // don't worry, we don't rely on this cast unless IsAlias is true
1465  auto ab = static_cast< AliasBlockFile * > ( &*b );
1466 
1467  // don't worry, we don't rely on this cast unless ISDataAvailable is false
1468  // which means that it still needs to access the file.
1469  auto db = static_cast< ODDecodeBlockFile * > ( &*b );
1470 
1471  if (b->IsAlias() && ab->GetAliasedFileName() == fName) {
1472  needToRename = true;
1473 
1474  //ODBlocks access the aliased file on another thread, so we need to pause them before this continues.
1475  readLocks.push_back( ab->LockForRead() );
1476  }
1477  //now for encoded OD blocks (e.g. flac)
1478  else if (!b->IsDataAvailable() && db->GetEncodedAudioFilename() == fName) {
1479  needToRename = true;
1480 
1481  //ODBlocks access the aliased file on another thread, so we need to pause them before this continues.
1482  readLocks.push_back( db->LockForRead() );
1483  }
1484  }
1485  ++iter;
1486  }
1487 
1488  if (needToRename) {
1489  if (!wxRenameFile(fullPath,
1490  renamedFullPath))
1491  {
1492  // ACK!!! The renaming was unsuccessful!!!
1493  // (This shouldn't happen, since we tried creating a
1494  // file of this name and then deleted it just a
1495  // second earlier.) But we'll handle this scenario
1496  // just in case!!!
1497 
1498  // Print error message and cancel the export
1499  wxLogSysError(_("Unable to rename '%s' to '%s'."),
1500  fullPath,
1501  renamedFullPath);
1502 
1503  // Destruction of readLocks puts things back where they were
1504  return false;
1505  }
1506  else
1507  {
1508  //point the aliases to the NEW filename.
1509  BlockHash::iterator iter = mBlockFileHash.begin();
1510  while (iter != mBlockFileHash.end())
1511  {
1512  BlockFilePtr b = iter->second.lock();
1513  if (b) {
1514  auto ab = static_cast< AliasBlockFile * > ( &*b );
1515  auto db = static_cast< ODDecodeBlockFile * > ( &*b );
1516 
1517  if (b->IsAlias() && ab->GetAliasedFileName() == fName)
1518  {
1519  ab->ChangeAliasedFileName(wxFileNameWrapper{ renamedFileName });
1520  wxPrintf(_("Changed block %s to new alias name\n"),
1521  b->GetFileName().name.GetFullName());
1522 
1523  }
1524  else if (!b->IsDataAvailable() && db->GetEncodedAudioFilename() == fName) {
1525  db->ChangeAudioFile(wxFileNameWrapper{ renamedFileName });
1526  }
1527  }
1528  ++iter;
1529  }
1530 
1531  }
1532 
1533  aliasList.Remove(fullPath);
1534  aliasList.Add(renamedFullPath);
1535  }
1536 
1537  // Success!!! Either we successfully renamed the file,
1538  // or we didn't need to!
1539  return true;
1540 }
1541 
1542 // Check the BlockFiles against the disk state.
1543 // Missing Blockfile data can be regenerated if possible or replaced with silence.
1544 // Orphan blockfiles can be deleted.
1545 // Note that even BlockFiles not referenced by the current savefile (but locked
1546 // by history) will be reflected in the mBlockFileHash, and that's a
1547 // good thing; this is one reason why we use the hash and not the most
1548 // recent savefile.
1549 int DirManager::ProjectFSCK(const bool bForceError, const bool bAutoRecoverMode)
1550 {
1551  // In earlier versions of this method, enumerations of errors were
1552  // all done in sequence, then the user was prompted for each type of error.
1553  // The enumerations are now interleaved with prompting, because, for example,
1554  // user choosing to replace missing aliased block files with silence
1555  // needs to put in SilentBlockFiles and DELETE the corresponding auf files,
1556  // so those would then not be cumulated in missingAUFHash.
1557  // We still do the FindX methods outside the conditionals,
1558  // so the log always shows all found errors.
1559 
1560  int action; // choice of action for each type of error
1561  int nResult = 0;
1562 
1563  if (bForceError && !bAutoRecoverMode)
1564  {
1565  // TODO: Replace with more user friendly error message?
1566  /* i18n-hint: The audacity project file is XML and has 'tags' in it,
1567  rather like html tags <something>some stuff</something>.
1568  This error message is about the tags that hold the sequence information.
1569  The error message is confusing to users in English, and could just say
1570  "Found problems with <sequence> when checking project file." */
1571  wxString msg = _("Project check read faulty Sequence tags.");
1572  const wxChar *buttons[] =
1573  {_("Close project immediately with no changes"),
1574  _("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."),
1575  NULL};
1576  wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
1577  action = ShowMultiDialog(msg, _("Warning - Problems Reading Sequence Tags"), buttons);
1578  if (action == 0)
1579  nResult = FSCKstatus_CLOSE_REQ;
1580  else
1582  }
1583 
1584  wxArrayString filePathArray; // *all* files in the project directory/subdirectories
1585  wxString dirPath = (projFull != wxT("") ? projFull : mytemp);
1587  dirPath,
1588  filePathArray, // output: all files in project directory tree
1589  wxEmptyString, // All dirs
1590  wxEmptyString, // All files
1591  true, false,
1592  mBlockFileHash.size(), // rough guess of how many BlockFiles will be found/processed, for progress
1593  _("Inspecting project file data"));
1594 
1595  //
1596  // MISSING ALIASED AUDIO FILES
1597  //
1599  BlockHash missingAliasedFileAUFHash; // (.auf) AliasBlockFiles whose aliased files are missing
1600  BlockHash missingAliasedFilePathHash; // full paths of missing aliased files
1601  this->FindMissingAliasedFiles(missingAliasedFileAUFHash, missingAliasedFilePathHash);
1602 
1603  if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAliasedFileAUFHash.empty())
1604  {
1605  // In auto-recover mode, we always create silent blocks, and do not ask user.
1606  // This makes sure the project is complete next time we open it.
1607  if (bAutoRecoverMode)
1608  action = 2;
1609  else
1610  {
1611  wxString msgA =
1612 _("Project check of \"%s\" folder \
1613 \ndetected %lld missing external audio file(s) \
1614 \n('aliased files'). There is no way for Audacity \
1615 \nto recover these files automatically. \
1616 \n\nIf you choose the first or second option below, \
1617 \nyou can try to find and restore the missing files \
1618 \nto their previous location. \
1619 \n\nNote that for the second option, the waveform \
1620 \nmay not show silence. \
1621 \n\nIf you choose the third option, this will save the \
1622 \nproject in its current state, unless you \"Close \
1623 \nproject immediately\" on further error alerts.");
1624  wxString msg;
1625  msg.Printf(msgA, this->projName, (long long) missingAliasedFilePathHash.size());
1626  const wxChar *buttons[] =
1627  {_("Close project immediately with no changes"),
1628  _("Treat missing audio as silence (this session only)"),
1629  _("Replace missing audio with silence (permanent immediately)."),
1630  NULL};
1631  wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
1632  action = ShowMultiDialog(msg, _("Warning - Missing Aliased File(s)"), buttons);
1633  }
1634 
1635  if (action == 0)
1636  nResult = FSCKstatus_CLOSE_REQ;
1637  else
1638  {
1639  // LL: A progress dialog should probably be used here
1640  BlockHash::iterator iter = missingAliasedFileAUFHash.begin();
1641  while (iter != missingAliasedFileAUFHash.end())
1642  {
1643  // This type cast is safe. We checked that it's an alias block file earlier.
1644  BlockFilePtr b = iter->second.lock();
1645  wxASSERT(b);
1646  if (b) {
1647  auto ab = static_cast< AliasBlockFile * > ( &*b );
1648 
1649  if (action == 2)
1650  {
1651  // silence the blockfiles by yanking the filename
1652  // This is done, eventually, in PCMAliasBlockFile::ReadData()
1653  // and ODPCMAliasBlockFile::ReadData, in the stack of b->Recover().
1654  // There, if the mAliasedFileName is bad, it zeroes the data.
1655  wxFileNameWrapper dummy;
1656  dummy.Clear();
1657  ab->ChangeAliasedFileName(std::move(dummy));
1658 
1659  // If recovery fails for one file, silence it,
1660  // and don't try to recover other files but
1661  // silence them too. GuardedCall will cause an appropriate
1662  // error message for the user.
1663  GuardedCall(
1664  [&] { ab->Recover(); },
1665  [&] (AudacityException*) { action = 1; }
1666  );
1667 
1669  }
1670 
1671  if (action == 1)
1672  // Silence error logging for this block in this session.
1673  ab->SilenceAliasLog();
1674  }
1675  ++iter;
1676  }
1677  if ((action == 2) && bAutoRecoverMode)
1678  wxLogWarning(_(" Project check replaced missing aliased file(s) with silence."));
1679  }
1680  }
1681 
1682  //
1683  // MISSING ALIAS (.AUF) AliasBlockFiles
1684  //
1685  // Alias summary regeneration must happen after checking missing aliased files.
1686  //
1687  BlockHash missingAUFHash; // missing (.auf) AliasBlockFiles
1688  this->FindMissingAUFs(missingAUFHash);
1689  if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUFHash.empty())
1690  {
1691  // In auto-recover mode, we just recreate the alias files, and do not ask user.
1692  // This makes sure the project is complete next time we open it.
1693  if (bAutoRecoverMode)
1694  action = 0;
1695  else
1696  {
1697  wxString msgA =
1698 _("Project check of \"%s\" folder \
1699 \ndetected %lld missing alias (.auf) blockfile(s). \
1700 \nAudacity can fully regenerate these files \
1701 \nfrom the current audio in the project.");
1702  wxString msg;
1703  msg.Printf(msgA, this->projName, (long long) missingAUFHash.size());
1704  const wxChar *buttons[] = {_("Regenerate alias summary files (safe and recommended)"),
1705  _("Fill in silence for missing display data (this session only)"),
1706  _("Close project immediately with no further changes"),
1707  NULL};
1708  wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
1709  action = ShowMultiDialog(msg, _("Warning - Missing Alias Summary File(s)"), buttons);
1710  }
1711 
1712  if (action == 2)
1713  nResult = FSCKstatus_CLOSE_REQ;
1714  else
1715  {
1716  // LL: A progress dialog should probably be used here
1717  BlockHash::iterator iter = missingAUFHash.begin();
1718  while (iter != missingAUFHash.end())
1719  {
1720  BlockFilePtr b = iter->second.lock();
1721  wxASSERT(b);
1722  if (b) {
1723  if(action==0) {
1724  //regenerate from data
1725  // If recovery fails for one file, silence it,
1726  // and don't try to recover other files but
1727  // silence them too. GuardedCall will cause an appropriate
1728  // error message for the user.
1729  GuardedCall(
1730  [&] {
1731  b->Recover();
1732  nResult |= FSCKstatus_CHANGED;
1733  },
1734  [&] (AudacityException*) { action = 1; }
1735  );
1736  }
1737 
1738  if (action==1){
1739  // Silence error logging for this block in this session.
1740  b->SilenceLog();
1741  }
1742  }
1743  ++iter;
1744  }
1745  if ((action == 0) && bAutoRecoverMode)
1746  wxLogWarning(_(" Project check regenerated missing alias summary file(s)."));
1747  }
1748  }
1749 
1750  //
1751  // MISSING (.AU) SimpleBlockFiles
1752  //
1753  BlockHash missingAUHash; // missing data (.au) blockfiles
1754  this->FindMissingAUs(missingAUHash);
1755  if ((nResult != FSCKstatus_CLOSE_REQ) && !missingAUHash.empty())
1756  {
1757  // In auto-recover mode, we just always create silent blocks.
1758  // This makes sure the project is complete next time we open it.
1759  if (bAutoRecoverMode)
1760  action = 2;
1761  else
1762  {
1763  wxString msgA =
1764 _("Project check of \"%s\" folder \
1765 \ndetected %lld missing audio data (.au) blockfile(s), \
1766 \nprobably due to a bug, system crash, or accidental \
1767 \ndeletion. There is no way for Audacity to recover \
1768 \nthese missing files automatically. \
1769 \n\nIf you choose the first or second option below, \
1770 \nyou can try to find and restore the missing files \
1771 \nto their previous location. \
1772 \n\nNote that for the second option, the waveform \
1773 \nmay not show silence.");
1774  wxString msg;
1775  msg.Printf(msgA, this->projName, (long long) missingAUHash.size());
1776  const wxChar *buttons[] =
1777  {_("Close project immediately with no further changes"),
1778  _("Treat missing audio as silence (this session only)"),
1779  _("Replace missing audio with silence (permanent immediately)"),
1780  NULL};
1781  wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
1782  action = ShowMultiDialog(msg, _("Warning - Missing Audio Data Block File(s)"), buttons);
1783  }
1784 
1785  if (action == 0)
1786  nResult = FSCKstatus_CLOSE_REQ;
1787  else
1788  {
1789  // LL: A progress dialog should probably be used here
1790  BlockHash::iterator iter = missingAUHash.begin();
1791  while (iter != missingAUHash.end())
1792  {
1793  BlockFilePtr b = iter->second.lock();
1794  wxASSERT(b);
1795  if (b) {
1796  if (action == 2)
1797  {
1798  //regenerate from data
1799  // If recovery fails for one file, silence it,
1800  // and don't try to recover other files but
1801  // silence them too. GuardedCall will cause an appropriate
1802  // error message for the user.
1803  GuardedCall(
1804  [&] {
1805  //regenerate with zeroes
1806  b->Recover();
1807  nResult |= FSCKstatus_CHANGED;
1808  },
1809  [&] (AudacityException*) { action = 1; }
1810  );
1811  }
1812 
1813  if (action == 1)
1814  b->SilenceLog();
1815  }
1816  ++iter;
1817  }
1818  if ((action == 2) && bAutoRecoverMode)
1819  wxLogWarning(_(" Project check replaced missing audio data block file(s) with silence."));
1820  }
1821  }
1822 
1823  //
1824  // ORPHAN BLOCKFILES (.au and .auf files that are not in the project.)
1825  //
1826  wxArrayString orphanFilePathArray; // orphan .au and .auf files
1827  this->FindOrphanBlockFiles(filePathArray, orphanFilePathArray);
1828 
1829  if ((nResult != FSCKstatus_CLOSE_REQ) && !orphanFilePathArray.IsEmpty())
1830  {
1831  // In auto-recover mode, leave orphan blockfiles alone.
1832  // They will be deleted when project is saved the first time.
1833  if (bAutoRecoverMode)
1834  {
1835  wxLogWarning(_(" Project check ignored orphan block file(s). They will be deleted when project is saved."));
1836  action = 1;
1837  }
1838  else
1839  {
1840  wxString msgA =
1841 _("Project check of \"%s\" folder \
1842 \nfound %d orphan block file(s). These files are \
1843 \nunused by this project, but might belong to \
1844 other projects. \
1845 \nThey are doing no harm and are small.");
1846  wxString msg;
1847  msg.Printf(msgA, this->projName, (int)orphanFilePathArray.GetCount());
1848 
1849  const wxChar *buttons[] =
1850  {_("Continue without deleting; ignore the extra files this session"),
1851  _("Close project immediately with no further changes"),
1852  _("Delete orphan files (permanent immediately)"),
1853  NULL};
1854  wxLog::FlushActive(); // MultiDialog has "Show Log..." button, so make sure log is current.
1855  action = ShowMultiDialog(msg, _("Warning - Orphan Block File(s)"), buttons);
1856  }
1857 
1858  if (action == 1)
1859  nResult = FSCKstatus_CLOSE_REQ;
1860  // Nothing is done if (action == 0).
1861  else if (action == 2)
1862  {
1863  // FSCKstatus_CHANGED was bogus here.
1864  // The files are deleted, so "Undo Project Repair" could not do anything.
1865  // Plus they affect none of the valid tracks, so incorrect to mark them changed,
1866  // and no need for refresh.
1867  // nResult |= FSCKstatus_CHANGED;
1868  for (size_t i = 0; i < orphanFilePathArray.GetCount(); i++)
1869  wxRemoveFile(orphanFilePathArray[i]);
1870  }
1871  }
1872 
1873  if ((nResult != FSCKstatus_CLOSE_REQ) && !ODManager::HasLoadedODFlag())
1874  {
1875  // Remove any empty directories.
1876  ProgressDialog pProgress
1877  (_("Progress"),
1878  _("Cleaning up unused directories in project data"));
1879  // nDirCount is for updating pProgress. +1 because we may DELETE dirPath.
1880  int nDirCount = RecursivelyCountSubdirs(dirPath) + 1;
1881  RecursivelyRemoveEmptyDirs(dirPath, nDirCount, &pProgress);
1882  }
1883 
1884  // Summarize and flush the log.
1885  if (bForceError ||
1886  !missingAliasedFileAUFHash.empty() ||
1887  !missingAUFHash.empty() ||
1888  !missingAUHash.empty() ||
1889  !orphanFilePathArray.IsEmpty())
1890  {
1891  wxLogWarning(_("Project check found file inconsistencies inspecting the loaded project data."));
1892  wxLog::FlushActive(); // Flush is modal and will clear the log (both desired).
1893 
1894  // In auto-recover mode, we didn't do any ShowMultiDialog calls above, so put up an alert.
1895  if (bAutoRecoverMode)
1897  _("Project check found file inconsistencies during automatic recovery.\n\nSelect 'Show Log...' in the Help menu to see details."),
1898  _("Warning: Problems in Automatic Recovery"),
1899  wxOK | wxICON_EXCLAMATION);
1900  }
1901 
1903  return nResult;
1904 }
1905 
1907  BlockHash& missingAliasedFileAUFHash, // output: (.auf) AliasBlockFiles whose aliased files are missing
1908  BlockHash& missingAliasedFilePathHash) // output: full paths of missing aliased files
1909 {
1910  BlockHash::iterator iter = mBlockFileHash.begin();
1911  while (iter != mBlockFileHash.end())
1912  {
1913  wxString key = iter->first; // file name and extension
1914  BlockFilePtr b = iter->second.lock();
1915  if (b) {
1916  if (b->IsAlias())
1917  {
1918  const wxFileName &aliasedFileName =
1919  static_cast< AliasBlockFile* > ( &*b )->GetAliasedFileName();
1920  wxString aliasedFileFullPath = aliasedFileName.GetFullPath();
1921  // wxEmptyString can happen if user already chose to "replace... with silence".
1922  if ((aliasedFileFullPath != wxEmptyString) &&
1923  !aliasedFileName.FileExists())
1924  {
1925  missingAliasedFileAUFHash[key] = b;
1926  if (missingAliasedFilePathHash.find(aliasedFileFullPath) ==
1927  missingAliasedFilePathHash.end()) // Add it only once.
1928  // Not actually using the block here, just the path,
1929  // so set the block to NULL to create the entry.
1930  missingAliasedFilePathHash[aliasedFileFullPath] = {};
1931  }
1932  }
1933  }
1934  ++iter;
1935  }
1936 
1937  iter = missingAliasedFilePathHash.begin();
1938  while (iter != missingAliasedFilePathHash.end())
1939  {
1940  wxLogWarning(_("Missing aliased audio file: '%s'"), iter->first);
1941  ++iter;
1942  }
1943 }
1944 
1946  BlockHash& missingAUFHash) // output: missing (.auf) AliasBlockFiles
1947 {
1948  BlockHash::iterator iter = mBlockFileHash.begin();
1949  while (iter != mBlockFileHash.end())
1950  {
1951  const wxString &key = iter->first;
1952  BlockFilePtr b = iter->second.lock();
1953  if (b) {
1954  if (b->IsAlias() && b->IsSummaryAvailable())
1955  {
1956  /* don't look in hash; that might find files the user moved
1957  that the Blockfile abstraction can't find itself */
1958  wxFileNameWrapper fileName{ MakeBlockFilePath(key) };
1959  fileName.SetName(key);
1960  fileName.SetExt(wxT("auf"));
1961  if (!fileName.FileExists())
1962  {
1963  missingAUFHash[key] = b;
1964  wxLogWarning(_("Missing alias (.auf) block file: '%s'"),
1965  fileName.GetFullPath());
1966  }
1967  }
1968  }
1969  ++iter;
1970  }
1971 }
1972 
1974  BlockHash& missingAUHash) // missing data (.au) blockfiles
1975 {
1976  BlockHash::iterator iter = mBlockFileHash.begin();
1977  while (iter != mBlockFileHash.end())
1978  {
1979  const wxString &key = iter->first;
1980  BlockFilePtr b = iter->second.lock();
1981  if (b) {
1982  if (!b->IsAlias())
1983  {
1984  wxFileNameWrapper fileName{ MakeBlockFilePath(key) };
1985  fileName.SetName(key);
1986  fileName.SetExt(wxT("au"));
1987  const auto path = fileName.GetFullPath();
1988  if (!fileName.FileExists() ||
1989  wxFile{ path }.Length() == 0)
1990  {
1991  missingAUHash[key] = b;
1992  wxLogWarning(_("Missing data block file: '%s'"), path);
1993  }
1994  }
1995  }
1996  ++iter;
1997  }
1998 }
1999 
2000 // Find .au and .auf files that are not in the project.
2002  const wxArrayString& filePathArray, // input: all files in project directory
2003  wxArrayString& orphanFilePathArray) // output: orphan files
2004 {
2005  DirManager *clipboardDM = NULL;
2006 
2007  for (size_t i = 0; i < filePathArray.GetCount(); i++)
2008  {
2009  const wxFileName &fullname = filePathArray[i];
2010  wxString basename = fullname.GetName();
2011  const wxString ext{fullname.GetExt()};
2012  if ((mBlockFileHash.find(basename) == mBlockFileHash.end()) && // is orphan
2013  // Consider only Audacity data files.
2014  // Specifically, ignore <branding> JPG and <import> OGG ("Save Compressed Copy").
2015  (ext.IsSameAs(wxT("au")) ||
2016  ext.IsSameAs(wxT("auf"))))
2017  {
2018  if (!clipboardDM) {
2020 
2021  if (clipTracks) {
2022  TrackListIterator clipIter(clipTracks);
2023  Track *track = clipIter.First();
2024  if (track)
2025  clipboardDM = track->GetDirManager().get();
2026  }
2027  }
2028 
2029  // Ignore it if it exists in the clipboard (from a previously closed project)
2030  if (!(clipboardDM && clipboardDM->ContainsBlockFile(basename)))
2031  orphanFilePathArray.Add(fullname.GetFullPath());
2032  }
2033  }
2034  for (size_t i = 0; i < orphanFilePathArray.GetCount(); i++)
2035  wxLogWarning(_("Orphan block file: '%s'"), orphanFilePathArray[i]);
2036 }
2037 
2038 
2040 {
2041  wxArrayString filePathArray; // *all* files in the project directory/subdirectories
2042  wxString dirPath = (projFull != wxT("") ? projFull : mytemp);
2044  dirPath,
2045  filePathArray, // output: all files in project directory tree
2046  wxEmptyString, // All dirs
2047  wxEmptyString, // All files
2048  true, false,
2049  mBlockFileHash.size(), // rough guess of how many BlockFiles will be found/processed, for progress
2050  _("Inspecting project file data"));
2051 
2052  wxArrayString orphanFilePathArray;
2053  this->FindOrphanBlockFiles(
2054  filePathArray, // input: all files in project directory tree
2055  orphanFilePathArray); // output: orphan files
2056 
2057  // Remove all orphan blockfiles.
2058  for (size_t i = 0; i < orphanFilePathArray.GetCount(); i++)
2059  wxRemoveFile(orphanFilePathArray[i]);
2060 }
2061 
2063 {
2064 #ifdef DEPRECATED_AUDIO_CACHE
2065  // See http://bugzilla.audacityteam.org/show_bug.cgi?id=545.
2066  bool cacheBlockFiles = false;
2067  gPrefs->Read(wxT("/Directories/CacheBlockFiles"), &cacheBlockFiles);
2068 
2069  if (!cacheBlockFiles)
2070  return; // user opted not to cache block files
2071 
2072  int lowMem = gPrefs->Read(wxT("/Directories/CacheLowMem"), 16l);
2073  if (lowMem < 16) {
2074  lowMem = 16;
2075  }
2076  lowMem <<= 20;
2077 
2078  BlockHash::iterator iter;
2079  int numNeed = 0;
2080 
2081  iter = mBlockFileHash.begin();
2082  while (iter != mBlockFileHash.end())
2083  {
2084  BlockFilePtr b = iter->second.lock();
2085  if (b) {
2086  if (b->GetNeedFillCache())
2087  numNeed++;
2088  }
2089  ++iter;
2090  }
2091 
2092  if (numNeed == 0)
2093  return;
2094 
2095  ProgressDialog progress(_("Caching audio"),
2096  _("Caching audio into memory"));
2097 
2098  iter = mBlockFileHash.begin();
2099  int current = 0;
2100  while (iter != mBlockFileHash.end())
2101  {
2102  BlockFilePtr b = iter->second.lock();
2103  if (b) {
2104  if (b->GetNeedFillCache() && (GetFreeMemory() > lowMem)) {
2105  b->FillCache();
2106  }
2107 
2108  if (!progress.Update(current, numNeed))
2109  break; // user cancelled progress dialog, stop caching
2110  current++;
2111  }
2112  ++iter;
2113  }
2114 #endif // DEPRECATED_AUDIO_CACHE
2115 }
2116 
2118 {
2119  BlockHash::iterator iter;
2120  int numNeed = 0;
2121 
2122  iter = mBlockFileHash.begin();
2123  while (iter != mBlockFileHash.end())
2124  {
2125  BlockFilePtr b = iter->second.lock();
2126  if (b) {
2127  if (b->GetNeedWriteCacheToDisk())
2128  numNeed++;
2129  }
2130  ++iter;
2131  }
2132 
2133  if (numNeed == 0)
2134  return;
2135 
2136  ProgressDialog progress(_("Saving recorded audio"),
2137  _("Saving recorded audio to disk"));
2138 
2139  iter = mBlockFileHash.begin();
2140  int current = 0;
2141  while (iter != mBlockFileHash.end())
2142  {
2143  BlockFilePtr b = iter->second.lock();
2144  if (b) {
2145  if (b->GetNeedWriteCacheToDisk())
2146  {
2147  b->WriteCacheToDisk();
2148  progress.Update(current, numNeed);
2149  }
2150  current++;
2151  }
2152  ++iter;
2153  }
2154 }
2155 
static BlockFilePtr BuildFromXML(DirManager &dm, const wxChar **attrs)
static int RecursivelyRemoveEmptyDirs(wxString dirPath, int nDirCount=0, ProgressDialog *pProgress=NULL)
Definition: DirManager.cpp:258
wxString projName
Definition: DirManager.h:213
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
static void CleanDir(const wxString &path, const wxString &dirSpec, const wxString &fileSpec, const wxString &msg, int flags=0)
Definition: DirManager.cpp:444
static wxString globaltemp
Definition: DirManager.h:230
static int RecursivelyEnumerate(wxString dirPath, wxArrayString &filePathArray, wxString dirspec, wxString filespec, bool bFiles, bool bDirs, int progress_count=0, int progress_bias=0, ProgressDialog *progress=NULL)
Definition: DirManager.cpp:162
Creates and manages BlockFile objects.
Definition: DirManager.h:54
wxString GetProjectName()
Definition: DirManager.cpp:684
void BalanceInfoAdd(const wxString &)
Definition: DirManager.cpp:845
#define FSCKstatus_SAVE_AUP
Definition: DirManager.h:35
size_t mMaxSamples
Definition: DirManager.h:226
A BlockFile is a chunk of immutable audio data.
Definition: BlockFile.h:56
static unsigned long gBlockFileDestructionCount
Definition: BlockFile.h:65
BlockFilePtr NewAliasBlockFile(const wxString &aliasedFile, sampleCount aliasStart, size_t aliasLen, int aliasChannel)
size_t mLoadingBlockLen
Definition: DirManager.h:224
root of a hierarchy of classes that are thrown and caught by Audacity.
static int numDirManagers
Definition: DirManager.h:232
ProgressDialog Class.
void SetLocalTempDir(const wxString &path)
Definition: DirManager.cpp:715
void SetMissingAliasedFileWarningShouldShow(bool b)
Changes the behavior of missing aliased blockfiles warnings.
unsigned mLoadingTargetIdx
Definition: DirManager.h:222
bool EnsureSafeFilename(const wxFileName &fName)
wxString GetDataFilesDir() const
Definition: DirManager.cpp:710
static int RecursivelyCountSubdirs(wxString dirPath)
Definition: DirManager.cpp:238
An AliasBlockFile that references uncompressed data in an existing file.
#define FSCKstatus_CHANGED
Definition: DirManager.h:34
BlockFilePtr NewODDecodeBlockFile(const wxString &aliasedFile, sampleCount aliasStart, size_t aliasLen, int aliasChannel, int decodeType)
wxString projPath
Definition: DirManager.h:214
void BalanceFileAdd(int)
Definition: DirManager.cpp:824
wxLongLong GetFreeDiskSpace()
Definition: DirManager.cpp:689
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
#define OSFILENAME(X)
Definition: Internat.h:173
static BlockFilePtr BuildFromXML(const wxString &dir, const wxChar **attrs, size_t len, sampleFormat format)
static
virtual bool IsSummaryBeingComputed()
Returns TRUE if the summary has not yet been written, but is actively being computed and written to d...
Definition: BlockFile.h:146
#define THROW_INCONSISTENCY_EXCEPTION
void FindMissingAliasedFiles(BlockHash &missingAliasedFileAUFHash, BlockHash &missingAliasedFilePathHash)
wxMemorySize GetFreeMemory()
Definition: DirManager.cpp:120
void FindMissingAUFs(BlockHash &missingAUFHash)
static BlockFilePtr BuildFromXML(DirManager &dm, const wxChar **attrs)
Reconstructs from XML a ODPCMAliasBlockFile and reschedules it for OD loading.
const std::shared_ptr< DirManager > & GetDirManager() const
Definition: Track.h:299
int ShowWarningDialog(wxWindow *parent, const wxString &internalDialogName, const wxString &message, bool showCancelButton, const wxString &footer)
Definition: Warning.cpp:93
std::shared_ptr< BlockFile > BlockFilePtr
Definition: BlockFile.h:48
void ChangeAudioFile(wxFileNameWrapper &&newAudioFile)
static bool dontDeleteTempFiles
Definition: DirManager.h:233
int format
Definition: ExportPCM.cpp:56
int ProjectFSCK(const bool bForceError, const bool bAutoRecoverMode)
wxFileNameWrapper MakeBlockFilePath(const wxString &value)
Definition: DirManager.cpp:720
void FindMissingAUs(BlockHash &missingAUHash)
void BalanceInfoDel(const wxString &)
Definition: DirManager.cpp:892
struct DirManager::BalanceInfo mBalanceInfo
BlockArray * mLoadingTarget
Definition: DirManager.h:221
sampleFormat
Definition: Types.h:187
char * samplePtr
Definition: Types.h:202
bool SetProject(wxString &newProjPath, wxString &newProjName, const bool bCreate)
Definition: DirManager.cpp:477
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:101
ProgressResult Update(int value, const wxString &message=wxEmptyString)
static bool CopyFile(const wxString &file1, const wxString &file2, bool overwrite=true)
Definition: FileNames.cpp:46
sampleFormat mLoadingFormat
Definition: DirManager.h:223
static void MarkLoadedODFlag()
sets a flag that is set if we have loaded some OD blockfiles from PCM.
Definition: ODManager.cpp:508
BlockHash mBlockFileHash
Definition: DirManager.h:193
std::pair< bool, wxString > CopyToNewProjectDirectory(BlockFile *f)
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:418
int BalanceMidAdd(int, int)
Definition: DirManager.cpp:798
static bool HasLoadedODFlag()
returns a flag that is set if we have loaded some OD blockfiles from PCM.
Definition: ODManager.cpp:522
wxString GetProjectDataDir()
Definition: DirManager.cpp:679
void WriteCacheToDisk()
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), const F3 &delayedHandler={})
wxString projFull
Definition: DirManager.h:215
static TrackList * GetClipboardTracks()
Definition: Project.cpp:4789
BlockFilePtr CopyBlockFile(const BlockFilePtr &b)
int ShowMultiDialog(const wxString &message, const wxString &title, const wxChar **buttons, const wxString &boxMsg, bool log)
static BlockFilePtr BuildFromXML(DirManager &dm, const wxChar **attrs)
Reconstructs from XML a ODDecodeBlockFile and reschedules it for OD loading.
wxArrayString aliasList
Definition: DirManager.h:219
std::unordered_map< wxString, std::weak_ptr< BlockFile > > BlockHash
Definition: DirManager.h:42
virtual void SetFileName(wxFileNameWrapper &&name)
sets the file name the summary info will be saved in. threadsafe.
Definition: BlockFile.cpp:133
An iterator for a TrackList.
Definition: Track.h:401
BlockFilePtr NewODAliasBlockFile(const wxString &aliasedFile, sampleCount aliasStart, size_t aliasLen, int aliasChannel)
wxFileNameWrapper MakeBlockFileName()
Definition: DirManager.cpp:961
static BlockFilePtr BuildFromXML(DirManager &dm, const wxChar **attrs)
static
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
static BlockFilePtr BuildFromXML(DirManager &dm, const wxChar **attrs)
static
const wxChar * name
Definition: Distortion.cpp:94
wxString mytemp
Definition: DirManager.h:231
virtual GetFileNameResult GetFileName() const
Definition: BlockFile.cpp:127
static unsigned int hexchar_to_int(unsigned int x)
Definition: DirManager.cpp:787
A BlockFile that refers to data in an existing file.
Definition: BlockFile.h:259
static int RecursivelyEnumerateWithProgress(wxString dirPath, wxArrayString &filePathArray, wxString dirspec, wxString filespec, bool bFiles, bool bDirs, int progress_count, const wxChar *message)
Definition: DirManager.cpp:216
static void RecursivelyRemove(wxArrayString &filePathArray, int count, int bias, int flags, const wxChar *message=NULL)
Definition: DirManager.cpp:303
const wxFileName & name
Definition: BlockFile.h:99
bool ContainsBlockFile(const BlockFile *b) const
Returns true if the blockfile pointed to by b is contained by the DirManager.
BlockFilePtr NewSimpleBlockFile(samplePtr sampleData, size_t sampleLen, sampleFormat format, bool allowDeferredWrite=false)
#define FSCKstatus_CLOSE_REQ
Definition: DirManager.h:33
static BlockFilePtr BuildFromXML(const wxString &projDir, const wxChar **attrs)
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
virtual ~DirManager()
Definition: DirManager.cpp:420
AudacityApp & wxGetApp()
void RemoveOrphanBlockfiles()
virtual bool IsSummaryAvailable() const
Returns TRUE if this block's complete summary has been computed and is ready (for OD) ...
Definition: BlockFile.h:140
void FillBlockfilesCache()
BalanceInfo & GetBalanceInfo()
Definition: DirManager.cpp:862
void create(Args &&...args)
Definition: MemoryX.h:653
bool AssignFile(wxFileNameWrapper &filename, const wxString &value, bool check)
Definition: DirManager.cpp:752
static void CleanTempDir()
Definition: DirManager.cpp:436
unsigned long mLastBlockFileDestructionCount
Definition: DirManager.h:228
void FindOrphanBlockFiles(const wxArrayString &filePathArray, wxArrayString &orphanFilePathArray)