From 05e991100fdcea9ddb442c9618c8f40bdebeb442 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 22 Jul 2021 21:57:21 -0400 Subject: [PATCH] Duplicate FileNames.* in SelectFile.* --- src/SelectFile.cpp | 836 +++++++++++++++++++++++++++++++++++++++++++++ src/SelectFile.h | 263 ++++++++++++++ 2 files changed, 1099 insertions(+) create mode 100644 src/SelectFile.cpp create mode 100644 src/SelectFile.h diff --git a/src/SelectFile.cpp b/src/SelectFile.cpp new file mode 100644 index 000000000..d6480b789 --- /dev/null +++ b/src/SelectFile.cpp @@ -0,0 +1,836 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + FileNames.cpp + + James Crook + +********************************************************************//** + +\class FileNames +\brief Provides Static functions to yield filenames. + +This class helps us with setting a base path, and makes it easier +for us to keep track of the different kinds of files we read and write +from. + +JKC: In time I plan to add all file names and file extensions +used throughout Audacity into this one place. + +*//********************************************************************/ + + +#include "FileNames.h" + + + +#include + +#include +#include +#include +#include +#include +#include "BasicUI.h" +#include "Prefs.h" +#include "Internat.h" +#include "PlatformCompatibility.h" +#include "wxFileNameWrapper.h" +#include "widgets/AudacityMessageBox.h" +#include "widgets/FileDialog/FileDialog.h" + +#if defined(__WXMAC__) || defined(__WXGTK__) +#include +#endif + +#if defined(__WXMSW__) +#include +#endif + +static wxString gDataDir; + +const FileNames::FileType + FileNames::AllFiles{ XO("All files"), { wxT("") } } + /* i18n-hint an Audacity project is the state of the program, stored as + files that can be reopened to resume the session later */ + , FileNames::AudacityProjects{ XO("AUP3 project files"), { wxT("aup3") }, true } + , FileNames::DynamicLibraries{ +#if defined(__WXMSW__) + XO("Dynamically Linked Libraries"), { wxT("dll") }, true +#elif defined(__WXMAC__) + XO("Dynamic Libraries"), { wxT("dylib") }, true +#else + XO("Dynamically Linked Libraries"), { wxT("so*") }, true +#endif + } + , FileNames::TextFiles{ XO("Text files"), { wxT("txt") }, true } + , FileNames::XMLFiles{ XO("XML files"), { wxT("xml"), wxT("XML") }, true } +; + +wxString FileNames::FormatWildcard( const FileTypes &fileTypes ) +{ + // |-separated list of: + // [ Description, + // ( if appendExtensions, then ' (', globs, ')' ), + // '|', + // globs ] + // where globs is a ;-separated list of filename patterns, which are + // '*' for an empty extension, else '*.' then the extension + // Only the part before | is displayed in the choice drop-down of file + // dialogs + // + // Exceptional case: if there is only one type and its description is empty, + // then just give the globs with no | + // Another exception: an empty description, when there is more than one + // type, is replaced with a default + // Another exception: if an extension contains a dot, it is interpreted as + // not really an extension, but a literal filename + + const wxString dot{ '.' }; + const auto makeGlobs = [&dot]( const FileExtensions &extensions ){ + wxString globs; + for ( const auto &extension: extensions ) { + if ( !globs.empty() ) + globs += ';'; + if ( extension.Contains( dot ) ) + globs += extension; + else { + globs += '*'; + if ( !extension.empty() ) { + globs += '.'; + globs += extension; + } + } + } + return globs; + }; + + const auto defaultDescription = []( const FileExtensions &extensions ){ + // Assume extensions is not empty + wxString exts = extensions[0]; + for (size_t ii = 1, size = extensions.size(); ii < size; ++ii ) { + exts += XO(", ").Translation(); + exts += extensions[ii]; + } + /* i18n-hint a type or types such as "txt" or "txt, xml" will be + substituted for %s */ + return XO("%s files").Format( exts ); + }; + + if ( fileTypes.size() == 1 && fileTypes[0].description.empty() ) { + return makeGlobs( fileTypes[0].extensions ); + } + else { + wxString result; + for ( const auto &fileType : fileTypes ) { + const auto &extensions = fileType.extensions; + if (extensions.empty()) + continue; + + if (!result.empty()) + result += '|'; + + const auto globs = makeGlobs( extensions ); + + auto mask = fileType.description; + if ( mask.empty() ) + mask = defaultDescription( extensions ); + if ( fileType.appendExtensions ) + mask.Join( XO("(%s)").Format( globs ), " " ); + result += mask.Translation(); + result += '|'; + result += globs; + } + return result; + } +} + +bool FileNames::DoCopyFile( + const FilePath& file1, const FilePath& file2, bool overwrite) +{ +#ifdef __WXMSW__ + + // workaround not needed + return wxCopyFile(file1, file2, overwrite); + +#else + // PRL: Compensate for buggy wxCopyFile that returns false success, + // which was a cause of case 4 in comment 10 of + // http://bugzilla.audacityteam.org/show_bug.cgi?id=1759 + // Destination file was created, but was empty + // Bug was introduced after wxWidgets 2.8.12 at commit + // 0597e7f977c87d107e24bf3e95ebfa3d60efc249 of wxWidgets repo + + bool existed = wxFileExists(file2); + bool result = wxCopyFile(file1, file2, overwrite) && + wxFile{ file1 }.Length() == wxFile{ file2 }.Length(); + if (!result && !existed) + wxRemoveFile(file2); + return result; + +#endif +} + +bool FileNames::HardLinkFile( const FilePath& file1, const FilePath& file2 ) +{ +#ifdef __WXMSW__ + + // Fix forced ASCII conversions and wrong argument order - MJB - 29/01/2019 + //return ::CreateHardLinkA( file1.c_str(), file2.c_str(), NULL ); + return ( 0 != ::CreateHardLink( file2, file1, NULL ) ); + +#else + + return 0 == ::link( file1.c_str(), file2.c_str() ); + +#endif +} + +wxString FileNames::MkDir(const wxString &Str) +{ + // Behaviour of wxFileName::DirExists() and wxFileName::MkDir() has + // changed between wx2.6 and wx2.8, so we use static functions instead. + if (!wxFileName::DirExists(Str)) + wxFileName::Mkdir(Str, 511, wxPATH_MKDIR_FULL); + + return Str; +} + +// originally an ExportMultipleDialog method. Append suffix if newName appears in otherNames. +void FileNames::MakeNameUnique(FilePaths &otherNames, + wxFileName &newName) +{ + if (otherNames.Index(newName.GetFullName(), false) >= 0) { + int i=2; + wxString orig = newName.GetName(); + do { + newName.SetName(wxString::Format(wxT("%s-%d"), orig, i)); + i++; + } while (otherNames.Index(newName.GetFullName(), false) >= 0); + } + otherNames.push_back(newName.GetFullName()); +} + +// The APP name has upercase first letter (so that Quit Audacity is correctly +// capitalised on Mac, but we want lower case APP name in paths. +// This function does that substitution, IF the last component of +// the path is 'Audacity'. +wxString FileNames::LowerCaseAppNameInPath( const wxString & dirIn){ + wxString dir = dirIn; + // BUG 1577 Capitalisation of Audacity in path... + if( dir.EndsWith( "Audacity" ) ) + { + int nChars = dir.length() - wxString( "Audacity" ).length(); + dir = dir.Left( nChars ) + "audacity"; + } + return dir; +} + +FilePath FileNames::DataDir() +{ + // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict + // between wxStandardPaths and wxConfig under Linux. The latter + // creates a normal file as "$HOME/.audacity", while the former + // expects the ".audacity" portion to be a directory. + if (gDataDir.empty()) + { + // If there is a directory "Portable Settings" relative to the + // executable's EXE file, the prefs are stored in there, otherwise + // the prefs are stored in the user data dir provided by the OS. + wxFileName exePath(PlatformCompatibility::GetExecutablePath()); +#if defined(__WXMAC__) + // Path ends for example in "Audacity.app/Contents/MacOSX" + //exePath.RemoveLastDir(); + //exePath.RemoveLastDir(); + // just remove the MacOSX part. + exePath.RemoveLastDir(); +#endif + wxFileName portablePrefsPath(exePath.GetPath(), wxT("Portable Settings")); + + if (::wxDirExists(portablePrefsPath.GetFullPath())) + { + // Use "Portable Settings" folder + gDataDir = portablePrefsPath.GetFullPath(); + } else + { + // Use OS-provided user data dir folder + wxString dataDir( LowerCaseAppNameInPath( wxStandardPaths::Get().GetUserDataDir() )); +#if defined( __WXGTK__ ) + dataDir = dataDir + wxT("-data"); +#endif + gDataDir = FileNames::MkDir(dataDir); + } + } + return gDataDir; +} + +FilePath FileNames::ResourcesDir(){ + wxString resourcesDir( LowerCaseAppNameInPath( wxStandardPaths::Get().GetResourcesDir() )); + return resourcesDir; +} + +FilePath FileNames::HtmlHelpDir() +{ +#if defined(__WXMAC__) + wxFileName exePath(PlatformCompatibility::GetExecutablePath()); + // Path ends for example in "Audacity.app/Contents/MacOSX" + //exePath.RemoveLastDir(); + //exePath.RemoveLastDir(); + // just remove the MacOSX part. + exePath.RemoveLastDir(); + + //for mac this puts us within the .app: Audacity.app/Contents/SharedSupport/ + return wxFileName( exePath.GetPath()+wxT("/help/manual"), wxEmptyString ).GetFullPath(); +#else + //linux goes into /*prefix*/share/audacity/ + //windows (probably) goes into the dir containing the .exe + wxString dataDir = FileNames::LowerCaseAppNameInPath( wxStandardPaths::Get().GetDataDir()); + return wxFileName( dataDir+wxT("/help/manual"), wxEmptyString ).GetFullPath(); +#endif +} + +FilePath FileNames::LegacyChainDir() +{ + // Don't force creation of it + return wxFileName{ DataDir(), wxT("Chains") }.GetFullPath(); +} + +FilePath FileNames::MacroDir() +{ + return FileNames::MkDir( wxFileName( DataDir(), wxT("Macros") ).GetFullPath() ); +} + +FilePath FileNames::NRPDir() +{ + return FileNames::MkDir( wxFileName( DataDir(), wxT("NRP") ).GetFullPath() ); +} + +FilePath FileNames::NRPFile() +{ + return wxFileName( NRPDir(), wxT("noisegate.nrp") ).GetFullPath(); +} + +FilePath FileNames::PlugInDir() +{ + return FileNames::MkDir( wxFileName( DataDir(), wxT("Plug-Ins") ).GetFullPath() ); +} + +FilePath FileNames::PluginRegistry() +{ + return wxFileName( DataDir(), wxT("pluginregistry.cfg") ).GetFullPath(); +} + +FilePath FileNames::PluginSettings() +{ + return wxFileName( DataDir(), wxT("pluginsettings.cfg") ).GetFullPath(); +} + +FilePath FileNames::BaseDir() +{ + wxFileName baseDir; + +#if defined(__WXMAC__) + baseDir = PlatformCompatibility::GetExecutablePath(); + + // Path ends for example in "Audacity.app/Contents/MacOSX" + //baseDir.RemoveLastDir(); + //baseDir.RemoveLastDir(); + // just remove the MacOSX part. + baseDir.RemoveLastDir(); +#elif defined(__WXMSW__) + // Don't use wxStandardPaths::Get().GetDataDir() since it removes + // the "Debug" directory in debug builds. + baseDir = PlatformCompatibility::GetExecutablePath(); +#else + // Linux goes into /*prefix*/share/audacity/ + baseDir = FileNames::LowerCaseAppNameInPath(wxStandardPaths::Get().GetPluginsDir()); +#endif + + return baseDir.GetPath(); +} + +FilePath FileNames::ModulesDir() +{ + wxFileName modulesDir(BaseDir(), wxEmptyString); + + modulesDir.AppendDir(wxT("modules")); + + return modulesDir.GetFullPath(); +} + +FilePath FileNames::ThemeDir() +{ + return FileNames::MkDir( wxFileName( DataDir(), wxT("Theme") ).GetFullPath() ); +} + +FilePath FileNames::ThemeComponentsDir() +{ + return FileNames::MkDir( wxFileName( ThemeDir(), wxT("Components") ).GetFullPath() ); +} + +FilePath FileNames::ThemeCachePng() +{ + return wxFileName( ThemeDir(), wxT("ImageCache.png") ).GetFullPath(); +} + +FilePath FileNames::ThemeCacheHtm() +{ + return wxFileName( ThemeDir(), wxT("ImageCache.htm") ).GetFullPath(); +} + +FilePath FileNames::ThemeImageDefsAsCee() +{ + return wxFileName( ThemeDir(), wxT("ThemeImageDefsAsCee.h") ).GetFullPath(); +} + +FilePath FileNames::ThemeCacheAsCee( ) +{ +// DA: Theme sourcery file name. +#ifndef EXPERIMENTAL_DA + return wxFileName( ThemeDir(), wxT("ThemeAsCeeCode.h") ).GetFullPath(); +#else + return wxFileName( ThemeDir(), wxT("DarkThemeAsCeeCode.h") ).GetFullPath(); +#endif +} + +FilePath FileNames::ThemeComponent(const wxString &Str) +{ + return wxFileName( ThemeComponentsDir(), Str, wxT("png") ).GetFullPath(); +} + +// +// Returns the full path of program module (.exe, .dll, .so, .dylib) containing address +// +FilePath FileNames::PathFromAddr(void *addr) +{ + wxFileName name; + +#if defined(__WXMAC__) || defined(__WXGTK__) + Dl_info info; + if (dladdr(addr, &info)) { + char realname[PLATFORM_MAX_PATH + 1]; + int len; + name = LAT1CTOWX(info.dli_fname); + len = readlink(OSINPUT(name.GetFullPath()), realname, PLATFORM_MAX_PATH); + if (len > 0) { + realname[len] = 0; + name.SetFullName(LAT1CTOWX(realname)); + } + } +#elif defined(__WXMSW__) && defined(_UNICODE) + // The GetModuleHandlEx() function did not appear until Windows XP and + // GetModuleFileName() did appear until Windows 2000, so we have to + // check for them at runtime. + typedef BOOL (WINAPI *getmodulehandleex)(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE* phModule); + typedef DWORD (WINAPI *getmodulefilename)(HMODULE hModule, LPWCH lpFilename, DWORD nSize); + getmodulehandleex gmhe = + (getmodulehandleex) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetModuleHandleExW"); + getmodulefilename gmfn = + (getmodulefilename) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetModuleFileNameW"); + + if (gmhe != NULL && gmfn != NULL) { + HMODULE module; + if (gmhe(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPTSTR) addr, + &module)) { + TCHAR path[MAX_PATH]; + DWORD nSize; + + nSize = gmfn(module, path, MAX_PATH); + if (nSize && nSize < MAX_PATH) { + name = LAT1CTOWX(path); + } + } + } +#endif + + return name.GetFullPath(); +} + + +bool FileNames::IsPathAvailable( const FilePath & Path){ + if( Path.IsEmpty() ) + return false; +#ifndef __WIN32__ + return true; +#else + wxFileNameWrapper filePath( Path ); + return filePath.DirExists() && !filePath.FileExists(); +#endif +} + +wxFileNameWrapper FileNames::DefaultToDocumentsFolder(const wxString &preference) +{ + wxFileNameWrapper result; + +#ifdef __WIN32__ + wxFileName defaultPath( wxStandardPaths::Get().GetDocumentsDir(), "" ); + defaultPath.AppendDir( wxTheApp->GetAppName() ); + result.SetPath( gPrefs->Read( preference, defaultPath.GetPath( wxPATH_GET_VOLUME ) ) ); + + // MJB: Bug 1899 & Bug 2007. Only create directory if the result is the default path + bool bIsDefaultPath = result == defaultPath; + if( !bIsDefaultPath ) + { + // IF the prefs directory doesn't exist - (Deleted by our user perhaps?) + // or exists as a file + // THEN fallback to using the default directory. + bIsDefaultPath = !IsPathAvailable( result.GetPath(wxPATH_GET_VOLUME|wxPATH_GET_SEPARATOR ) ); + if( bIsDefaultPath ) + { + result.SetPath( defaultPath.GetPath( wxPATH_GET_VOLUME ) ); + // Don't write to gPrefs. + // We typically do it later, (if directory actually gets used) + } + } + if ( bIsDefaultPath ) + { + // The default path might not exist since it is a sub-directory of 'Documents' + // There is no error if the path could not be created. That's OK. + // The dialog that Audacity offers will allow the user to select a valid directory. + result.Mkdir(0755, wxPATH_MKDIR_FULL); + } +#else + result.AssignHomeDir(); + result.SetPath(gPrefs->Read( preference, result.GetPath() + "/Documents")); +#endif + + return result; +} + +wxString FileNames::PreferenceKey(FileNames::Operation op, FileNames::PathType type) +{ + wxString key; + switch (op) { + case FileNames::Operation::Temp: + key = wxT("/Directories/TempDir"); break; + case FileNames::Operation::Presets: + key = wxT("/Presets/Path"); break; + case FileNames::Operation::Open: + key = wxT("/Directories/Open"); break; + case FileNames::Operation::Save: + key = wxT("/Directories/Save"); break; + case FileNames::Operation::Import: + key = wxT("/Directories/Import"); break; + case FileNames::Operation::Export: + key = wxT("/Directories/Export"); break; + case FileNames::Operation::MacrosOut: + key = wxT("/Directories/MacrosOut"); break; + case FileNames::Operation::_None: + default: + break; + } + + switch (type) { + case FileNames::PathType::User: + key += "/Default"; break; + case FileNames::PathType::LastUsed: + key += "/LastUsed"; break; + case FileNames::PathType::_None: + default: + break; + } + + return key; +} + +FilePath FileNames::FindDefaultPath(Operation op) +{ + auto key = PreferenceKey(op, PathType::User); + + if (key.empty()) + return wxString{}; + + // If the user specified a default path, then use that + FilePath path = gPrefs->Read(key, wxT("")); + if (!path.empty()) { + return path; + } + + // Maybe the last used path is available + key = PreferenceKey(op, PathType::LastUsed); + path = gPrefs->Read(key, wxT("")); + if (!path.empty()) { + return path; + } + + // Last resort is to simply return the default folder + return DefaultToDocumentsFolder("").GetPath(); +} + +void FileNames::UpdateDefaultPath(Operation op, const FilePath &path) +{ + if (path.empty()) + return; + wxString key; + if (op == Operation::Temp) { + key = PreferenceKey(op, PathType::_None); + } + else { + key = PreferenceKey(op, PathType::LastUsed); + } + if (!key.empty()) { + gPrefs->Write(key, path); + gPrefs->Flush(); + } +} + +FilePath +FileNames::SelectFile(Operation op, + const TranslatableString& message, + const FilePath& default_path, + const FilePath& default_filename, + const FileExtension& default_extension, + const FileTypes& fileTypes, + int flags, + wxWindow *parent) +{ + return WithDefaultPath(op, default_path, [&](const FilePath &path) { + wxString filter; + if ( !default_extension.empty() ) + filter = wxT("*.") + default_extension; + return FileSelector( + message.Translation(), path, default_filename, filter, + FormatWildcard( fileTypes ), + flags, parent, wxDefaultCoord, wxDefaultCoord); + }); +} + +bool FileNames::IsMidi(const FilePath &fName) +{ + const auto extension = fName.AfterLast(wxT('.')); + return + extension.IsSameAs(wxT("gro"), false) || + extension.IsSameAs(wxT("midi"), false) || + extension.IsSameAs(wxT("mid"), false); +} + +static FilePaths sAudacityPathList; + +const FilePaths &FileNames::AudacityPathList() +{ + return sAudacityPathList; +} + +void FileNames::SetAudacityPathList( FilePaths list ) +{ + sAudacityPathList = std::move( list ); +} + +// static +void FileNames::AddUniquePathToPathList(const FilePath &pathArg, + FilePaths &pathList) +{ + wxFileNameWrapper pathNorm { pathArg }; + pathNorm.Normalize(); + const wxString newpath{ pathNorm.GetFullPath() }; + + for(const auto &path : pathList) { + if (pathNorm == wxFileNameWrapper{ path }) + return; + } + + pathList.push_back(newpath); +} + +// static +void FileNames::AddMultiPathsToPathList(const wxString &multiPathStringArg, + FilePaths &pathList) +{ + wxString multiPathString(multiPathStringArg); + while (!multiPathString.empty()) { + wxString onePath = multiPathString.BeforeFirst(wxPATH_SEP[0]); + multiPathString = multiPathString.AfterFirst(wxPATH_SEP[0]); + AddUniquePathToPathList(onePath, pathList); + } +} + +#include + +// static +void FileNames::FindFilesInPathList(const wxString & pattern, + const FilePaths & pathList, + FilePaths & results, + int flags) +{ + wxLogNull nolog; + + if (pattern.empty()) { + return; + } + + wxFileNameWrapper ff; + + for(size_t i = 0; i < pathList.size(); i++) { + ff = pathList[i] + wxFILE_SEP_PATH + pattern; + wxDir::GetAllFiles(ff.GetPath(), &results, ff.GetFullName(), flags); + } +} + +#if defined(__WXMSW__) +static wxCharBuffer mFilename; + +// +// On Windows, wxString::mb_str() can return a NULL pointer if the +// conversion to multi-byte fails. So, based on direction intent, +// returns a pointer to an empty string or prompts for a NEW name. +// +char *FileNames::VerifyFilename(const wxString &s, bool input) +{ + static wxCharBuffer buf; + wxString name = s; + + if (input) { + if ((char *) (const char *)name.mb_str() == NULL) { + name = wxEmptyString; + } + } + else { + wxFileName ff(name); + FileExtension ext; + while ((char *) (const char *)name.mb_str() == NULL) { + AudacityMessageBox( + XO( +"The specified filename could not be converted due to Unicode character use.")); + + ext = ff.GetExt(); + name = FileNames::SelectFile(FileNames::Operation::_None, + XO("Specify New Filename:"), + wxEmptyString, + name, + ext, + { ext.empty() + ? FileNames::AllFiles + : FileType{ {}, { ext } } + }, + wxFD_SAVE | wxRESIZE_BORDER, + wxGetTopLevelParent(NULL)); + } + } + + mFilename = name.mb_str(); + + return (char *) (const char *) mFilename; +} +#endif + +bool FileNames::WritableLocationCheck(const FilePath& path) +{ + bool status = wxFileName::IsDirWritable(path); + + if (!status) + { + using namespace BasicUI; + ShowMessageBox( + XO("Directory %s does not have write permissions").Format(path), + MessageBoxOptions{} + .Caption(XO("Error")) + .IconStyle(Icon::Error) + .ButtonStyle(Button::Ok) + ); + } + + return status; +} + +// Using this with wxStringArray::Sort will give you a list that +// is alphabetical, without depending on case. If you use the +// default sort, you will get strings with 'R' before 'a', because it is in caps. +int FileNames::CompareNoCase(const wxString& first, const wxString& second) +{ + return first.CmpNoCase(second); +} + +// Create a unique filename using the passed prefix and suffix +wxString FileNames::CreateUniqueName(const wxString &prefix, + const wxString &suffix /* = wxEmptyString */) +{ + static int count = 0; + + return wxString::Format(wxT("%s %s N-%i.%s"), + prefix, + wxDateTime::Now().Format(wxT("%Y-%m-%d %H-%M-%S")), + ++count, + suffix); +} + +wxString FileNames::UnsavedProjectExtension() +{ + return wxT("aup3unsaved"); +} + +// How to detect whether the file system of a path is FAT +// No apparent way to do it with wxWidgets +#if defined(__DARWIN__) +#include +bool FileNames::IsOnFATFileSystem(const FilePath &path) +{ + struct statfs fs; + if (statfs(wxPathOnly(path).c_str(), &fs)) + // Error from statfs + return false; + return 0 == strcmp(fs.f_fstypename, "msdos"); +} +#elif defined(__linux__) +#include +#include "/usr/include/linux/magic.h" +bool FileNames::IsOnFATFileSystem(const FilePath &path) +{ + struct statfs fs; + if (statfs(wxPathOnly(path).c_str(), &fs)) + // Error from statfs + return false; + return fs.f_type == MSDOS_SUPER_MAGIC; +} +#elif defined(_WIN32) +#include +bool FileNames::IsOnFATFileSystem(const FilePath &path) +{ + wxFileNameWrapper fileName{path}; + if (!fileName.HasVolume()) + return false; + auto volume = AbbreviatePath(fileName) + wxT("\\"); + DWORD volumeFlags; + wxChar volumeType[64]; + if (!::GetVolumeInformationW( + volume.wc_str(), NULL, 0, NULL, NULL, + &volumeFlags, + volumeType, + WXSIZEOF(volumeType))) + return false; + wxString type(volumeType); + if (type == wxT("FAT") || type == wxT("FAT32")) + return true; + return false; +} +#else +bool FileNames::IsOnFATFileSystem(const FilePath &path) +{ + return false; +} +#endif + +wxString FileNames::AbbreviatePath( const wxFileName &fileName ) +{ + wxString target; +#ifdef __WXMSW__ + + // Drive letter plus colon + target = fileName.GetVolume() + wxT(":"); + +#else + + // Shorten the path, arbitrarily to 3 components + auto path = fileName; + path.SetFullName(wxString{}); + while(path.GetDirCount() > 3) + path.RemoveLastDir(); + target = path.GetFullPath(); + +#endif + return target; +} + diff --git a/src/SelectFile.h b/src/SelectFile.h new file mode 100644 index 000000000..6f1cc5ecf --- /dev/null +++ b/src/SelectFile.h @@ -0,0 +1,263 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + FileNames.h + + James Crook + +**********************************************************************/ + +#ifndef __AUDACITY_FILE_NAMES__ +#define __AUDACITY_FILE_NAMES__ + + + +#include // for wxDIR_FILES +#include // function return value +#include "Identifier.h" +#include "Prefs.h" +#include + +// Please try to support unlimited path length instead of using PLATFORM_MAX_PATH! +// Define one constant for maximum path value, so we don't have to do +// platform-specific conditionals everywhere we want to check it. +#define PLATFORM_MAX_PATH 260 // Play it safe for default, with same value as Windows' MAX_PATH. + +#ifdef __WXMAC__ +#undef PLATFORM_MAX_PATH +#define PLATFORM_MAX_PATH PATH_MAX +#endif + +#ifdef __WXGTK__ +// Some systems do not restrict the path length and therefore PATH_MAX is undefined +#ifdef PATH_MAX +#undef PLATFORM_MAX_PATH +#define PLATFORM_MAX_PATH PATH_MAX +#endif +#endif + +#ifdef __WXX11__ +// wxX11 should also get the platform-specific definition of PLATFORM_MAX_PATH, so do not declare here. +#endif + +#ifdef __WXMSW__ +#undef PLATFORM_MAX_PATH +#define PLATFORM_MAX_PATH MAX_PATH +#endif + +class wxFileName; +class wxFileNameWrapper; + +namespace FileNames +{ + // A description of a type of file + struct FileType { + FileType() = default; + + FileType( TranslatableString d, FileExtensions e, bool a = false ) + : description{ std::move( d ) } + , extensions( std::move( e ) ) + , appendExtensions{ a } + {} + + TranslatableString description; + FileExtensions extensions; + // Whether to extend the displayed description with mention of the + // extensions: + bool appendExtensions = false; + }; + + // Frequently used types + extern AUDACITY_DLL_API const FileType + AllFiles // * + , AudacityProjects // *.aup3 + , DynamicLibraries // depends on the operating system + , TextFiles // *.txt + , XMLFiles; // *.xml, *.XML + + using FileTypes = std::vector< FileType >; + + // Convert fileTypes into a single string as expected by wxWidgets file + // selection dialog + AUDACITY_DLL_API wxString FormatWildcard( const FileTypes &fileTypes ); + + // This exists to compensate for bugs in wxCopyFile: + AUDACITY_DLL_API bool DoCopyFile( + const FilePath& file1, const FilePath& file2, bool overwrite = true); + + // wxWidgets doesn't have a function to do this: make a hard file-system + // link if possible. It might not be, as when the paths are on different + // storage devices. + AUDACITY_DLL_API + bool HardLinkFile( const FilePath& file1, const FilePath& file2); + + AUDACITY_DLL_API wxString MkDir(const wxString &Str); + + AUDACITY_DLL_API bool IsMidi(const FilePath &fName); + + /** \brief A list of directories that should be searched for Audacity files + * (plug-ins, help files, etc.). + * + * On Unix this will include the directory Audacity was installed into, + * plus the current user's .audacity-data/Plug-Ins directory. Additional + * directories can be specified using the AUDACITY_PATH environment + * variable. On Windows or Mac OS, this will include the directory + * which contains the Audacity program. */ + AUDACITY_DLL_API const FilePaths &AudacityPathList(); + AUDACITY_DLL_API void SetAudacityPathList( FilePaths list ); + + // originally an ExportMultipleDialog method. Append suffix if newName appears in otherNames. + AUDACITY_DLL_API void MakeNameUnique( + FilePaths &otherNames, wxFileName &newName); + + AUDACITY_DLL_API wxString LowerCaseAppNameInPath( const wxString & dirIn); + /** \brief Audacity user data directory + * + * Where audacity keeps its settings and other user data squirreled away, + * by default ~/.audacity-data/ on Unix, Application Data/Audacity on + * windows system */ + AUDACITY_DLL_API FilePath DataDir(); + AUDACITY_DLL_API FilePath ResourcesDir(); + AUDACITY_DLL_API FilePath HtmlHelpDir(); + AUDACITY_DLL_API FilePath HtmlHelpIndexFile(bool quick); + AUDACITY_DLL_API FilePath LegacyChainDir(); + AUDACITY_DLL_API FilePath MacroDir(); + AUDACITY_DLL_API FilePath NRPDir(); + AUDACITY_DLL_API FilePath NRPFile(); + AUDACITY_DLL_API FilePath PluginRegistry(); + AUDACITY_DLL_API FilePath PluginSettings(); + + AUDACITY_DLL_API FilePath BaseDir(); + AUDACITY_DLL_API FilePath ModulesDir(); + + /** \brief The user plug-in directory (not a system one) + * + * This returns the string path to where the user may have put plug-ins + * if they don't have system admin rights. Under default settings, it's + * /Plug-Ins/ */ + AUDACITY_DLL_API FilePath PlugInDir(); + AUDACITY_DLL_API FilePath ThemeDir(); + AUDACITY_DLL_API FilePath ThemeComponentsDir(); + AUDACITY_DLL_API FilePath ThemeCachePng(); + AUDACITY_DLL_API FilePath ThemeCacheAsCee(); + AUDACITY_DLL_API FilePath ThemeComponent(const wxString &Str); + AUDACITY_DLL_API FilePath ThemeCacheHtm(); + AUDACITY_DLL_API FilePath ThemeImageDefsAsCee(); + + // Obtain name of loaded module that contains address + AUDACITY_DLL_API FilePath PathFromAddr(void *addr); + + AUDACITY_DLL_API bool IsPathAvailable( const FilePath & Path); + AUDACITY_DLL_API wxFileNameWrapper DefaultToDocumentsFolder + (const wxString &preference); + + // If not None, determines a preference key (for the default path string) to + // be read and updated + enum class Operation { + // _ on None to defeat some macro that is expanding this. + _None, + + // These do not have a specific pathtype + Temp, + Presets, + + // These have default/lastused pathtypes + Open, + Save, + Import, + Export, + MacrosOut + }; + + enum class PathType { + // _ on None to defeat some macro that is expanding this. + _None, + User, + LastUsed + }; + + AUDACITY_DLL_API wxString PreferenceKey(FileNames::Operation op, FileNames::PathType type); + + AUDACITY_DLL_API FilePath FindDefaultPath(Operation op); + AUDACITY_DLL_API void UpdateDefaultPath(Operation op, const FilePath &path); + + // F is a function taking a wxString, returning wxString + template + FilePath WithDefaultPath + (Operation op, const FilePath &defaultPath, F function) + { + auto path = gPrefs->Read(PreferenceKey(op, PathType::User), defaultPath); + if (path.empty()) + path = FileNames::FindDefaultPath(op); + auto result = function(path); + FileNames::UpdateDefaultPath(op, ::wxPathOnly(result)); + return result; + } + + AUDACITY_DLL_API FilePath + SelectFile(Operation op, // op matters only when default_path is empty + const TranslatableString& message, + const FilePath& default_path, + const FilePath& default_filename, + const FileExtension& default_extension, + const FileTypes& fileTypes, + int flags, + wxWindow *parent); + + // Useful functions for working with search paths + AUDACITY_DLL_API void AddUniquePathToPathList(const FilePath &path, + FilePaths &pathList); + AUDACITY_DLL_API void AddMultiPathsToPathList(const wxString &multiPathString, + FilePaths &pathList); + AUDACITY_DLL_API void FindFilesInPathList(const wxString & pattern, + const FilePaths & pathList, + FilePaths &results, + int flags = wxDIR_FILES); + + /** \brief Protect against Unicode to multi-byte conversion failures + * on Windows */ +#if defined(__WXMSW__) + AUDACITY_DLL_API char *VerifyFilename(const wxString &s, bool input = true); +#endif + + //! Check location on writable access and return true if checked successfully. + AUDACITY_DLL_API bool WritableLocationCheck(const FilePath& path); + + // wxString compare function for sorting case, which is needed to load correctly. + AUDACITY_DLL_API int CompareNoCase(const wxString& first, const wxString& second); + + // Create a unique filename using the passed prefix and suffix + AUDACITY_DLL_API wxString CreateUniqueName(const wxString &prefix, + const wxString &suffix = wxEmptyString); + + // File extension used for unsaved/temporary project files + AUDACITY_DLL_API wxString UnsavedProjectExtension(); + + AUDACITY_DLL_API + bool IsOnFATFileSystem(const FilePath &path); + + AUDACITY_DLL_API + //! Give enough of the path to identify the device. (On Windows, drive letter plus ':') + wxString AbbreviatePath(const wxFileName &fileName); +}; + +// Use this macro to wrap all filenames and pathnames that get +// passed directly to a system call, like opening a file, creating +// a directory, checking to see that a file exists, etc... +#if defined(__WXMSW__) +// Note, on Windows we don't define an OSFILENAME() to prevent accidental use. +// See VerifyFilename() for an explanation. +#define OSINPUT(X) FileNames::VerifyFilename(X, true) +#define OSOUTPUT(X) FileNames::VerifyFilename(X, false) +#elif defined(__WXMAC__) +#define OSFILENAME(X) ((char *) (const char *)(X).fn_str()) +#define OSINPUT(X) OSFILENAME(X) +#define OSOUTPUT(X) OSFILENAME(X) +#else +#define OSFILENAME(X) ((char *) (const char *)(X).mb_str()) +#define OSINPUT(X) OSFILENAME(X) +#define OSOUTPUT(X) OSFILENAME(X) +#endif + +#endif