1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   FileNames.cpp
6 
7   James Crook
8 
9 ********************************************************************//**
10 
11 \class FileNames
12 \brief Provides Static functions to yield filenames.
13 
14 This class helps us with setting a base path, and makes it easier
15 for us to keep track of the different kinds of files we read and write
16 from.
17 
18 JKC: In time I plan to add all file names and file extensions
19 used throughout Audacity into this one place.
20 
21 *//********************************************************************/
22 
23 #include "FileNames.h"
24 
25 
26 
27 #include <memory>
28 
29 #include <wx/defs.h>
30 #include <wx/filename.h>
31 #include <wx/intl.h>
32 #include <wx/stdpaths.h>
33 #include "BasicUI.h"
34 #include "Prefs.h"
35 #include "Internat.h"
36 #include "ModuleConstants.h"
37 #include "PlatformCompatibility.h"
38 #include "wxFileNameWrapper.h"
39 
40 #if defined(__WXMAC__) || defined(__WXGTK__)
41 #include <dlfcn.h>
42 #endif
43 
44 #if defined(__WXMSW__)
45 #include <windows.h>
46 #endif
47 
48 static wxString gDataDir;
49 
50 const FileNames::FileType
51      FileNames::AllFiles{ XO("All files"), { wxT("") } }
52      /* i18n-hint an Audacity project is the state of the program, stored as
53      files that can be reopened to resume the session later */
54    , FileNames::AudacityProjects{ XO("AUP3 project files"), { wxT("aup3") }, true }
55    , FileNames::DynamicLibraries{
56 #if defined(__WXMSW__)
57       XO("Dynamically Linked Libraries"), { wxT("dll") }, true
58 #elif defined(__WXMAC__)
59       XO("Dynamic Libraries"), { wxT("dylib") }, true
60 #else
61       XO("Dynamically Linked Libraries"), { wxT("so*") }, true
62 #endif
63      }
64    , FileNames::TextFiles{ XO("Text files"), { wxT("txt") }, true }
65    , FileNames::XMLFiles{ XO("XML files"), { wxT("xml"), wxT("XML") }, true }
66 ;
67 
FormatWildcard(const FileTypes & fileTypes)68 wxString FileNames::FormatWildcard( const FileTypes &fileTypes )
69 {
70    // |-separated list of:
71    // [ Description,
72    //   ( if appendExtensions, then ' (', globs, ')' ),
73    //   '|',
74    //   globs ]
75    // where globs is a ;-separated list of filename patterns, which are
76    // '*' for an empty extension, else '*.' then the extension
77    // Only the part before | is displayed in the choice drop-down of file
78    // dialogs
79    //
80    // Exceptional case: if there is only one type and its description is empty,
81    // then just give the globs with no |
82    // Another exception: an empty description, when there is more than one
83    // type, is replaced with a default
84    // Another exception:  if an extension contains a dot, it is interpreted as
85    // not really an extension, but a literal filename
86 
87    const wxString dot{ '.' };
88    const auto makeGlobs = [&dot]( const FileExtensions &extensions ){
89       wxString globs;
90       for ( const auto &extension: extensions ) {
91          if ( !globs.empty() )
92             globs += ';';
93          if ( extension.Contains( dot ) )
94             globs += extension;
95          else {
96             globs += '*';
97             if ( !extension.empty() ) {
98                globs += '.';
99                globs += extension;
100             }
101          }
102       }
103       return globs;
104    };
105 
106    const auto defaultDescription = []( const FileExtensions &extensions ){
107       // Assume extensions is not empty
108       wxString exts = extensions[0];
109       for (size_t ii = 1, size = extensions.size(); ii < size; ++ii ) {
110          exts += XO(", ").Translation();
111          exts += extensions[ii];
112       }
113       /* i18n-hint a type or types such as "txt" or "txt, xml" will be
114          substituted for %s */
115       return XO("%s files").Format( exts );
116    };
117 
118    if ( fileTypes.size() == 1 && fileTypes[0].description.empty() ) {
119       return makeGlobs( fileTypes[0].extensions );
120    }
121    else {
122       wxString result;
123       for ( const auto &fileType : fileTypes ) {
124          const auto &extensions = fileType.extensions;
125          if (extensions.empty())
126             continue;
127 
128          if (!result.empty())
129             result += '|';
130 
131          const auto globs = makeGlobs( extensions );
132 
133          auto mask = fileType.description;
134          if ( mask.empty() )
135            mask = defaultDescription( extensions );
136          if ( fileType.appendExtensions )
137             mask.Join( XO("(%s)").Format( globs ), " " );
138          result += mask.Translation();
139          result += '|';
140          result += globs;
141       }
142       return result;
143    }
144 }
145 
DoCopyFile(const FilePath & file1,const FilePath & file2,bool overwrite)146 bool FileNames::DoCopyFile(
147    const FilePath& file1, const FilePath& file2, bool overwrite)
148 {
149 #ifdef __WXMSW__
150 
151    // workaround not needed
152    return wxCopyFile(file1, file2, overwrite);
153 
154 #else
155    // PRL:  Compensate for buggy wxCopyFile that returns false success,
156    // which was a cause of case 4 in comment 10 of
157    // http://bugzilla.audacityteam.org/show_bug.cgi?id=1759
158    // Destination file was created, but was empty
159    // Bug was introduced after wxWidgets 2.8.12 at commit
160    // 0597e7f977c87d107e24bf3e95ebfa3d60efc249 of wxWidgets repo
161 
162    bool existed = wxFileExists(file2);
163    bool result = wxCopyFile(file1, file2, overwrite) &&
164       wxFile{ file1 }.Length() == wxFile{ file2 }.Length();
165    if (!result && !existed)
166       wxRemoveFile(file2);
167    return result;
168 
169 #endif
170 }
171 
HardLinkFile(const FilePath & file1,const FilePath & file2)172 bool FileNames::HardLinkFile( const FilePath& file1, const FilePath& file2 )
173 {
174 #ifdef __WXMSW__
175 
176    // Fix forced ASCII conversions and wrong argument order - MJB - 29/01/2019
177    //return ::CreateHardLinkA( file1.c_str(), file2.c_str(), NULL );
178    return ( 0 != ::CreateHardLink( file2, file1, NULL ) );
179 
180 #else
181 
182    return 0 == ::link( file1.c_str(), file2.c_str() );
183 
184 #endif
185 }
186 
MkDir(const wxString & Str)187 wxString FileNames::MkDir(const wxString &Str)
188 {
189    // Behaviour of wxFileName::DirExists() and wxFileName::MkDir() has
190    // changed between wx2.6 and wx2.8, so we use static functions instead.
191    if (!wxFileName::DirExists(Str))
192       wxFileName::Mkdir(Str, 511, wxPATH_MKDIR_FULL);
193 
194    return Str;
195 }
196 
197 // originally an ExportMultipleDialog method. Append suffix if newName appears in otherNames.
MakeNameUnique(FilePaths & otherNames,wxFileName & newName)198 void FileNames::MakeNameUnique(FilePaths &otherNames,
199    wxFileName &newName)
200 {
201    if (otherNames.Index(newName.GetFullName(), false) >= 0) {
202       int i=2;
203       wxString orig = newName.GetName();
204       do {
205          newName.SetName(wxString::Format(wxT("%s-%d"), orig, i));
206          i++;
207       } while (otherNames.Index(newName.GetFullName(), false) >= 0);
208    }
209    otherNames.push_back(newName.GetFullName());
210 }
211 
212 // The APP name has upercase first letter (so that Quit Audacity is correctly
213 // capitalised on Mac, but we want lower case APP name in paths.
214 // This function does that substitution, IF the last component of
215 // the path is 'Audacity'.
LowerCaseAppNameInPath(const wxString & dirIn)216 wxString FileNames::LowerCaseAppNameInPath( const wxString & dirIn){
217    wxString dir = dirIn;
218    // BUG 1577 Capitalisation of Audacity in path...
219    if( dir.EndsWith( "Audacity" ) )
220    {
221       int nChars = dir.length() - wxString( "Audacity" ).length();
222       dir = dir.Left( nChars ) + "audacity";
223    }
224    return dir;
225 }
226 
DataDir()227 FilePath FileNames::DataDir()
228 {
229    // LLL:  Wouldn't you know that as of WX 2.6.2, there is a conflict
230    //       between wxStandardPaths and wxConfig under Linux.  The latter
231    //       creates a normal file as "$HOME/.audacity", while the former
232    //       expects the ".audacity" portion to be a directory.
233    if (gDataDir.empty())
234    {
235       // If there is a directory "Portable Settings" relative to the
236       // executable's EXE file, the prefs are stored in there, otherwise
237       // the prefs are stored in the user data dir provided by the OS.
238       wxFileName exePath(PlatformCompatibility::GetExecutablePath());
239 #if defined(__WXMAC__)
240       // Path ends for example in "Audacity.app/Contents/MacOSX"
241       //exePath.RemoveLastDir();
242       //exePath.RemoveLastDir();
243       // just remove the MacOSX part.
244       exePath.RemoveLastDir();
245 #endif
246       wxFileName portablePrefsPath(exePath.GetPath(), wxT("Portable Settings"));
247 
248       if (::wxDirExists(portablePrefsPath.GetFullPath()))
249       {
250          // Use "Portable Settings" folder
251          gDataDir = portablePrefsPath.GetFullPath();
252       } else
253       {
254          // Use OS-provided user data dir folder
255          wxString dataDir( LowerCaseAppNameInPath( wxStandardPaths::Get().GetUserDataDir() ));
256 #if defined( __WXGTK__ )
257          dataDir = dataDir + wxT("-data");
258 #endif
259          gDataDir = FileNames::MkDir(dataDir);
260       }
261    }
262    return gDataDir;
263 }
264 
ResourcesDir()265 FilePath FileNames::ResourcesDir(){
266    wxString resourcesDir( LowerCaseAppNameInPath( wxStandardPaths::Get().GetResourcesDir() ));
267    return resourcesDir;
268 }
269 
HtmlHelpDir()270 FilePath FileNames::HtmlHelpDir()
271 {
272 #if defined(__WXMAC__)
273    wxFileName exePath(PlatformCompatibility::GetExecutablePath());
274       // Path ends for example in "Audacity.app/Contents/MacOSX"
275       //exePath.RemoveLastDir();
276       //exePath.RemoveLastDir();
277       // just remove the MacOSX part.
278       exePath.RemoveLastDir();
279 
280    //for mac this puts us within the .app: Audacity.app/Contents/SharedSupport/
281    return wxFileName( exePath.GetPath()+wxT("/help/manual"), wxEmptyString ).GetFullPath();
282 #else
283    //linux goes into /*prefix*/share/audacity/
284    //windows (probably) goes into the dir containing the .exe
285    wxString dataDir = FileNames::LowerCaseAppNameInPath( wxStandardPaths::Get().GetDataDir());
286    return wxFileName( dataDir+wxT("/help/manual"), wxEmptyString ).GetFullPath();
287 #endif
288 }
289 
LegacyChainDir()290 FilePath FileNames::LegacyChainDir()
291 {
292    // Don't force creation of it
293    return wxFileName{ DataDir(), wxT("Chains") }.GetFullPath();
294 }
295 
MacroDir()296 FilePath FileNames::MacroDir()
297 {
298    return FileNames::MkDir( wxFileName( DataDir(), wxT("Macros") ).GetFullPath() );
299 }
300 
NRPDir()301 FilePath FileNames::NRPDir()
302 {
303    return FileNames::MkDir( wxFileName( DataDir(), wxT("NRP") ).GetFullPath() );
304 }
305 
NRPFile()306 FilePath FileNames::NRPFile()
307 {
308    return wxFileName( NRPDir(), wxT("noisegate.nrp") ).GetFullPath();
309 }
310 
PlugInDir()311 FilePath FileNames::PlugInDir()
312 {
313    return FileNames::MkDir( wxFileName( DataDir(), wxT("Plug-Ins") ).GetFullPath() );
314 }
315 
PluginRegistry()316 FilePath FileNames::PluginRegistry()
317 {
318    return wxFileName( DataDir(), wxT("pluginregistry.cfg") ).GetFullPath();
319 }
320 
PluginSettings()321 FilePath FileNames::PluginSettings()
322 {
323    return wxFileName( DataDir(), wxT("pluginsettings.cfg") ).GetFullPath();
324 }
325 
BaseDir()326 FilePath FileNames::BaseDir()
327 {
328    wxFileName baseDir;
329 
330 #if defined(__WXMAC__)
331    baseDir = PlatformCompatibility::GetExecutablePath();
332 
333    // Path ends for example in "Audacity.app/Contents/MacOSX"
334    //baseDir.RemoveLastDir();
335    //baseDir.RemoveLastDir();
336    // just remove the MacOSX part.
337    baseDir.RemoveLastDir();
338 #elif defined(__WXMSW__)
339    // Don't use wxStandardPaths::Get().GetDataDir() since it removes
340    // the "Debug" directory in debug builds.
341    baseDir = PlatformCompatibility::GetExecutablePath();
342 #else
343    // Linux goes into /*prefix*/share/audacity/
344    baseDir = FileNames::LowerCaseAppNameInPath(wxStandardPaths::Get().GetPluginsDir());
345 #endif
346 
347    return baseDir.GetPath();
348 }
349 
ModulesDir()350 FilePath FileNames::ModulesDir()
351 {
352    wxFileName modulesDir(BaseDir(), wxEmptyString);
353 
354    modulesDir.AppendDir(wxT("modules"));
355 
356    return modulesDir.GetFullPath();
357 }
358 
ThemeDir()359 FilePath FileNames::ThemeDir()
360 {
361    return FileNames::MkDir( wxFileName( DataDir(), wxT("Theme") ).GetFullPath() );
362 }
363 
ThemeComponentsDir()364 FilePath FileNames::ThemeComponentsDir()
365 {
366    return FileNames::MkDir( wxFileName( ThemeDir(), wxT("Components") ).GetFullPath() );
367 }
368 
ThemeCachePng()369 FilePath FileNames::ThemeCachePng()
370 {
371    return wxFileName( ThemeDir(), wxT("ImageCache.png") ).GetFullPath();
372 }
373 
ThemeCacheHtm()374 FilePath FileNames::ThemeCacheHtm()
375 {
376    return wxFileName( ThemeDir(), wxT("ImageCache.htm") ).GetFullPath();
377 }
378 
ThemeImageDefsAsCee()379 FilePath FileNames::ThemeImageDefsAsCee()
380 {
381    return wxFileName( ThemeDir(), wxT("ThemeImageDefsAsCee.h") ).GetFullPath();
382 }
383 
ThemeCacheAsCee()384 FilePath FileNames::ThemeCacheAsCee( )
385 {
386 // DA: Theme sourcery file name.
387 #ifndef EXPERIMENTAL_DA
388    return wxFileName( ThemeDir(), wxT("ThemeAsCeeCode.h") ).GetFullPath();
389 #else
390    return wxFileName( ThemeDir(), wxT("DarkThemeAsCeeCode.h") ).GetFullPath();
391 #endif
392 }
393 
ThemeComponent(const wxString & Str)394 FilePath FileNames::ThemeComponent(const wxString &Str)
395 {
396    return wxFileName( ThemeComponentsDir(), Str, wxT("png") ).GetFullPath();
397 }
398 
399 //
400 // Returns the full path of program module (.exe, .dll, .so, .dylib) containing address
401 //
PathFromAddr(void * addr)402 FilePath FileNames::PathFromAddr(void *addr)
403 {
404     wxFileName name;
405 
406 #if defined(__WXMAC__) || defined(__WXGTK__)
407 #define OSFILENAME(X) ((char *) (const char *)(X).fn_str())
408 #define OSINPUT(X) OSFILENAME(X)
409    Dl_info info;
410    if (dladdr(addr, &info)) {
411       char realname[PLATFORM_MAX_PATH + 1];
412       int len;
413       name = LAT1CTOWX(info.dli_fname);
414       len = readlink(OSINPUT(name.GetFullPath()), realname, PLATFORM_MAX_PATH);
415       if (len > 0) {
416          realname[len] = 0;
417          name.SetFullName(LAT1CTOWX(realname));
418       }
419    }
420 #elif defined(__WXMSW__) && defined(_UNICODE)
421    // The GetModuleHandlEx() function did not appear until Windows XP and
422    // GetModuleFileName() did appear until Windows 2000, so we have to
423    // check for them at runtime.
424    typedef BOOL (WINAPI *getmodulehandleex)(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE* phModule);
425    typedef DWORD (WINAPI *getmodulefilename)(HMODULE hModule, LPWCH lpFilename, DWORD nSize);
426    getmodulehandleex gmhe =
427       (getmodulehandleex) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
428                                          "GetModuleHandleExW");
429    getmodulefilename gmfn =
430       (getmodulefilename) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
431                                          "GetModuleFileNameW");
432 
433    if (gmhe != NULL && gmfn != NULL) {
434       HMODULE module;
435       if (gmhe(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
436                (LPTSTR) addr,
437                &module)) {
438          TCHAR path[MAX_PATH];
439          DWORD nSize;
440 
441          nSize = gmfn(module, path, MAX_PATH);
442          if (nSize && nSize < MAX_PATH) {
443             name = LAT1CTOWX(path);
444          }
445       }
446    }
447 #endif
448 
449     return name.GetFullPath();
450 }
451 
452 
IsPathAvailable(const FilePath & Path)453 bool FileNames::IsPathAvailable( const FilePath & Path){
454    if( Path.IsEmpty() )
455       return false;
456 #ifndef __WIN32__
457    return true;
458 #else
459    wxFileNameWrapper filePath( Path );
460    return filePath.DirExists() && !filePath.FileExists();
461 #endif
462 }
463 
DefaultToDocumentsFolder(const wxString & preference)464 wxFileNameWrapper FileNames::DefaultToDocumentsFolder(const wxString &preference)
465 {
466    wxFileNameWrapper result;
467 
468 #ifdef __WIN32__
469    wxFileName defaultPath( wxStandardPaths::Get().GetDocumentsDir(), "" );
470 
471    defaultPath.AppendDir( AppName );
472    result.SetPath( gPrefs->Read( preference, defaultPath.GetPath( wxPATH_GET_VOLUME ) ) );
473 
474    // MJB: Bug 1899 & Bug 2007.  Only create directory if the result is the default path
475    bool bIsDefaultPath = result == defaultPath;
476    if( !bIsDefaultPath )
477    {
478       // IF the prefs directory doesn't exist - (Deleted by our user perhaps?)
479       //    or exists as a file
480       // THEN fallback to using the default directory.
481       bIsDefaultPath = !IsPathAvailable( result.GetPath(wxPATH_GET_VOLUME|wxPATH_GET_SEPARATOR ) );
482       if( bIsDefaultPath )
483       {
484          result.SetPath( defaultPath.GetPath( wxPATH_GET_VOLUME ) );
485          // Don't write to gPrefs.
486          // We typically do it later, (if directory actually gets used)
487       }
488    }
489    if ( bIsDefaultPath )
490    {
491       // The default path might not exist since it is a sub-directory of 'Documents'
492       // There is no error if the path could not be created.  That's OK.
493       // The dialog that Audacity offers will allow the user to select a valid directory.
494       result.Mkdir(0755, wxPATH_MKDIR_FULL);
495    }
496 #else
497    result.AssignHomeDir();
498    result.SetPath(gPrefs->Read( preference, result.GetPath() + "/Documents"));
499 #endif
500 
501    return result;
502 }
503 
PreferenceKey(FileNames::Operation op,FileNames::PathType type)504 wxString FileNames::PreferenceKey(FileNames::Operation op, FileNames::PathType type)
505 {
506    wxString key;
507    switch (op) {
508       case FileNames::Operation::Temp:
509          key = wxT("/Directories/TempDir"); break;
510       case FileNames::Operation::Presets:
511          key = wxT("/Presets/Path"); break;
512       case FileNames::Operation::Open:
513          key = wxT("/Directories/Open"); break;
514       case FileNames::Operation::Save:
515          key = wxT("/Directories/Save"); break;
516       case FileNames::Operation::Import:
517          key = wxT("/Directories/Import"); break;
518       case FileNames::Operation::Export:
519          key = wxT("/Directories/Export"); break;
520       case FileNames::Operation::MacrosOut:
521          key = wxT("/Directories/MacrosOut"); break;
522       case FileNames::Operation::_None:
523       default:
524          break;
525    }
526 
527    switch (type) {
528       case FileNames::PathType::User:
529          key += "/Default"; break;
530       case FileNames::PathType::LastUsed:
531          key += "/LastUsed"; break;
532       case FileNames::PathType::_None:
533       default:
534          break;
535    }
536 
537    return key;
538 }
539 
FindDefaultPath(Operation op)540 FilePath FileNames::FindDefaultPath(Operation op)
541 {
542    auto key = PreferenceKey(op, PathType::User);
543 
544    if (key.empty())
545       return wxString{};
546 
547    // If the user specified a default path, then use that
548    FilePath path = gPrefs->Read(key, wxT(""));
549    if (!path.empty()) {
550       return path;
551    }
552 
553    // Maybe the last used path is available
554    key = PreferenceKey(op, PathType::LastUsed);
555    path = gPrefs->Read(key, wxT(""));
556    if (!path.empty()) {
557       return path;
558    }
559 
560    // Last resort is to simply return the default folder
561    return DefaultToDocumentsFolder("").GetPath();
562 }
563 
UpdateDefaultPath(Operation op,const FilePath & path)564 void FileNames::UpdateDefaultPath(Operation op, const FilePath &path)
565 {
566    if (path.empty())
567       return;
568    wxString key;
569    if (op == Operation::Temp) {
570       key = PreferenceKey(op, PathType::_None);
571    }
572    else {
573       key = PreferenceKey(op, PathType::LastUsed);
574    }
575    if (!key.empty()) {
576       gPrefs->Write(key, path);
577       gPrefs->Flush();
578    }
579 }
580 
IsMidi(const FilePath & fName)581 bool FileNames::IsMidi(const FilePath &fName)
582 {
583    const auto extension = fName.AfterLast(wxT('.'));
584    return
585       extension.IsSameAs(wxT("gro"), false) ||
586       extension.IsSameAs(wxT("midi"), false) ||
587       extension.IsSameAs(wxT("mid"), false);
588 }
589 
590 static FilePaths sAudacityPathList;
591 
AudacityPathList()592 const FilePaths &FileNames::AudacityPathList()
593 {
594    return sAudacityPathList;
595 }
596 
SetAudacityPathList(FilePaths list)597 void FileNames::SetAudacityPathList( FilePaths list )
598 {
599    sAudacityPathList = std::move( list );
600 }
601 
602 // static
AddUniquePathToPathList(const FilePath & pathArg,FilePaths & pathList)603 void FileNames::AddUniquePathToPathList(const FilePath &pathArg,
604                                           FilePaths &pathList)
605 {
606    wxFileNameWrapper pathNorm { pathArg };
607    pathNorm.Normalize();
608    const wxString newpath{ pathNorm.GetFullPath() };
609 
610    for(const auto &path : pathList) {
611       if (pathNorm == wxFileNameWrapper{ path })
612          return;
613    }
614 
615    pathList.push_back(newpath);
616 }
617 
618 // static
AddMultiPathsToPathList(const wxString & multiPathStringArg,FilePaths & pathList)619 void FileNames::AddMultiPathsToPathList(const wxString &multiPathStringArg,
620                                           FilePaths &pathList)
621 {
622    wxString multiPathString(multiPathStringArg);
623    while (!multiPathString.empty()) {
624       wxString onePath = multiPathString.BeforeFirst(wxPATH_SEP[0]);
625       multiPathString = multiPathString.AfterFirst(wxPATH_SEP[0]);
626       AddUniquePathToPathList(onePath, pathList);
627    }
628 }
629 
630 #include <wx/log.h>
631 
632 // static
FindFilesInPathList(const wxString & pattern,const FilePaths & pathList,FilePaths & results,int flags)633 void FileNames::FindFilesInPathList(const wxString & pattern,
634                                       const FilePaths & pathList,
635                                       FilePaths & results,
636                                       int flags)
637 {
638    wxLogNull nolog;
639 
640    if (pattern.empty()) {
641       return;
642    }
643 
644    wxFileNameWrapper ff;
645 
646    for(size_t i = 0; i < pathList.size(); i++) {
647       ff = pathList[i] + wxFILE_SEP_PATH + pattern;
648       wxDir::GetAllFiles(ff.GetPath(), &results, ff.GetFullName(), flags);
649    }
650 }
651 
WritableLocationCheck(const FilePath & path,const TranslatableString & message)652 bool FileNames::WritableLocationCheck(const FilePath& path,
653                                     const TranslatableString & message)
654 {
655     bool status = wxFileName::IsDirWritable(path);
656 
657     if (!status)
658     {
659         using namespace BasicUI;
660         ShowMessageBox(
661             message +
662             XO("\n%s does not have write permissions.").Format(path),
663             MessageBoxOptions{}
664                 .Caption(XO("Error"))
665                 .IconStyle(Icon::Error)
666                 .ButtonStyle(Button::Ok)
667         );
668     }
669 
670     return status;
671 }
672 
673 // Using this with wxStringArray::Sort will give you a list that
674 // is alphabetical, without depending on case.  If you use the
675 // default sort, you will get strings with 'R' before 'a', because it is in caps.
CompareNoCase(const wxString & first,const wxString & second)676 int FileNames::CompareNoCase(const wxString& first, const wxString& second)
677 {
678    return first.CmpNoCase(second);
679 }
680 
681 // Create a unique filename using the passed prefix and suffix
CreateUniqueName(const wxString & prefix,const wxString & suffix)682 wxString FileNames::CreateUniqueName(const wxString &prefix,
683                                      const wxString &suffix /* = wxEmptyString */)
684 {
685    static int count = 0;
686 
687    return wxString::Format(wxT("%s %s N-%i.%s"),
688                            prefix,
689                            wxDateTime::Now().Format(wxT("%Y-%m-%d %H-%M-%S")),
690                            ++count,
691                            suffix);
692 }
693 
UnsavedProjectExtension()694 wxString FileNames::UnsavedProjectExtension()
695 {
696    return wxT("aup3unsaved");
697 }
698 
699 // How to detect whether the file system of a path is FAT
700 // No apparent way to do it with wxWidgets
701 #if defined(__DARWIN__)
702 #include <sys/mount.h>
IsOnFATFileSystem(const FilePath & path)703 bool FileNames::IsOnFATFileSystem(const FilePath &path)
704 {
705    struct statfs fs;
706    if (statfs(wxPathOnly(path).c_str(), &fs))
707       // Error from statfs
708       return false;
709    return 0 == strcmp(fs.f_fstypename, "msdos");
710 }
711 #elif defined(__linux__)
712 #include <sys/statfs.h>
713 #include "/usr/include/linux/magic.h"
IsOnFATFileSystem(const FilePath & path)714 bool FileNames::IsOnFATFileSystem(const FilePath &path)
715 {
716    struct statfs fs;
717    if (statfs(wxPathOnly(path).c_str(), &fs))
718       // Error from statfs
719       return false;
720    return fs.f_type == MSDOS_SUPER_MAGIC;
721 }
722 #elif defined(_WIN32)
723 #include <fileapi.h>
IsOnFATFileSystem(const FilePath & path)724 bool FileNames::IsOnFATFileSystem(const FilePath &path)
725 {
726    wxFileNameWrapper fileName{path};
727    if (!fileName.HasVolume())
728       return false;
729    auto volume = AbbreviatePath(fileName) + wxT("\\");
730    DWORD volumeFlags;
731    wxChar volumeType[64];
732    if (!::GetVolumeInformationW(
733       volume.wc_str(), NULL, 0, NULL, NULL,
734       &volumeFlags,
735       volumeType,
736       WXSIZEOF(volumeType)))
737       return false;
738    wxString type(volumeType);
739    if (type == wxT("FAT") || type == wxT("FAT32"))
740       return true;
741    return false;
742 }
743 #else
IsOnFATFileSystem(const FilePath & path)744 bool FileNames::IsOnFATFileSystem(const FilePath &path)
745 {
746    return false;
747 }
748 #endif
749 
AbbreviatePath(const wxFileName & fileName)750 wxString FileNames::AbbreviatePath( const wxFileName &fileName )
751 {
752    wxString target;
753 #ifdef __WXMSW__
754 
755    // Drive letter plus colon
756    target = fileName.GetVolume() + wxT(":");
757 
758 #else
759 
760    // Shorten the path, arbitrarily to 3 components
761    auto path = fileName;
762    path.SetFullName(wxString{});
763    while(path.GetDirCount() > 3)
764       path.RemoveLastDir();
765    target = path.GetFullPath();
766 
767 #endif
768    return target;
769 }
770