mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-20 17:41:13 +02:00
New library for files
This commit is contained in:
@@ -12,6 +12,7 @@ set( LIBRARIES
|
||||
lib-exceptions
|
||||
lib-preferences
|
||||
lib-math
|
||||
lib-files
|
||||
)
|
||||
|
||||
if ( ${_OPT}has_networking )
|
||||
|
135
libraries/lib-files/AudacityLogger.cpp
Normal file
135
libraries/lib-files/AudacityLogger.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
AudacityLogger.cpp
|
||||
|
||||
******************************************************************//**
|
||||
|
||||
\class AudacityLogger
|
||||
\brief AudacityLogger is a thread-safe logger class
|
||||
|
||||
Provides thread-safe logging based on the wxWidgets log facility.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
|
||||
#include "AudacityLogger.h"
|
||||
|
||||
#include "Internat.h"
|
||||
#include "MemoryX.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <wx/ffile.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/tokenzr.h>
|
||||
|
||||
//
|
||||
// AudacityLogger class
|
||||
//
|
||||
// By providing an Audacity specific logging class, it can be made thread-safe and,
|
||||
// as such, can be used by the ever growing threading within Audacity.
|
||||
//
|
||||
|
||||
AudacityLogger *AudacityLogger::Get()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
std::call_once( flag, []{
|
||||
// wxWidgets will clean up the logger for the main thread, so we can say
|
||||
// safenew. See:
|
||||
// http://docs.wxwidgets.org/3.0/classwx_log.html#a2525bf54fa3f31dc50e6e3cd8651e71d
|
||||
std::unique_ptr < wxLog > // DELETE any previous logger
|
||||
{ wxLog::SetActiveTarget(safenew AudacityLogger) };
|
||||
} );
|
||||
|
||||
// Use dynamic_cast so that we get a NULL ptr in case our logger
|
||||
// is no longer the target.
|
||||
return dynamic_cast<AudacityLogger *>(wxLog::GetActiveTarget());
|
||||
}
|
||||
|
||||
AudacityLogger::AudacityLogger()
|
||||
: wxEvtHandler(),
|
||||
wxLog()
|
||||
{
|
||||
mUpdated = false;
|
||||
}
|
||||
|
||||
AudacityLogger::~AudacityLogger() = default;
|
||||
|
||||
void AudacityLogger::Flush()
|
||||
{
|
||||
if (mUpdated && mListener && mListener())
|
||||
mUpdated = false;
|
||||
}
|
||||
|
||||
auto AudacityLogger::SetListener(Listener listener) -> Listener
|
||||
{
|
||||
auto result = std::move(mListener);
|
||||
mListener = std::move(listener);
|
||||
return result;
|
||||
}
|
||||
|
||||
void AudacityLogger::DoLogText(const wxString & str)
|
||||
{
|
||||
if (!wxIsMainThread()) {
|
||||
wxMutexGuiEnter();
|
||||
}
|
||||
|
||||
if (mBuffer.empty()) {
|
||||
wxString stamp;
|
||||
|
||||
TimeStamp(&stamp);
|
||||
|
||||
mBuffer << stamp << _TS("Audacity ") << AUDACITY_VERSION_STRING << wxT("\n");
|
||||
}
|
||||
|
||||
mBuffer << str << wxT("\n");
|
||||
|
||||
mUpdated = true;
|
||||
|
||||
Flush();
|
||||
|
||||
if (!wxIsMainThread()) {
|
||||
wxMutexGuiLeave();
|
||||
}
|
||||
}
|
||||
|
||||
bool AudacityLogger::SaveLog(const wxString &fileName) const
|
||||
{
|
||||
wxFFile file(fileName, wxT("w"));
|
||||
|
||||
if (file.IsOpened()) {
|
||||
file.Write(mBuffer);
|
||||
file.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudacityLogger::ClearLog()
|
||||
{
|
||||
mBuffer = wxEmptyString;
|
||||
DoLogText(wxT("Log Cleared."));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxString AudacityLogger::GetLog(int count)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
wxString buffer;
|
||||
|
||||
auto lines = wxStringTokenize(mBuffer, wxT("\r\n"), wxTOKEN_RET_DELIMS);
|
||||
for (int index = lines.size() - 1; index >= 0 && count > 0; --index, --count)
|
||||
{
|
||||
buffer.Prepend(lines[index]);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
61
libraries/lib-files/AudacityLogger.h
Normal file
61
libraries/lib-files/AudacityLogger.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
AudacityLogger.h
|
||||
|
||||
Dominic Mazzoni
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_LOGGER__
|
||||
#define __AUDACITY_LOGGER__
|
||||
|
||||
#include <wx/log.h> // to inherit
|
||||
#include <wx/event.h> // to inherit wxEvtHandler
|
||||
|
||||
class wxFrame;
|
||||
class wxTextCtrl;
|
||||
|
||||
class FILES_API AudacityLogger final : public wxEvtHandler,
|
||||
public wxLog
|
||||
{
|
||||
public:
|
||||
|
||||
~AudacityLogger() override;
|
||||
|
||||
// Get the singleton instance or null
|
||||
static AudacityLogger *Get();
|
||||
|
||||
bool SaveLog(const wxString &fileName) const;
|
||||
bool ClearLog();
|
||||
|
||||
//! Retrieve all or some of the lines since most recent ClearLog or start of program
|
||||
/*! If `count == 0` or is more than the number of lines, return all; else return the last `count` lines */
|
||||
wxString GetLog(int count = 0);
|
||||
|
||||
//! Get all the accumulated text since program start or last ClearLog()
|
||||
const wxString &GetBuffer() const { return mBuffer; }
|
||||
|
||||
void Flush() override;
|
||||
|
||||
//! Type of function called by Flush
|
||||
/*! @return true if flush completed
|
||||
*/
|
||||
using Listener = std::function< bool() >;
|
||||
|
||||
//! Set the unique listener, returning any previous one
|
||||
Listener SetListener(Listener listener);
|
||||
|
||||
protected:
|
||||
void DoLogText(const wxString & msg) override;
|
||||
|
||||
private:
|
||||
AudacityLogger();
|
||||
|
||||
Listener mListener;
|
||||
wxString mBuffer;
|
||||
bool mUpdated;
|
||||
};
|
||||
|
||||
#endif
|
34
libraries/lib-files/CMakeLists.txt
Normal file
34
libraries/lib-files/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
#[[
|
||||
A library of utilities relating generally to files and file names, including
|
||||
an exception type for file errors.
|
||||
|
||||
Also the definition of certain significant file paths, such as the directory
|
||||
for temporary projects.
|
||||
|
||||
Also the global logger which can save to a file.
|
||||
]]#
|
||||
|
||||
set( SOURCES
|
||||
AudacityLogger.cpp
|
||||
AudacityLogger.h
|
||||
FileException.cpp
|
||||
FileException.h
|
||||
FileIO.cpp
|
||||
FileIO.h
|
||||
FileNames.cpp
|
||||
FileNames.h
|
||||
PlatformCompatibility.cpp
|
||||
PlatformCompatibility.h
|
||||
TempDirectory.cpp
|
||||
TempDirectory.h
|
||||
wxFileNameWrapper.h
|
||||
)
|
||||
set( LIBRARIES
|
||||
lib-exceptions-interface
|
||||
lib-preferences-interface
|
||||
PRIVATE
|
||||
wxBase
|
||||
)
|
||||
audacity_library( lib-files "${SOURCES}" "${LIBRARIES}"
|
||||
"" ""
|
||||
)
|
66
libraries/lib-files/FileException.cpp
Normal file
66
libraries/lib-files/FileException.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
@file FileException.cpp
|
||||
@brief implements FileException
|
||||
|
||||
|
||||
Created by Paul Licameli on 11/22/16.
|
||||
|
||||
*/
|
||||
|
||||
#include "FileException.h"
|
||||
#include "FileNames.h"
|
||||
|
||||
#include "Prefs.h"
|
||||
|
||||
FileException::~FileException()
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString FileException::ErrorMessage() const
|
||||
{
|
||||
TranslatableString format;
|
||||
switch (cause) {
|
||||
case Cause::Open:
|
||||
format = XO("Audacity failed to open a file in %s.");
|
||||
break;
|
||||
case Cause::Read:
|
||||
format = XO("Audacity failed to read from a file in %s.");
|
||||
break;
|
||||
case Cause::Write:
|
||||
return WriteFailureMessage(fileName);
|
||||
case Cause::Rename:
|
||||
format =
|
||||
XO("Audacity successfully wrote a file in %s but failed to rename it as %s.");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return format.Format(
|
||||
FileNames::AbbreviatePath(fileName), renameTarget.GetFullName() );
|
||||
}
|
||||
|
||||
wxString FileException::ErrorHelpUrl() const
|
||||
{
|
||||
switch (cause) {
|
||||
case Cause::Open:
|
||||
case Cause::Read:
|
||||
return "Error:_Opening_or_reading_file";
|
||||
break;
|
||||
case Cause::Write:
|
||||
case Cause::Rename:
|
||||
return "Error:_Disk_full_or_not_writable";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
TranslatableString
|
||||
FileException::WriteFailureMessage(const wxFileName &fileName)
|
||||
{
|
||||
return XO("Audacity failed to write to a file.\n"
|
||||
"Perhaps %s is not writable or the disk is full.\n"
|
||||
"For tips on freeing up space, click the help button."
|
||||
).Format(FileNames::AbbreviatePath(fileName));
|
||||
}
|
67
libraries/lib-files/FileException.h
Normal file
67
libraries/lib-files/FileException.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*!
|
||||
@file FileException.h
|
||||
@brief MessageBoxException for failures of file operations
|
||||
|
||||
|
||||
Created by Paul Licameli on 11/22/16.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __AUDACITY_FILE_EXCEPTION__
|
||||
#define __AUDACITY_FILE_EXCEPTION__
|
||||
|
||||
#include "AudacityException.h" // to inherit
|
||||
#include <wx/filename.h> // wxFileName member variable
|
||||
|
||||
//! Thrown for failure of file or database operations in deeply nested places
|
||||
class FILES_API FileException /* not final */
|
||||
: public MessageBoxException
|
||||
{
|
||||
public:
|
||||
//! Identifies file operation that failed
|
||||
enum class Cause {
|
||||
Open,
|
||||
Read,
|
||||
Write, //!< most important to detect when storage space is exhausted
|
||||
Rename //!< involves two filenames
|
||||
};
|
||||
|
||||
explicit FileException(
|
||||
Cause cause_, //!< What kind of file operation failed
|
||||
const wxFileName &fileName_, //!< Which file suffered a failure
|
||||
const TranslatableString &caption = XO("File Error"), //!< Shown in message box frame, not the main message
|
||||
const wxFileName &renameTarget_ = {} //!< A second file name, only for renaming failure
|
||||
)
|
||||
// DV: We consider a FileException to be internal for now.
|
||||
// We used to have some odd cases related to the read-only folder in 3.0.0,
|
||||
// so it is good to have a full picture here.
|
||||
// In case we see too many - we will tweak this behavior in the next release.
|
||||
: MessageBoxException{ ExceptionType::Internal, caption }
|
||||
, cause{ cause_ }, fileName{ fileName_ }, renameTarget{ renameTarget_ }
|
||||
{}
|
||||
|
||||
FileException( const FileException &that )
|
||||
: MessageBoxException( that )
|
||||
, cause{ that.cause }
|
||||
, fileName{ that.fileName }
|
||||
, renameTarget{ that.renameTarget }
|
||||
{}
|
||||
|
||||
FileException& operator= (FileException&&) = delete;
|
||||
|
||||
~FileException() override;
|
||||
|
||||
static TranslatableString WriteFailureMessage(const wxFileName &fileName);
|
||||
|
||||
protected:
|
||||
//! %Format an error message appropriate for the @ref Cause.
|
||||
TranslatableString ErrorMessage() const override;
|
||||
wxString ErrorHelpUrl() const override;
|
||||
|
||||
public:
|
||||
Cause cause;
|
||||
wxFileName fileName;
|
||||
wxFileName renameTarget;
|
||||
};
|
||||
|
||||
#endif
|
84
libraries/lib-files/FileIO.cpp
Normal file
84
libraries/lib-files/FileIO.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
FileIO.cpp
|
||||
|
||||
Leland Lucius
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "FileIO.h"
|
||||
|
||||
#include <wx/defs.h>
|
||||
#include <wx/crt.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include "wxFileNameWrapper.h"
|
||||
|
||||
FileIO::FileIO(const wxFileNameWrapper & name, FileIOMode mode)
|
||||
: mMode(mode),
|
||||
mOpen(false)
|
||||
{
|
||||
wxString scheme;
|
||||
|
||||
auto path = name.GetFullPath();
|
||||
if (mMode == FileIO::Input) {
|
||||
mInputStream = std::make_unique<wxFFileInputStream>(path);
|
||||
if (mInputStream == NULL || !mInputStream->IsOk()) {
|
||||
wxPrintf(wxT("Couldn't get input stream: %s\n"), path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mOutputStream = std::make_unique<wxFFileOutputStream>(path);
|
||||
if (mOutputStream == NULL || !mOutputStream->IsOk()) {
|
||||
wxPrintf(wxT("Couldn't get output stream: %s\n"), path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mOpen = true;
|
||||
}
|
||||
|
||||
FileIO::~FileIO()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool FileIO::IsOpened()
|
||||
{
|
||||
return mOpen;
|
||||
}
|
||||
|
||||
bool FileIO::Close()
|
||||
{
|
||||
bool success = true;
|
||||
if (mOutputStream) {
|
||||
// mOutputStream->Sync() returns void! Rrr!
|
||||
success = mOutputStream->GetFile()->Flush() &&
|
||||
mOutputStream->Close();
|
||||
mOutputStream.reset();
|
||||
}
|
||||
mInputStream.reset();
|
||||
mOpen = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
wxInputStream & FileIO::Read(void *buf, size_t size)
|
||||
{
|
||||
if (mInputStream == NULL) {
|
||||
return *mInputStream;
|
||||
}
|
||||
|
||||
return mInputStream->Read(buf, size);
|
||||
}
|
||||
|
||||
wxOutputStream & FileIO::Write(const void *buf, size_t size)
|
||||
{
|
||||
if (mOutputStream == NULL) {
|
||||
return *mOutputStream;
|
||||
}
|
||||
|
||||
return mOutputStream->Write(buf, size);
|
||||
}
|
50
libraries/lib-files/FileIO.h
Normal file
50
libraries/lib-files/FileIO.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
FileIO.h
|
||||
|
||||
Leland Lucius
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_FILEIO__
|
||||
#define __AUDACITY_FILEIO__
|
||||
|
||||
#include <memory>
|
||||
|
||||
class wxInputStream;
|
||||
class wxOutputStream;
|
||||
class wxFFileOutputStream;
|
||||
class wxFileNameWrapper;
|
||||
|
||||
class FILES_API FileIO
|
||||
{
|
||||
public:
|
||||
typedef enum FileIOMode
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
} FileIOMode;
|
||||
|
||||
public:
|
||||
FileIO(const wxFileNameWrapper & name, FileIOMode mode);
|
||||
|
||||
// Calls Close()
|
||||
~FileIO();
|
||||
|
||||
bool IsOpened();
|
||||
|
||||
bool Close();
|
||||
|
||||
wxInputStream & Read(void *buffer, size_t size);
|
||||
wxOutputStream & Write(const void *buffer, size_t size);
|
||||
|
||||
private:
|
||||
FileIOMode mMode;
|
||||
std::unique_ptr<wxInputStream> mInputStream;
|
||||
std::unique_ptr<wxFFileOutputStream> mOutputStream;
|
||||
bool mOpen;
|
||||
};
|
||||
|
||||
#endif
|
767
libraries/lib-files/FileNames.cpp
Normal file
767
libraries/lib-files/FileNames.cpp
Normal file
@@ -0,0 +1,767 @@
|
||||
/**********************************************************************
|
||||
|
||||
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 <memory>
|
||||
|
||||
#include <wx/defs.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/stdpaths.h>
|
||||
#include "BasicUI.h"
|
||||
#include "Prefs.h"
|
||||
#include "Internat.h"
|
||||
#include "ModuleConstants.h"
|
||||
#include "PlatformCompatibility.h"
|
||||
#include "wxFileNameWrapper.h"
|
||||
|
||||
#if defined(__WXMAC__) || defined(__WXGTK__)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#if defined(__WXMSW__)
|
||||
#include <windows.h>
|
||||
#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__)
|
||||
#define OSFILENAME(X) ((char *) (const char *)(X).fn_str())
|
||||
#define OSINPUT(X) OSFILENAME(X)
|
||||
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( AppName );
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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 <wx/log.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 <sys/mount.h>
|
||||
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 <sys/statfs.h>
|
||||
#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 <fileapi.h>
|
||||
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;
|
||||
}
|
227
libraries/lib-files/FileNames.h
Normal file
227
libraries/lib-files/FileNames.h
Normal file
@@ -0,0 +1,227 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
FileNames.h
|
||||
|
||||
James Crook
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_FILE_NAMES__
|
||||
#define __AUDACITY_FILE_NAMES__
|
||||
|
||||
#include <wx/dir.h> // for wxDIR_FILES
|
||||
#include <wx/string.h> // function return value
|
||||
#include "Identifier.h"
|
||||
#include "Prefs.h"
|
||||
|
||||
// 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 FILES_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
|
||||
FILES_API wxString FormatWildcard( const FileTypes &fileTypes );
|
||||
|
||||
// This exists to compensate for bugs in wxCopyFile:
|
||||
FILES_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.
|
||||
FILES_API
|
||||
bool HardLinkFile( const FilePath& file1, const FilePath& file2);
|
||||
|
||||
FILES_API wxString MkDir(const wxString &Str);
|
||||
|
||||
FILES_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. */
|
||||
FILES_API const FilePaths &AudacityPathList();
|
||||
FILES_API void SetAudacityPathList( FilePaths list );
|
||||
|
||||
// originally an ExportMultipleDialog method. Append suffix if newName appears in otherNames.
|
||||
FILES_API void MakeNameUnique(
|
||||
FilePaths &otherNames, wxFileName &newName);
|
||||
|
||||
FILES_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 */
|
||||
FILES_API FilePath DataDir();
|
||||
FILES_API FilePath ResourcesDir();
|
||||
FILES_API FilePath HtmlHelpDir();
|
||||
FILES_API FilePath HtmlHelpIndexFile(bool quick);
|
||||
FILES_API FilePath LegacyChainDir();
|
||||
FILES_API FilePath MacroDir();
|
||||
FILES_API FilePath NRPDir();
|
||||
FILES_API FilePath NRPFile();
|
||||
FILES_API FilePath PluginRegistry();
|
||||
FILES_API FilePath PluginSettings();
|
||||
|
||||
FILES_API FilePath BaseDir();
|
||||
FILES_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
|
||||
* <DataDir>/Plug-Ins/ */
|
||||
FILES_API FilePath PlugInDir();
|
||||
FILES_API FilePath ThemeDir();
|
||||
FILES_API FilePath ThemeComponentsDir();
|
||||
FILES_API FilePath ThemeCachePng();
|
||||
FILES_API FilePath ThemeCacheAsCee();
|
||||
FILES_API FilePath ThemeComponent(const wxString &Str);
|
||||
FILES_API FilePath ThemeCacheHtm();
|
||||
FILES_API FilePath ThemeImageDefsAsCee();
|
||||
|
||||
// Obtain name of loaded module that contains address
|
||||
FILES_API FilePath PathFromAddr(void *addr);
|
||||
|
||||
FILES_API bool IsPathAvailable( const FilePath & Path);
|
||||
FILES_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
|
||||
};
|
||||
|
||||
FILES_API wxString PreferenceKey(FileNames::Operation op, FileNames::PathType type);
|
||||
|
||||
FILES_API FilePath FindDefaultPath(Operation op);
|
||||
FILES_API void UpdateDefaultPath(Operation op, const FilePath &path);
|
||||
|
||||
// F is a function taking a wxString, returning wxString
|
||||
template<typename F>
|
||||
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;
|
||||
}
|
||||
|
||||
// Useful functions for working with search paths
|
||||
FILES_API void AddUniquePathToPathList(const FilePath &path,
|
||||
FilePaths &pathList);
|
||||
FILES_API void AddMultiPathsToPathList(const wxString &multiPathString,
|
||||
FilePaths &pathList);
|
||||
FILES_API void FindFilesInPathList(const wxString & pattern,
|
||||
const FilePaths & pathList,
|
||||
FilePaths &results,
|
||||
int flags = wxDIR_FILES);
|
||||
|
||||
|
||||
//! Check location on writable access and return true if checked successfully.
|
||||
FILES_API bool WritableLocationCheck(const FilePath& path);
|
||||
|
||||
// wxString compare function for sorting case, which is needed to load correctly.
|
||||
FILES_API int CompareNoCase(const wxString& first, const wxString& second);
|
||||
|
||||
// Create a unique filename using the passed prefix and suffix
|
||||
FILES_API wxString CreateUniqueName(const wxString &prefix,
|
||||
const wxString &suffix = wxEmptyString);
|
||||
|
||||
// File extension used for unsaved/temporary project files
|
||||
FILES_API wxString UnsavedProjectExtension();
|
||||
|
||||
FILES_API
|
||||
bool IsOnFATFileSystem(const FilePath &path);
|
||||
|
||||
FILES_API
|
||||
//! Give enough of the path to identify the device. (On Windows, drive letter plus ':')
|
||||
wxString AbbreviatePath(const wxFileName &fileName);
|
||||
};
|
||||
|
||||
#endif
|
46
libraries/lib-files/PlatformCompatibility.cpp
Normal file
46
libraries/lib-files/PlatformCompatibility.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
PlatformCompatibility.cpp
|
||||
|
||||
Markus Meyer
|
||||
|
||||
*******************************************************************//*!
|
||||
|
||||
\class PlatformCompatibility
|
||||
\brief Filename Compatibility utilities.
|
||||
|
||||
\see FileNames
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
|
||||
#include "PlatformCompatibility.h"
|
||||
|
||||
#include <wx/filefn.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stdpaths.h>
|
||||
#include <wx/app.h>
|
||||
|
||||
FilePath PlatformCompatibility::GetLongFileName(const FilePath &shortFileName)
|
||||
{
|
||||
wxFileName fn(shortFileName);
|
||||
|
||||
return fn.GetLongPath();
|
||||
}
|
||||
|
||||
const FilePath &PlatformCompatibility::GetExecutablePath()
|
||||
{
|
||||
static bool found = false;
|
||||
static FilePath path;
|
||||
|
||||
if (!found) {
|
||||
path = wxStandardPaths::Get().GetExecutablePath();
|
||||
|
||||
found = true;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
44
libraries/lib-files/PlatformCompatibility.h
Normal file
44
libraries/lib-files/PlatformCompatibility.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
PlatformCompatibility.h
|
||||
|
||||
Platform-specific compatibility functions
|
||||
|
||||
This file implements functions needed to work around
|
||||
platform-specific problems and which cannot be solved by a simple
|
||||
#ifdef/#endif plus two or three lines additional code. Wherever
|
||||
possible, the implementation should be such, that the function is
|
||||
implemented on every platform, but is a dummy for those platforms
|
||||
on which it is not needed, so additional #ifdef's are unnecessary.
|
||||
|
||||
Markus Meyer
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_COMPATIBILITY__
|
||||
#define __AUDACITY_COMPATIBILITY__
|
||||
|
||||
#include "Identifier.h"
|
||||
|
||||
class FILES_API PlatformCompatibility
|
||||
{
|
||||
public:
|
||||
//
|
||||
// On Win32, this function gets the long file name (like
|
||||
// "C:\Program Files\Project.aup3") from a short file name like
|
||||
// "C:\PROGRA~1\PROJEC~1.AUP. On other systems, the function
|
||||
// just returns the exact string it is given.
|
||||
//
|
||||
static FilePath GetLongFileName(const FilePath& shortFileName);
|
||||
|
||||
//
|
||||
// Get filename and path of executable (e.g. "/usr/bin/audacity" on
|
||||
// Linux or "C:\Program Files\Audacity\Audacity.exe" on Windows)
|
||||
// This string is unchanging
|
||||
//
|
||||
static const FilePath &GetExecutablePath();
|
||||
};
|
||||
|
||||
#endif
|
130
libraries/lib-files/TempDirectory.cpp
Normal file
130
libraries/lib-files/TempDirectory.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
TempDirectory.cpp
|
||||
|
||||
Paul Licameli split from FileNames.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "TempDirectory.h"
|
||||
|
||||
#include "FileNames.h"
|
||||
#include "BasicUI.h"
|
||||
|
||||
static wxString &TempDirPath()
|
||||
{
|
||||
static wxString path;
|
||||
return path;
|
||||
}
|
||||
|
||||
/// Returns the directory used for temp files.
|
||||
/// \todo put a counter in here to see if it gets used a lot.
|
||||
/// if it does, then maybe we should cache the path name
|
||||
/// each time.
|
||||
wxString TempDirectory::TempDir()
|
||||
{
|
||||
auto &path = TempDirPath();
|
||||
if (gPrefs && path.empty())
|
||||
path =
|
||||
gPrefs->Read(PreferenceKey(FileNames::Operation::Temp,
|
||||
FileNames::PathType::_None), wxT(""));
|
||||
|
||||
if (FileNames::IsOnFATFileSystem(path))
|
||||
{
|
||||
BasicUI::ShowErrorDialog( {},
|
||||
XO("Unsuitable"),
|
||||
XO("The temporary files directory is on a FAT formatted drive.\n"
|
||||
"Resetting to default location."),
|
||||
"Error:_Unsuitable_drive"
|
||||
);
|
||||
|
||||
path = DefaultTempDir();
|
||||
UpdateDefaultPath(FileNames::Operation::Temp, path);
|
||||
}
|
||||
|
||||
return FileNames::MkDir(path);
|
||||
}
|
||||
|
||||
void TempDirectory::ResetTempDir()
|
||||
{
|
||||
TempDirPath().clear();
|
||||
}
|
||||
|
||||
/** \brief Default temp directory */
|
||||
static FilePath sDefaultTempDir;
|
||||
|
||||
const FilePath &TempDirectory::DefaultTempDir()
|
||||
{
|
||||
return sDefaultTempDir;
|
||||
}
|
||||
|
||||
void TempDirectory::SetDefaultTempDir( const FilePath &tempDir )
|
||||
{
|
||||
sDefaultTempDir = tempDir;
|
||||
}
|
||||
|
||||
// We now disallow temp directory name that puts it where cleaner apps will
|
||||
// try to clean out the files.
|
||||
bool TempDirectory::IsTempDirectoryNameOK( const FilePath & Name )
|
||||
{
|
||||
if( Name.empty() )
|
||||
return false;
|
||||
|
||||
wxFileName tmpFile;
|
||||
tmpFile.AssignTempFileName(wxT("nn"));
|
||||
// use Long Path to expand out any abbreviated long substrings.
|
||||
wxString BadPath = tmpFile.GetLongPath();
|
||||
::wxRemoveFile(tmpFile.GetFullPath());
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// This test is to fix bug 1220 on a 1.x to 2.x to 2.1.3 upgrade.
|
||||
// It is less permissive than we could be as it stops a path
|
||||
// with this string ANYWHERE within it rather than excluding just
|
||||
// the paths that the earlier Audacities used to create.
|
||||
if( Name.Contains( "/tmp/") )
|
||||
return false;
|
||||
BadPath = BadPath.BeforeLast( '/' ) + "/";
|
||||
wxFileName cmpFile( Name );
|
||||
wxString NameCanonical = cmpFile.GetLongPath( ) + "/";
|
||||
#else
|
||||
BadPath = BadPath.BeforeLast( '\\' ) + "\\";
|
||||
wxFileName cmpFile( Name );
|
||||
wxString NameCanonical = cmpFile.GetLongPath( ) + "\\";
|
||||
#endif
|
||||
|
||||
if (FATFilesystemDenied(NameCanonical,
|
||||
XO("The temporary files directory is on a FAT formatted drive.\n"
|
||||
"Resetting to default location.")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(NameCanonical.StartsWith( BadPath ));
|
||||
}
|
||||
|
||||
wxString TempDirectory::UnsavedProjectFileName()
|
||||
{
|
||||
wxFileName fn(TempDir(),
|
||||
FileNames::CreateUniqueName(wxT("New Project"), FileNames::UnsavedProjectExtension()));
|
||||
|
||||
return fn.GetFullPath();
|
||||
}
|
||||
|
||||
bool TempDirectory::FATFilesystemDenied( const FilePath &path,
|
||||
const TranslatableString &msg, const BasicUI::WindowPlacement &placement )
|
||||
{
|
||||
if (FileNames::IsOnFATFileSystem(path))
|
||||
{
|
||||
BasicUI::ShowErrorDialog( placement,
|
||||
XO("Unsuitable"),
|
||||
XO("%s\n\nFor tips on suitable drives, click the help button.").Format(msg),
|
||||
"Error:_Unsuitable_drive"
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
39
libraries/lib-files/TempDirectory.h
Normal file
39
libraries/lib-files/TempDirectory.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
TempDirectory.h
|
||||
|
||||
Paul Licameli split from FileNames.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_TEMP_DIRECTORY__
|
||||
#define __AUDACITY_TEMP_DIRECTORY__
|
||||
|
||||
|
||||
#include "BasicUI.h"
|
||||
|
||||
class TranslatableString;
|
||||
class wxWindow;
|
||||
|
||||
#include "Identifier.h"
|
||||
|
||||
namespace TempDirectory
|
||||
{
|
||||
FILES_API wxString TempDir();
|
||||
FILES_API void ResetTempDir();
|
||||
|
||||
FILES_API const FilePath &DefaultTempDir();
|
||||
FILES_API void SetDefaultTempDir( const FilePath &tempDir );
|
||||
FILES_API bool IsTempDirectoryNameOK( const FilePath & Name );
|
||||
|
||||
// Create a filename for an unsaved/temporary project file
|
||||
FILES_API wxString UnsavedProjectFileName();
|
||||
|
||||
FILES_API bool FATFilesystemDenied(const FilePath &path,
|
||||
const TranslatableString &msg,
|
||||
const BasicUI::WindowPlacement &placement = {});
|
||||
};
|
||||
|
||||
#endif
|
92
libraries/lib-files/wxFileNameWrapper.h
Normal file
92
libraries/lib-files/wxFileNameWrapper.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
wxFileNameWrapper.h
|
||||
|
||||
Paul Licameli
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_WXFILENAMEWRAPPER__
|
||||
#define __AUDACITY_WXFILENAMEWRAPPER__
|
||||
|
||||
class wxArrayString;
|
||||
|
||||
#include <wx/filename.h> // to inherit
|
||||
|
||||
// The wxFileName does not have a move constructor.
|
||||
// So add one to it, so that it passes around by value more quickly.
|
||||
class wxFileNameWrapper : public wxFileName
|
||||
{
|
||||
public:
|
||||
using wxFileName::wxFileName;
|
||||
|
||||
explicit
|
||||
wxFileNameWrapper(const wxFileName &that)
|
||||
: wxFileName(that)
|
||||
{}
|
||||
|
||||
wxFileNameWrapper() = default;
|
||||
wxFileNameWrapper(const wxFileNameWrapper &that) = default;
|
||||
wxFileNameWrapper &operator= (const wxFileNameWrapper &that) = default;
|
||||
|
||||
void swap(wxFileNameWrapper &that)
|
||||
{
|
||||
if (this != &that) {
|
||||
#if 0
|
||||
// Awful hack number 1 makes gcc 5 choke
|
||||
enum : size_t { Size = sizeof(*this) };
|
||||
// Do it bitwise.
|
||||
// std::aligned_storage<Size>::type buffer;
|
||||
char buffer[Size];
|
||||
memcpy(&buffer, this, Size);
|
||||
memcpy(this, &that, Size);
|
||||
memcpy(&that, &buffer, Size);
|
||||
#else
|
||||
// Awful hack number 2 relies on knowing the class layout
|
||||
// This is the less evil one but watch out for redefinition of the base class
|
||||
struct Contents
|
||||
{
|
||||
void swap(Contents &that) {
|
||||
m_volume.swap(that.m_volume);
|
||||
m_dirs.swap(that.m_dirs);
|
||||
m_name.swap(that.m_name);
|
||||
m_ext.swap(that.m_ext);
|
||||
std::swap(m_relative, that.m_relative);
|
||||
std::swap(m_hasExt, that.m_hasExt);
|
||||
std::swap(m_dontFollowLinks, that.m_dontFollowLinks);
|
||||
};
|
||||
|
||||
wxString m_volume;
|
||||
wxArrayString m_dirs;
|
||||
wxString m_name, m_ext;
|
||||
bool m_relative;
|
||||
bool m_hasExt;
|
||||
bool m_dontFollowLinks;
|
||||
};
|
||||
|
||||
reinterpret_cast<Contents*>(this)->swap
|
||||
(*reinterpret_cast<Contents*>(&that));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Define move copy and assignment in terms of swap
|
||||
wxFileNameWrapper(wxFileNameWrapper &&that)
|
||||
{
|
||||
swap(that);
|
||||
}
|
||||
|
||||
wxFileNameWrapper &operator= (wxFileNameWrapper &&that)
|
||||
{
|
||||
if (this != &that) {
|
||||
Clear();
|
||||
swap(that);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -15,7 +15,6 @@
|
||||
|
||||
#include <wx/defs.h>
|
||||
#include <wx/fileconf.h>
|
||||
|
||||
#include "Identifier.h"
|
||||
|
||||
class PREFERENCES_API FileConfig : public wxConfigBase
|
||||
|
Reference in New Issue
Block a user