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