• R/O
  • SSH
  • HTTPS

dzlib-tools:


File Info

Rev. 1511
Size 146,369 bytes
Time 2023-02-01 19:11:42
Author dummzeuch
Log Message

new class procedure TFilesystem.AssertMatchingFileExists

Content

{.GXFormatter.config=twm}
/// <summary>
/// implements utility functions for file accesss </summary>
unit u_dzFileUtils;

{$INCLUDE 'dzlib.inc'}

interface

uses
  Windows,
  SysUtils,
  Classes,
  Types,
  u_dzTranslator,
  u_dzTypes;

type
  EFileUtils = class(Exception);
  ECreateUniqueDir = class(EFileUtils);
  /// <summary>
  /// raised by DelTree if the DirName parameter is not a valid directory name </summary>
  EDirNotFound = class(EFileUtils);
  EPathTooLong = class(EFileUtils);
  EInvalidPropertyCombination = class(EFileUtils);
  EFileNotFound = class(EFileUtils);
  EBackupFailed = class(EFileUtils);

type
  TFileAttributes = (
    dfaReadonly,
    dfaHidden, // Hidden files
    dfaSysFile, // System files
    dfaDirectory, // Directory files
    dfaArchive // Archive files
    );

  TFileAttributeSet = set of TFileAttributes;

const
  ALL_FILES_ATTRIB_SET = [dfaHidden, dfaSysFile, dfaDirectory, dfaArchive];

type
  TFileInfoRec = record
    Filename: string;
    Size: Int64;
    Timestamp: TDateTime;
  end;

const
  FILE_READ_DATA = $0001; // file & pipe
  FILE_LIST_DIRECTORY = $0001; // directory
  FILE_WRITE_DATA = $0002; // file & pipe
  FILE_ADD_FILE = $0002; // directory
  FILE_APPEND_DATA = $0004; // file
  FILE_ADD_SUBDIRECTORY = $0004; // directory
  FILE_CREATE_PIPE_INSTANCE = $0004; // named pipe
  FILE_READ_EA = $0008; // file & directory
  FILE_WRITE_EA = $0010; // file & directory
  FILE_EXECUTE = $0020; // file
  FILE_TRAVERSE = $0020; // directory
  FILE_DELETE_CHILD = $0040; // directory
  FILE_READ_ATTRIBUTES = $0080; // all
  FILE_WRITE_ATTRIBUTES = $0100; // all
  FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $1FF;
  FILE_GENERIC_READ = STANDARD_RIGHTS_READ or FILE_READ_DATA or FILE_READ_ATTRIBUTES or
    FILE_READ_EA or SYNCHRONIZE;
  FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or
    FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE;
  FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE or FILE_READ_ATTRIBUTES or FILE_EXECUTE or
    SYNCHRONIZE;

type
  TOnFileEnumCallback = procedure(_Sender: TObject; const _Filename: string) of object;

type
  /// <summary>
  /// a simple wrapper around FindFirst/FindNext which allows to search for
  /// specified attributes only (e.g. only directories), it automatically
  /// ignores the special '.' and '..' directories. </summary>
  TSimpleDirEnumerator = class
  protected
    /// stores the search mask ('c:\windows\*.exe')
    FMask: string;
    /// set of attributes a file must match
    FMustHaveAttr: TFileAttributeSet;
    /// set of attributes a file may have
    FMayHaveAttr: TFileAttributeSet;
    /// internally used TSearchRec structure
    FSr: TSearchRec;
    /// true if FindFirst was called and returned no error code
    FActive: Boolean;
    /// number of matching files found
    FMatchCount: Integer;
  public
    /// <summary>
    /// Creates a TSimpleDirEnumerator, sets the Mask, MayHaveAttr and MustHaveAttr
    /// properties.
    /// MustHaveAttr is set to []
    /// @param Mask is the file search mask and should include a path
    /// @param MayHaveAttr is the value for the MayHaveAttr property, defaults to
    ///                    ALL_FILES_ATTRIB_SET which includes all possible attributes,
    ///                    so calling FindNext will find any files or subdirectories,
    ///                    except the special '.' and '..' directories </summary>
    constructor Create(const _Mask: string; _MayHaveAttr: TFileAttributeSet = ALL_FILES_ATTRIB_SET);
    ///<summary>
    /// creates a TSimpleDirEnumerator for enumerating directories only </summary>
    constructor CreateForDirsOnly(const _Mask: string);
    ///<summary>
    /// creates a TSimpleDirEnumerator for enumerating files only </summary>
    constructor CreateForFilesOnly(const _Mask: string);
    /// <summary>
    /// Destructor, will call FindClose if necessary </summary>
    destructor Destroy; override;
    /// <summary>
    /// creates a TSimpleDirEnumerator, calls its FindAll method and frees it
    /// @param List is a string list to which the files will be appended, may be nil
    /// @param IncludePath determines whether the List of filenames includes the full path or not </summary>
    class function Execute(const _Mask: string; _List: TStrings;
      _MayHaveAttr: TFileAttributeSet = ALL_FILES_ATTRIB_SET; _IncludePath: Boolean = False; _Sort: Boolean = True): Integer;
    /// <summary>
    /// creates a TSimpleDirEnumerator, calls its FindAll method and frees it
    /// @param List is a string list to which the files will be appended, may be nil
    /// @param IncludePath determines whether the List of filenames includes the full path or not </summary>
    class function EnumFilesOnly(const _Mask: string; _List: TStrings;
      _IncludePath: Boolean = False; _Sort: Boolean = True): Integer; overload;
    class function EnumFilesOnly(const _Mask: string;
      _IncludePath: Boolean = False; _Sort: Boolean = True): TStringArray; overload;
    ///<summary>
    /// Calls the given callback for all files matching the mask. To Abort, raise EAbort. </summary>
    class procedure EnumFilesOnly(const _Mask: string; _Callback: TOnFileEnumCallback;
      _IncludePath: Boolean = False; _Sort: Boolean = True); overload;
    /// <summary>
    /// creates a TSimpleDirEnumerator, calls its FindAll method and frees it
    /// @param List is a string list to which the files will be appended, may be nil
    /// @param IncludePath determines whether the List of filenames includes the full path or not </summary>
    class function EnumDirsOnly(const _Mask: string; _List: TStrings;
      _IncludePath: Boolean = False; _Sort: Boolean = True): Integer; overload;
    class function EnumDirsOnly(const _Mask: string; _IncludePath: Boolean = False; _Sort: Boolean = True): TStringArray; overload;
    /// <summary>
    /// Calls SysUtils.FindFirst on first call and SysUtls.FindNext in later
    /// calls.
    /// @param Filename is the name of the file found, if result is true, if you need
    ///       more information about it, use the SR property, note that it
    ///       does not include the path
    /// @param IncludePath determines whether the List of filenames includes the full path or not
    ///                    defaults to false
    /// @Returns true, if a matching file was found, false otherwise </summary>
    function FindNext(out _Filename: string; _IncludePath: Boolean = False): Boolean; overload;
    /// <summary>
    /// Calls SysUtils.FindFirst on first call and SysUtls.FindNext in later
    /// calls. If it returns true, use the SR property to get information about
    /// the file. See the overloaded @link(FindNext) version if you need only
    /// the filename.
    /// @Returns true, if a matching file was found, false otherwise </summary>
    function FindNext: Boolean; overload;
    /// <summary>
    /// Calls FindNext until it returns false, stores all filenames in List and
    /// returns the number of files found.
    /// @param List is a TStrings object which will be filled with the filenames
    ///        of matching files, may be nil.
    /// @param IncludePath determines whether the List of filenames includes the full path or not
    /// @returns the number of matching files </summary>
    function FindAll(_List: TStrings = nil; _IncludePath: Boolean = False): Integer;
    /// <summary>
    /// Calls FindClose so FindNext will start again. Reset does not change any
    /// properties (e.g. Mask, MustHaveAttr, MayHaveAttr) </summary>
    procedure Reset;
    /// <summary>
    /// Returns the number of matches so far, that is the number of successful
    /// calls to FindNext. </summary>
    property MatchCount: Integer read FMatchCount;
    /// <summary>
    /// Returns the search mask </summary>
    property Mask: string read FMask; // write fMask;
    /// <summary>
    /// the set of attributes a file must have to be found by FindNext </summary>
    property MustHaveAttr: TFileAttributeSet read FMustHaveAttr write FMustHaveAttr;
    /// <summary>
    /// the set of allowed attributes for a file to be found by FindNext </summary>
    property MayHaveAttr: TFileAttributeSet read FMayHaveAttr write FMayHaveAttr;
    /// <summary>
    /// the search rec containing additional information about the file </summary>
    property Sr: TSearchRec read FSr;
  end;

type
  /// <summary>
  /// possible return values for the callback function </summary>
  TCopyProgressResult = (
    prContinue, // continue with the copy/move operation
    prCancel, // cancel the operation, cannot be resumed
    prStop, // stop the operation, can be resumed, if cfwRestartable was passed
    prQuiet); // continue the operation, do not call the callback

  /// <summary>
  /// reason for calling the callback function </summary>
  TCopyProgressReason = (
    prChunkFinished, // a chunk of the file has been copied
    prStreamSwitch); // started to copy a new stream (set in the first callback)

type
  /// <summary>
  /// Represents the status of a CopyFile/MoveFileWithProgress operation, passed
  /// as parameter to the callback function. </summary>
  TCopyProgressStatus = class
  protected
    FTotalFileSize: LARGE_INTEGER;
    FTotalBytesTransferred: LARGE_INTEGER;
    FStreamSize: LARGE_INTEGER;
    FStreamBytesTransferred: LARGE_INTEGER;
    FStreamNumber: LongWord;
    FCallbackReason: TCopyProgressReason;
    FSourceFile: THandle;
    FDestinationFile: THandle;
  public
    /// <summary>
    /// total size of the file </summary>
    property TotalFileSize: LARGE_INTEGER read FTotalFileSize;
    /// <summary>
    /// total bytes that have been transferred so far </summary>
    property TotalBytesTransferred: LARGE_INTEGER read FTotalBytesTransferred;
    /// <summary>
    /// size of the stream that is currently being transferred </summary>
    property StreamSize: LARGE_INTEGER read FStreamSize;
    /// <summary>
    /// bytes of the current stream taht have been transferred so far </summary>
    property StreamBytesTransferred: LARGE_INTEGER read FStreamBytesTransferred;
    /// <summary>
    /// Number of the current stream, starts with 1 (usually always 1) </summary>
    property StreamNumber: LongWord read FStreamNumber;
    /// <summary>
    /// reason for callback </summary>
    property CallbackReason: TCopyProgressReason read FCallbackReason;
    /// <summary>
    /// Handle of source file </summary>
    property SourceFile: THandle read FSourceFile;
    /// <summary>
    /// Handle of destination file </summary>
    property DestinationFile: THandle read FDestinationFile;
  end;

  ///<summary>
  /// Type for OnCopyFileProgress event
  ///  @param Status is the currenct status of the operation
  ///  @param Continue determines whether to continue copying or aborting, defaults
  ///         to prContinue </summary>
  TCopyFileProgressEvt = procedure(_Status: TCopyProgressStatus;
    var _Continue: TCopyProgressResult) of object;

  /// <summary>
  /// defines the action to take if a file already exists but has a different content </summary>
  TFileExistsAction = (feaIgnore, feaOverwrite);
  TQueryFileSyncAction = (fsaCopy, fsaSkip);
  TOnSyncing = procedure(_Sender: TObject; const _SrcDir, _DstDir: string) of object;
  TOnSyncingFile = procedure(_Sender: TObject; const _Source, _Dest: string; _Total, _Done: Int64) of object;

  ///<summary>
  /// called if a destination file already exists
  /// @param Action is the action to take, default is feaIgnore </summary>
  TOnFileExists = procedure(_Sender: TObject; const _SrcFile, _DstFile: TFileInfoRec;
    var _Action: TFileExistsAction) of object;

  ///<summary>
  /// Called instead of TOnFileExists if a destination file does not exist to allow filtering
  /// of e.g. file types.
  /// @param SyncIt must be set to false if the file should be skipped, default is true for
  ///               copying the file </summary>
  TOnQueryFileSync = procedure(_Sender: TObject; const _SrcFile: TFileInfoRec; const _DstFile: string;
    var _Action: TQueryFileSyncAction) of object;

  /// <summary>
  /// Synchronizes two directories </summary>
  TDirectorySync = class
  private
    FCurrentSource: string;
    FCurrentDest: string;
    FOnSyncingDir: TOnSyncing;
    FOnSyncingFile: TOnSyncingFile;
    FOnFileExists: TOnFileExists;
    FOnQueryFileSync: TOnQueryFileSync;
//    FOnDifferentFileExists: TOnDifferentFileExists;
//    FCheckContent: boolean;
//    procedure doOnDifferentFileExists(const _Filename: string; var _Action: TFileExistsAction);
    procedure doOnSyncingDir(const _SrcDir, _DstDir: string);
    ///<summary>
    /// Called before once before copying a file and possible several times while it is being
    /// copied to display a progress. </summary>
    procedure doOnSyncingFile(const _SrcFile, _DstFile: string; _Total, _Done: Int64);
    function doOnFileExists(const _SrcDir, _DstDir, _Filename: string): TFileExistsAction;
    function doOnQueryFileSync(const _SrcFile, _DstFile: string): TQueryFileSyncAction;
    procedure ProgressStatusCallback(_Status: TCopyProgressStatus; var _Continue: TCopyProgressResult);
  public
    /// <summary>
    /// Checks if there are files in the source directory that are already in
    /// the destination directory, for each file that exists, the OnFileExists
    /// event is called. </summary>
    procedure CheckOneWay(const _SrcDir, _DstDir: string);
    /// <summary>
    /// copies all files from DirA to DirB if they don't already exist
    /// (not implemented: if CheckContent=true, the content existing files will be checked and if
    ///                   it doesn't match, OnDifferentFileExists is called)
    /// @param FlattenDirHierarchy determines whether all files should be copied
    ///                            directly to DstDir or if subdirectories should
    ///                            be created, default is false </summary>
    procedure SyncOneWay(const _SrcDir, _DstDir: string; _FlattenDirHierarchy: Boolean = False);
    /// <summary>
    /// calls SyncOneWay(DirA, DirB) and SyncOneWay(DirB, DirA)
    /// (not implemented: if CheckContent=true, the content existing files will be checked and if
    ///                   it doesn't match, OnDifferentFileExists is called) </summary>
    procedure SyncBothWays(const _DirA, _DirB: string);
//    {! Not implemented: Called, if the content of an existing file is different }
//    property OnDifferentFileExists: TOnDifferentFileExists read FOnDifferentFileExists write FOnDifferentFileExists;
//    {! Not implemented: if true, OnDifferentFileExists will be called }
//    property CheckContent: boolean read FCheckContent write FCheckContent default false;
    /// <summary>
    /// called when a new directory is entered, to abort synchronization,
    /// raise an exception (e.g. SysUtils.Abort), and catch it in the calling method </summary>
    property OnSyncingDir: TOnSyncing read FOnSyncingDir write FOnSyncingDir;
    /// <summary>
    /// called when a file is being copied, to abort synchronization,
    /// raise an exception (e.g. SysUtils.Abort), and catch it in the calling method </summary>
    property OnSyncingFile: TOnSyncingFile read FOnSyncingFile write FOnSyncingFile;
    /// <summary>
    /// called from CheckOneWay if a destination file already exists </summary>
    property OnFileExists: TOnFileExists read FOnFileExists write FOnFileExists;
    ///<summary>
    /// Called from CheckOneWay instead of OnFileExists if the destination file does not
    /// exist. This is to allow filtering on e.g. file type. </summary>
    property OnQueryFileSync: TOnQueryFileSync read FOnQueryFileSync write FOnQueryFileSync;
  end;

  IUniqueTempDir = interface ['{D9A4A428-66AE-4BBC-B1CA-22CE4DE2FACB}']
    function Path: string;
    ///<summary> Path including trailing path delimiter </summary>
    function PathBS: string;
  end;

type
  TCopyFileFlags = (cfFailIfExists, cfForceOverwrite, cfRaiseException);
  TCopyFileFlagSet = set of TCopyFileFlags;
  TCopyFileFlagIfExists = (cfeFailIfExists, cfeOverwriteIfExists);
  TCopyFileFlagOverwriteReadonly = (cforDoNotOverwriteReadonly, cforOverwriteReadonly);
  TMoveFileExFlags = (mfCopyAllowed, {mfCreateHardlink,}mfDelayUntilReboot, mfFailIfNotTrackable,
    mfReplaceExisting, mfWriteThrough);
  TMoveFileExFlagSet = set of TMoveFileExFlags;
  TMatchingFileResult = (mfNotFound, mfDirectory, mfFile, mfSpecial);
  TCopyFileWithProgressRestartable = (cfwrNotRestartable, cfwrRestartable);
  TCopyFileWithProgressFlags = (cfwFailIfExists, cfwRestartable, cfwRaiseException);
  TCopyFileWithProgressFlagSet = set of TCopyFileWithProgressFlags;
  TCopyFileWithProgressResult = (cfwOK, cfwAborted, cfwError);
  TMoveFileWithProgressFlags = (
    mfwFailIfExists, /// < fail if the destination file already exists
    mfwAllowCopy, /// < allow using copy and delete if necessary
    mfwDelayUntilReboot, /// < wait until next reboot for moving the file
    mfwWriteThrough, /// < Setting this value guarantees that a move performed as a copy and delete operation is flushed to disk before the function returns.
    mfwFailIfNotTrackable, /// < The function fails if the source file is a link source, but the file cannot be tracked after the move.
    mfwRaiseException); /// < raise an exception if there is an error
  TMoveFileWithProgressFlagSet = set of TMoveFileWithProgressFlags;
  TCopyDirCreateIntermediate = (cdciCreateIntermediate, cdciDoNotCreateIntermediate);

const
  /// <summary>
  /// set of char constant containing all characters that are invalid in a filename </summary>
  INVALID_FILENAME_CHARS: set of AnsiChar = ['\', '/', ':', '*', '?', '"', '<', '>', '|'];

type
  /// <summary>
  /// This class owns all utility functions as class methods so they don't pollute the name space </summary>
  TFileSystem = class
  public
    class function CheckAccessToFile(DesiredAccess: DWORD; const Filename: WideString): Boolean;
    ///<summary>
    /// wraps the windows API function GetFullPathName </summary>
    class function GetFullPathName(const _fn: string): string;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}
    /// <summary>
    /// Returns a temporary filename.
    /// @param Directory is a string with the directory to create the file in, defaults
    ///                  to the TEMP directory.
    /// @param Prefix is a string with a prefix for the filename, defaults to 'dz'.
    /// @param Unique is an word that the function converts to a hexadecimal string
    /// for use in creating the temporary filename.)
    /// <ul>
    ///   <li>If Unique is nonzero, the function appends the hexadecimal string to
    ///       <b>Prefix</b>
    ///       to form the temporary filename. In this case, the function does not create
    ///       the specified file, and does not test whether the filename is unique.</li>
    ///   <li>If Unique is zero, the function uses a hexadecimal string derived
    ///       from the current system time. In this case, the function uses different
    ///       values until it finds a unique filename, and then it creates the file
    ///       in the <b>Directory</b>.</li>
    /// </ul>
    /// @returns a filename to use for a temporary file. </summary>
    class function GetTempFileName(_Directory: string = ''; const _Prefix: string = 'dz';
      _Unique: Word = 0): string;
//    ///<summary>
//    /// Returns a temporary filename which is ensured not to already exist before but has been created
//    /// in this call.
//    /// @param Directory is a string with the directory to create the file in, defaults
//    ///                  to the TEMP directory.
//    /// @param Prefix is a string with a prefix for the filename, defaults to 'dz'.
//    /// @param Ext is the extension for the filename, defaults to '.tmp'. </summary>
//    class function GetTempFileNameEx(_Directory: string = ''; const _Prefix: string = 'dz';
//      const _Ext: string = '.tmp'): string;
    /// <summary>
    /// Calls the corresponding Windows function and returns the short path name
    /// for an *existing* file or directory. </summary>
    class function GetShortPathname(const _LongName: string): string;

    /// <summary>
    /// Creates a unique subdirectory under BaseDir with the given Prefix
    /// if Basedir is an empty string the system's %TEMP% directory is used.
    /// @returns the name of the created directory </summary>
    class function CreateUniqueDirectory(_BaseDir: string = ''; const _Prefix: string = 'dz'): string;
    class function CreateUniqueTempDir(_DeleteOnlyIfEmpty: Boolean = False;
      const _Prefix: string = 'dz'): IUniqueTempDir; overload;
    class function CreateUniqueTempDir(const _BaseDir: string; _DeleteOnlyIfEmpty: Boolean = False;
      const _Prefix: string = 'dz'): IUniqueTempDir; overload;

    /// <summary>
    /// Calls the Win32Api function GetTempPath but returns a string rather than
    /// a PChar.
    /// @returns a string with the TEMP directory </summary>
    class function GetTempPath: string;

    /// <summary>
    /// Moves the file Source to Dest using the Windows MoveFile function.
    /// NOTE: this function is deprecated, use the overloaded version!
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param RaiseException is a boolean which controls whether the function
    ///        retrieves the Windows error and raises an exception
    ///        if it fails. If false, it will not raise an exception
    ///        but just return false if moving the file fails.
    /// @returns true, if the file could be moved, false otherwise. </summary>
    class function MoveFile(const _Source, _Dest: string;
      _RaiseException: Boolean): Boolean; overload; deprecated;
    /// <summary>
    /// Moves the file Source to Dest using the Windows MoveFile function.
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function
    ///        retrieves the Windows error and raises an exception
    ///        if it fails. If ehReturnFalse, it will not raise an exception
    ///        but just return false if moving the file fails.
    /// @returns true, if the file could be moved, false otherwise. </summary>
    class function MoveFile(const _Source, _Dest: string;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean; overload;

    /// <summary>
    /// Moves the file Source to Dest using the Windows MoveFileEx function.
    /// NOTE: this function is deprecated, use the overloaded version!
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @Param Flags is a set of flags corresponding to the Windows MoveEx flags
    /// @param RaiseException is a boolean which controls whether the function
    ///        retrieves the Windows error and raises an exception
    ///        if it fails. If false, it will not raise an exception
    ///        but just return false if moving the file fails.
    /// @returns true, if the file could be moved, false otherwise. </summary>
    class function MoveFileEx(const _Source, _Dest: string; _Flags: TMoveFileExFlagSet;
      _RaiseException: Boolean): Boolean; overload; deprecated;
    /// <summary>
    /// Moves the file Source to Dest using the Windows MoveFileEx function.
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @Param Flags is a set of flags corresponding to the Windows MoveEx flags
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function
    ///        retrieves the Windows error and raises an exception
    ///        if it fails. If ehReturnFalse, it will not raise an exception
    ///        but just return false if moving the file fails.
    /// @returns true, if the file could be moved, false otherwise. </summary>
    class function MoveFileEx(const _Source, _Dest: string; _Flags: TMoveFileExFlagSet;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean; overload;

    /// <summary>
    /// Copies the file Source to Dest using the Windows CopyFile function.
    /// NOTE: this function is deprecated, use the overloaded version!
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param FailIfExists is a boolean specifying whether the copy operation
    ///                     should fail if the destination file already exists.
    /// @param RaiseException is a boolean which controls whether the function
    ///                       retrieves the Windows error and raises an exception
    ///                       if it fails. If false, it will not raise an exception
    ///                       but just return false if copying the file fails.
    /// @param ForceOverwrite is a boolean which controls whether the function removes
    ///                       a read-only flag from the destination file if necessary.
    /// @returns true, if the file could be copied, false otherwise.
    /// @raises  EOSError if an error occurs and cfwRaiseException was passed </summary>
    class function CopyFile(const _Source, _Dest: string; _FailIfExists: Boolean;
      _RaiseException: Boolean = True; _ForceOverwrite: Boolean = False): Boolean; overload; deprecated;
    /// <summary>
    /// Copies the file Source to Dest using the Windows CopyFile function.
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param IfExists specifies whether the copy operation should fail if the destination
    ///                 file already exists. It can have the values
    ///                 * cfeFailIfExists   and
    ///                 * cfeOverwriteIfExists
    ///                 Defaults to cfeFailIfExists.
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function retrieves
    ///                      the Windows error and raises an exception if it fails.
    ///                      If ehReturnFalse, it will not raise an exception
    ///                      but just return false if copying the file fails.
    /// @param IfReadOnly controls whether the function removes a read-only flag from the
    ///                   destination file if necessary. It can have the values
    ///                   * cforDoNotOverwriteReadonly    and
    ///                   * cforOverwriteReadonly
    ///                   Defaults to cforDoNotOverwriteReadonly.
    /// @returns true, if the file could be copied, false otherwise.
    /// @raises  EOSError if an error occurs and cfwRaiseException was passed </summary>
    class function CopyFile(const _Source, _Dest: string;
      _IfExists: TCopyFileFlagIfExists = cfeFailIfExists;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException;
      _IfReadOnly: TCopyFileFlagOverwriteReadonly = cforDoNotOverwriteReadonly): Boolean; overload;

    /// <summary>
    /// Copies the file Source to Dest using the Windows CopyFile function.
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param Flags is a set of TCopyFileFlags specifying whether the copy operation
    ///        cfFailIfExists: fail if the destination file already exists.
    ///        cfForceOverwrite: remove a read-only flag from the destination file if necessary.
    ///        cfRaiseException: retrieve the Windows error and raise an exception if it fails.
    ///          If not set, it will not raise an exception but just return false if
    ///          copying the file fails.
    /// @returns true, if the file could be copied, false otherwise.
    /// @raises  EOSError if an error occurs and cfwRaiseException was passed </summary>
    class function CopyFile(const _Source, _Dest: string; _Flags: TCopyFileFlagSet): Boolean; overload;

    /// <summary>
    /// Copies the file Source to Dest using the Windows CopyFileEx function which
    /// allows for a progress callback
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param Flags is a set of TCopyFileWithProgressFlags specifying whether the copy operation
    ///        cfwFailIfExists: fail if the destination file already exists.
    ///        cfwRestartable: stores information in the destination file that allows
    ///          to restart a stopped copy operation
    ///        cfwRaiseException: retrieve the Windows error and raise an exception if it fails.
    ///          If not set, it will not raise an exception but just return cfwAborted
    ///          or cfwError if copying the file fails. (set by default)
    /// @returns cfeOK, if the copying succeeds, cfeAborted if the copying was aborted or
    ///          stopped in the callback function and cfeError on any other error.
    /// @raises  EOSError if an error occurs and cfwRaiseException was passed </summary>
    class function CopyFileWithProgress(const _Source, _Dest: string;
      _Progress: TCopyFileProgressEvt;
      _Flags: TCopyFileWithProgressFlagSet): TCopyFileWithProgressResult; overload;

    class function CopyFileWithProgress(const _Source, _Dest: string;
      _Progress: TCopyFileProgressEvt;
      _IfExists: TCopyFileFlagIfExists = cfeFailIfExists;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException;
      _Restartable: TCopyFileWithProgressRestartable = cfwrNotRestartable): TCopyFileWithProgressResult; overload;

    ///<summary>
    /// Copies all files that match the given Mask from SrcDir to DestDir and
    /// returns the number of files that were copied.
    /// If cfRaiseException is set in Flags, any error will raise an EOsError exception
    /// and the copying process will be aborted, otherwise errors will be silently
    /// ignored.
    /// If a destination file exists depending on the other Flag values the following
    /// happens:
    /// * If cfFailIfExists is set, the file is skipped or an exception is raised
    /// * If cfFailIfExists is not set, the file will be overwritten. If that fails
    ///   the file is skipped or an exception is raised
    /// * If cfFailIfExists is not set and cfForceOverwrite is set, the function
    ///   will also try to overwrite readonly files.
    /// if FilesSkipped is given, all skipped files will be added to that list (may be nil) </summary>
    class function CopyMatchingFiles(const _Mask, _SrcDir, _DestDir: string; _Flags: TCopyFileFlagSet;
      _FilesSkipped: TStrings = nil): Integer;

    ///<summary>
    /// Creates an emtpy (0-byte) file with the given file name </summary>
    class procedure CreateEmptyFile(const _fn: string);
    /// <summary>
    /// Copies the file Source to Dest using the Windows MoveFileWithProgress function which
    /// allows for a progress callback
    /// NOTE: If the file can be moved rather than copied, no call to the callback
    ///       function will occur!
    /// @param Source is a string containing the name of the existing file
    /// @param Dest is a string containing the destination file name
    /// @param Flags is a set of TCopyFileWithProgressFlags specifying whether the copy operation
    ///        cfwFailIfExists: fail if the destination file already exists.
    ///        cfwRestartable: stores information in the destination file that allows
    ///          to restart a stopped copy operation
    ///        cfwRaiseException: retrieve the Windows error and raise an exception if it fails.
    ///          If not set, it will not raise an exception but just return cfwAborted
    ///          or cfwError if copying the file fails. (set by default)
    /// @returns cfeOK, if the copying succeeds, cfeAborted if the copying was aborted or
    ///          stopped in the callback function and cfeError on any other error.
    /// @raises  EOSError if an error occurs and cfwRaiseException was passed </summary>
    class function MoveFileWithProgress(const _Source, _Dest: string; _Progress: TCopyFileProgressEvt;
      _Flags: TMoveFileWithProgressFlagSet = [mfwRaiseException]): TCopyFileWithProgressResult;

    /// <summary>
    /// Creates a directory (parent directories must already exist)
    /// NOTE: this function is deprecated, use the overloaded version!
    /// @param DirectoryName is the name for the new directory
    /// @param RaiseException determines whether an exception is raised on error, default = true
    /// @returns true, if the directory was created
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function CreateDir(const _DirectoryName: string; _RaiseException: Boolean): Boolean; overload; deprecated;
    /// <summary>
    /// Creates a directory (parent directories must already exist)
    /// @param DirectoryName is the name for the new directory
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function retrieves
    ///                      the Windows error and raises an exception if it fails.
    ///                      If ehReturnFalse, it will not raise an exception
    ///                      but just return false if copying the file fails.
    /// @returns true, if the directory was created
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function CreateDir(const _DirectoryName: string;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean; overload;

    /// <summary>
    /// Creates a new directory, including the creation of parent directories as needed.
    /// NOTE: this function is deprecated, use the overloaded version!
    /// @param DirectoryPath is the name for the new directory
    /// @param RaiseException determines whether an exception is raised on error, default = true
    /// @returns true, if the directory was created
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function ForceDir_old(const _DirectoryPath: string; _RaiseException: Boolean): Boolean; deprecated;
    /// <summary>
    /// Creates a directory (parent directories must already exist)
    /// @param DirectoryName is the name for the new directory
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function retrieves
    ///                      the Windows error and raises an exception if it fails.
    ///                      If ehReturnFalse, it will not raise an exception
    ///                      but just return false if copying the file fails.
    /// @returns true, if the directory was created
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function ForceDir(const _DirectoryPath: string;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean;

    ///<summary> See also CopyMatchingFiles </summary>
    class function CopyDir_Old(const _SrcDir, _DestDir: string; _CreateIntermediate: Boolean;
      _RaiseException: Boolean): Boolean; deprecated;
    class function CopyDir(const _SrcDir, _DestDir: string;
      _CreateIntermediate: TCopyDirCreateIntermediate = cdciCreateIntermediate;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean;

    /// <summary>
    /// Sets a file's readonly flag
    /// @param Filename is the file to change
    /// @param Set determines whether to set or clear the flag
    /// @returns true, if the readonly flag has been changed
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function SetReadonly(const _Filename: string; _Value: Boolean;
      _RaiseException: Boolean): Boolean; overload; deprecated;
    /// <summary>
    /// Sets a file's readonly flag
    /// @param Filename is the file to change
    /// @param Set determines whether to set or clear the flag
    /// @param ErrorHandling is a TErrorHandlingEnum which controls whether the function retrieves
    ///                      the Windows error and raises an exception if it fails.
    ///                      If ehReturnFalse, it will not raise an exception
    ///                      but just return false if copying the file fails.
    /// @returns true, if the readonly flag has been changed
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function SetReadonly(const _Filename: string; _Value: Boolean;
      _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean; overload;

    /// <summary>
    /// Deletes the file using the SysUtils.DeleteFile function.
    /// @param Filename is a string containing the name of the file
    /// @param RaiseException is a boolean which controls whether the function
    ///        retrieves the Windows error and raises an exception
    ///        if it fails. If false, it will not raise an exception
    ///        but just return false if deleting the file fails.
    /// @param Force is a boolean which controls whether this function will try to delete
    ///        readonly files, If true, it will use SetFileAttr to reset the
    ///        readonly attribut and try to delete the file again.
    /// @returns true, if the file could be deleted, false otherwise.
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function DeleteFile(const _Filename: string; _RaiseException: Boolean = True;
      _Force: Boolean = False): Boolean;

    /// <summary>
    /// Deletes all files in a directory matching a given filemask (non-recursive)
    /// @param Dir is a string containting the directory in which the files are to be
    ///            deleted, must NOT be empty
    /// @param Mask is a string containting the file search mask, all files matching
    ///             this mask will be deleted
    /// @param RaiseException is a boolean which controls whether the function
    ///                       retrieves the Windows error and raises an exception
    ///                       if it fails. If false, it will not raise an exception
    ///                       but just return false if moving the file fails.
    /// @param Force is a boolean which controls whether this function will try to delete
    ///              readonly files, If true, it will use SetFileAttr to reset the
    ///              readonly attribut and try to delete the file again.
    /// @param ExceptMask is a string contaning a mask for files not to delete even if they
    ///                   match the Mask, defaults to an empty string meaning no exceptions.
    ///                   The comparison is case insensitive.
    /// @returns the number of files that could *not* be deleted.
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function DeleteMatchingFiles(const _Dir, _Mask: string;
      _RaiseException: Boolean = True; _Force: Boolean = False): Integer; overload;
    class function DeleteMatchingFiles(const _Dir, _Mask: string; const _ExceptMask: string = '';
      _RaiseException: Boolean = True; _Force: Boolean = False): Integer; overload; deprecated;
    class function DeleteMatchingFiles(const _Dir, _Mask: string; const _ExceptMasks: array of string;
      _RaiseException: Boolean = True; _Force: Boolean = False): Integer; overload;

    ///<summary>
    /// Gets a list of directories matching the given mask.
    /// @param Mask is the name mask to match, note that it can contain wildcards only in the
    ///             last part of the name (e.g. 'c:\grmpf*\trallala' will NOT work)
    /// @param sl is a TStrings to which all matching directories will be added (it will not be cleared)
    ///           it can be NIL if the caller only wants the count, not the actual list.
    /// @param IncludePath determines whether the list should contain only the directory names or
    ///                    the full paths.
    /// @returns the number of matching directories </summary>
    class function FindMatchingDirs(const _Mask: string; _sl: TStrings; _IncludePath: Boolean = False): Integer;

    ///<summary>
    /// Finds the first directory matching the mask.
    /// @param Mask is the name mask to match, note that it can contain wildcards only in the
    ///             last part of the name (e.g. 'c:\grmpf*\trallala' will NOT work)
    /// @param Dir is a String to which the matching directory will be written.
    /// @returns true, if a matching directory was found. </summary>
    class function FindMatchingDir(const _Mask: string; out _Dir: string): Boolean;

    ///<summary>
    /// Gets a list of files matching the given mask. Only regular files will be found, no
    /// directories, no hidden files, no readonly files (files with faArchive will be found).
    /// See TSimpleDirEnumerator if you need more flexibility.
    /// @param Mask is the name mask to match, note that it can contain wildcards only in the
    ///             last part of the name (e.g. 'c:\grmpf*\trallala' will NOT work)
    /// @param sl is a TStrings to which all matching files will be added (it will not be cleared)
    ///           it can be NIL if the caller only wants the count, not the actual list.
    /// @param IncludePath determines whether the list should contain only the file names or
    ///                    the full paths.
    /// @returns the number of matching files </summary>
    class function FindMatchingFiles(const _Mask: string; _sl: TStrings; _IncludePath: Boolean = False;
      _Sort: Boolean = True): Integer; overload;
    class function FindMatchingFiles(const _Mask: string;
      _IncludePath: Boolean = False; _Sort: Boolean = True): TStringArray; overload;

    /// <summary>
    /// tries to find a matching file
    /// @param Mask is the filename mask to match
    /// @param Filename is the name of the file which has been found, only valid if result <> mfNotFound
    /// @returns mfNotFound, if no file was found, or mfDirectory, mfFile or mfSpecial
    ///          describing the type of the file which has been found.
    /// @NOTE: If there are multiple matches, the file name returned is not deterministic.
    ///        On an NTFS volume it is the last one in the NTFS sort order but that's not guaranteed. </summary>
    class function FindMatchingFile(const _Mask: string; out _Filename: string): TMatchingFileResult; overload;
    class function FindMatchingFile(const _Mask: string): TMatchingFileResult; overload;

    ///<summary>
    /// Returns true, if the given file exists. Note that wildcards are not supported! If you
    /// need wildcards, use FindMatchingFile.
    /// @param RaiseException determines whether an exception should be raised if the file does not exist
    /// @raises EFileNotFound if the file does not exist and RaiseException is true </summary>
    class function FileExists(const _Filename: string): Boolean; overload;
    class function FileExists(const _Filename: string; _RaiseException: Boolean): Boolean; overload; deprecated; // use AssertFileExists instead
    ///<summary>
    /// Checks if the given file exists. Note that wildcards are not supported! If you
    /// need wildcards, use AssertMatchingFileExists.
    /// @raises EFileNotFound if the file does not exist. </summary>
    class procedure AssertFileExists(const _Filename: string);
    class procedure AssertMatchingFileExists(const _Mask: string);

    ///<summary>
    /// @param RaiseException determines whether an exception should be raised if the directory does not exist
    /// @raises EDirNotFound if the directory does not exist and RaiseException is true </summary>
    class function DirExists(const _DirName: string): Boolean; overload;
    class function DirExists(const _DirName: string; _RaiseException: Boolean): Boolean; overload; deprecated; // use AssertDirExists instead
    ///<summary>
    /// Checks if the given directory exists.
    /// @raises EDirNotFound if the directory does not exist. </summary>
    class procedure AssertDirExists(const _DirName: string);

    ///<summary>
    /// @param DirNames is a TStrings object containing a list of directory names to check.
    /// @returns true, if all directories in the list exist, false otherwise
    /// @NOTE: Passing an empty list will return True. </summary>
    class function AllDirsExist(_DirNames: TStrings): Boolean;

    /// <summary>
    /// Deletes an empty directory using the SysUtils function RemoveDir
    /// The function will fail if the directory is not empty.
    /// @param DirName is the name of the directory to delete
    /// @param RaiseException is a boolean which controls whether the function
    ///                       retrieves the Windows error and raises an exception
    ///                       if it fails. If false, it will not raise an exception
    ///                       but just return false if deleting the directory fails.
    /// @param Force is a boolean which controls whether this function will try to delete
    ///              readonly directories, If true, it will use SetFileAttr to reset the
    ///              readonly attribut and try to delete the directory again.
    /// @returns True, if the directory could be deleted, false otherwise.
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function RemoveDir(const _DirName: string; _RaiseException: Boolean = True;
      _Force: Boolean = False): Boolean;

    /// <summary>
    /// function is deprecated, use DelDirTree instead!
    /// Note the different order of parameters of the new function! </summary>
    class function DelTree(const _DirName: string; _Force: Boolean = False;
      _RaiseException: Boolean = True): Boolean; deprecated;
    /// <summary>
    /// Deletes a directory with all files and subdirectories.
    /// Note: This new function has a different order of parameters than
    ///       the old DelTree function.
    /// @param DirName is the name of the directory to delete
    /// @param RaiseExceptin is a boolean which controls whether the function
    ///                      retrieves the Windows error and raises an exception
    ///                      if it fails. If false, it will not raise an exception
    ///                      but just return false if deleting the directory fails.
    /// @param Force specifies whether it should also delete readonly files
    /// @returns True, if the directory could be deleted, false otherwise.
    /// @raises EOSError if there was an error and RaiseException was true </summary>
    class function DelDirTree(const _DirName: string; _RaiseException: Boolean = True;
      _Force: Boolean = False): Boolean;

    /// <summary>
    /// reads a text file and returns its content as a string
    /// @param Filename is the name of the file to read
    /// @returns the file's content as a string </summary>
    class function ReadTextFile(const _Filename: string): string;

    ///<summary>
    /// Expands a relative FileName relative to the given BaseDir. </summary>
    class function ExpandFileNameRelBaseDir(const _Filename, _BaseDir: string): string;

    ///<summary>
    /// checks if a filename is relative (meaning: Not an absolute path)
    /// (This calls shlwapi.PathIsRelativeA/W.)
    /// @returns true, if the filename is relative </summary>
    class function PathIsRelative(const _Filename: string): Boolean;

    /// <summary>
    /// checks whether the given string is a valid filename (without path), that is
    /// does not contain one of the characters defined in INVALID_FILENAME_CHARS
    /// @param s is the string to check
    /// @param AllowDot determines whether a dot ('.') is allowed in the filename
    ///        the default is true, but you might not want that
    /// @param AllowPathChars determines whether the name may contain '\' characters and a single
    ///                       ':' as the second character, so a full path can be checked.
    /// @returns true, if the string is a valid filename, false otherwise </summary>
    class function IsValidFilename(const _s: string; _AllowDot: Boolean = True;
      _AllowPathChars: Boolean = False): Boolean; overload;
    /// <summary>
    /// checks whether the given string is a valid filename (without path), that is
    /// does not contain one of the characters defined in INVALID_FILENAME_CHARS and
    /// returns the first error position.
    /// @param s is the string to check
    /// @param ErrPos is the first error position, only valid it result = false
    /// @param AllowDot determines whether a dot ('.') is allowed in the filename
    ///        the default is true, but you might not want that
    /// @param AllowPathChars determines whether the name may contain '\' characters and a single
    ///                       ':' as the second character, so a full path can be checked.
    /// @returns true, if the string is a valid filename, false otherwise </summary>
    class function IsValidFilename(const _s: string; out _ErrPos: Integer; _AllowDot: Boolean = True;
      _AllowPathChars: Boolean = False): Boolean; overload;
    ///<summary>
    /// Replaces all invalid characters in the file name with the given character </summary>
    /// @param S is the input filename
    /// @param ReplaceChar is the character to use for replacing in valid characters
    ///                    defaults to '_'
    /// @param AllowPathChars determines whether the name may contain '\' characters and a single
    ///                       ':' as the second character, so a full path can be converted.
    /// @returns a valid filename </summary>
    class function MakeValidFilename(const _s: string; _ReplaceChar: Char = '_'; _AllowPathChars: Boolean = True): string;

    class function ContainsWildcard(const _Mask: string): Boolean;

    /// <summary>
    /// see also IsFileWritable
    /// @Returns true if the file exists and its readonly flag is set </summary>
    class function IsFileReadonly(const _Filename: string): Boolean;
    /// <summary>
    /// @Returns true if the file exists and is writable </summary>
    class function IsFileWritable(const _Filename: string): Boolean;

{$IFDEF SUPPORTS_ENHANCED_RECORDS}
    /// <summary>
    /// creates a backup of the file appending the current date and time to the base
    /// file name. If the copy operation fails, an underscore and a number will be appended
    /// to the filename. That number will be incremented, until the copy operation succeeds
    /// or it reaches 999.
    /// The original file will remain unchanged.
    /// See also TFileGenerationHandler and GenerateBackupFilename.
    /// @param Filename is the name of the file to back up
    /// @param BackupDir is a directory in which to create the backup file, if empty
    ///                  the same directory as the original file is used
    /// @returns the full filename of the created backup file
    /// @raises EBackupFailed if the copy operation failed even for the 1000th attempt. </summary>
    class function BackupFile(const _Filename: string; const _BackupDir: string = ''): string;
    /// <summary>
    /// Moves the file to a new, unique name, appending the current date and time to the base
    /// file name. If the move operation fails, an underscore and a number will be appended
    /// to the filename. That number will be incremented, until the copy operation succeeds
    /// or it reaches 999.
    /// See also TFileGenerationHandler and GenerateBackupFilename.
    /// @param Filename is the name of the file to move
    /// @param BackupDir is a directory in which to create the backup file, if empty
    ///                  the same directory as the original file is used
    /// @returns the full filename the file has been moved to.
    /// @raises EBackupFailed if the copy operation failed even for the 1000th attempt. </summary>
    class function MoveFileToBackup(const _Filename: string; const _BackupDir: string = ''): string;
{$ENDIF}
    ///<summary>
    /// Generates a backup of the filename by appending the current date and time to the base
    /// @param Filename is the name of the file to back up
    /// @param BackupDir is a directory in which to create the backup file, if empty
    ///                  the same directory as the original file is used
    /// @returns the full filename for the backup file </summary>
    class function GenerateBackupFilename(const _Filename: string; _BackupDir: string = ''): string;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    ///<summary>
    /// Uses kernel32.GetFullPathName and then compares the result using SameText.
    /// Note that this does not handle all possible cases, e.g. two files with totally different
    /// names could be the same due to hard links, soft links or multiple UNC paths pointing to
    // the same directory. </summary>
    class function IsSameFilename(const _fn1, _fn2: string): Boolean;

    ///<summary>
    /// Appends date and optionally time to the base filename using the format _YYYY-MM-DD_HH-MM-SS
    /// e.g. c:\test.txt -> c:\test_1966-05-05.txt or c:\test_1966-05-05_08-15-00.txt
    /// @param Filename is the filename to append to
    /// @param DateTime is a TDateTime value to append
    /// @param IncludeTime determines whether to append the date only or date and time
    /// see also AppendDate and AppendDateAndTime </summary>
    class function AppendDateTime(const _Filename: string; _DateTime: TDateTime; _IncludeTime: Boolean): string; overload;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    ///<summary>
    /// Appends the current date to the filename in the format _YYYY-MM-DD </summary>
    class function AppendDate(const _Filename: string): string; overload;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    ///<summary>
    /// Appends the given date to the filename in the format _YYYY-MM-DD </summary>
    class function AppendDate(const _Filename: string; _Date: TDateTime): string; overload;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    ///<summary>
    /// Appends the current date and time to the filename in the format _YYYY-MM-DD_HH-MM-SS </summary>
    class function AppendDateAndTime(const _Filename: string): string; overload;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    ///<summary>
    /// Appends the given date and time to the filename in the format _YYYY-MM-DD_HH-MM-SS </summary>
    class function AppendDateAndTime(const _Filename: string; _DateTime: TDateTime): string; overload;
{$IFDEF SUPPORTS_STATIC}
    static;
{$ENDIF}

    /// <summary>
    /// @returns a TFileInfoRec containing the filename, filesize and last access
    ///          timestamp of the file </summary>
    class function GetFileInfo(const _Filename: string): TFileInfoRec;
    /// <summary>
    /// Tries to get the file information containing filename, filesize
    /// and last access timestamp of the file.
    /// @param Info will contain these values, only valid if result = true </summary>
    class function TryGetFileInfo(const _Filename: string; out _Info: TFileInfoRec): Boolean;
    class function TryGetFileSize(const _Filename: string; out _Size: Int64): Boolean;
    class function GetFileSize(const _Filename: string): Int64;

    /// <summary>
    /// Returns the free space (in bytes) on the disk with the given drive letter </summary>
    class function DiskFree(_DriveLetter: AnsiChar): Int64; overload;
    /// <summary>
    /// Returns the free space (in bytes) on the disk on which the given directory is located </summary>
    class function DiskFree(const _Directory: string): Int64; overload;
    class function GetVolumeName(_DriveLetter: Char): string;
    class function GetRemoteVolumeName(const _Share: string): string;
    class procedure GetLocalVolumeNames(_sl: TStrings; _HdOnly: Boolean = False; _IgnoreEmpty: Boolean = True);

    ///<summary>
    /// changes the "full" file extension where "full" means it handles multiple
    /// extensions like .doc.exe </summary>
    class function ChangeFileExtFull(const _Filename, _NewExt: string): string;

    ///<summary>
    /// Like SysUtils.ChangeFileExt, only changes the part after the last '.' in the
    /// filename. </summary>
    class function ChangeFileExtLast(const _Filename, _NewExt: string): string;

    ///<summary>
    /// extracts the "full" file extension where "full" means it handles multiple
    /// extensions like .doc.exe
    /// @param IncludeDot determines whether to include the leading dot </summary>
    class function ExtractFileExtFull(const _Filename: string; _IncludeDot: Boolean = True): string;

    ///<summary>
    /// Like SysUtils.ExtractFileExt, only changes the part after the last '.' in the
    /// filename.
    /// @param IncludeDot determines whether to include the leading dot </summary>
    class function ExtractFileExtLast(const _Filename: string; _IncludeDot: Boolean = True): string;

    ///<summary>
    /// removes the "full" file extension where "full" means it handles multiple
    /// extensions like .doc.exe </summary>
    class function RemoveFileExtFull(const _Filename: string): string;

    ///<summary>
    /// Short for ChangeFileExtLast(_Filename, '') </summary>
    class function RemoveFileExtLast(const _Filename: string): string;

    ///<summary>
    /// @param Ext is the full file extension to check for, including the leading dot.
    /// @returns True, if the full extension of Filename matches the given extension.
    ///                Comparison is case insensitive
    ///          False if the extensions don't machch. </summary>
    class function HasFileExtFull(const _Filename: string; const _Ext: string): Boolean;
    ///<summary>
    /// @param Ext is the last file extension to check for, including the leading dot.
    /// @returns True, if the last extension of Filename matches the given extension.
    ///                Comparison is case insensitive
    ///          False if the extensions don't machch. </summary>
    class function HasFileExtLast(const _Filename: string; const _Ext: string): Boolean;

    ///<summary>
    /// Sets the file's date and time to the given time or to the current time
    /// @param Fileanme is the name of the file to touch
    /// @param lpSystemTime is a pointer to a TSystemTime record givng the time,
    ///                     Defaults to NIL which uses the current system time.
    /// @raises EFileNotFound if the file does not exist
    /// @raises EOsError if there calling the Windows API fails </summary>
    class procedure TouchFileTimes(const _Filename: string; lpSystemTime: PSystemTime = nil);
  end;

{$IFDEF SUPPORTS_ENHANCED_RECORDS}
type
  TFilename = record
  private
    FFull: string;
  public
    procedure Init(const _Full: string);
    property Full: string read FFull;
    ///<summary>
    /// Returns the drive of the file name.
    /// For file names with drive letters, the result is in the form "<drive>:".
    /// For file names with a UNC path the result is in the form "'\\<servername>\<sharename>'".
    // If the path contains neither style of path prefix, the result is an empty string. </summary>
    function Drive: string;
    ///<summary> Returns the directory excluding the last backslash, unless the path is the
    ///          root directory of a drive. </summary>
    function Directory: string;
    ///<summary> Returns the directory including the last backslash. </summary>
    function DirectoryBS: string;
    ///<summary> Returns the filename without directory and without extension(s).
    ///          Note that this treats everything after the first '.' as extension(s). </summary>
    function FilenameOnly: string;
    ///<summary> Returns the filename + extension(s) </summary>
    function Filename: string;
    ///<summary>
    /// returns an array of extensions, all including the leading '.' </summary>
    function GetExtensions: TStringArray;
    ///<symmary> Returns the extension(s) only.
    ///          Note that this treats everything after the first '.' as extension(s). </summary>
    function Extension: string;
    ///<symmary> Returns the (last) extension only.
    ///          Note that this treats only the text after the last '.' as extension(s). </summary>
    function LastExtension: string;
    ///<summary>
    /// Returns the filename without extension.
    /// Note that this treats everything after the first '.' as extension(s) </summary>
    function BaseName: string;
    ///<summary>
    /// replaces the drive part of the path with the given NewDrive. </summary>
    procedure ReplaceDrive(const _NewDrive: string);
    ///<summary>
    /// Replaces the directory part (including the drive) with the given NewDir </summary>
    procedure ReplaceDirectory(const _NewDir: string);
    ///<summary>
    /// Replaces filename and extension(s) with the given filename </summary>
    procedure ReplaceFilename(const _Filename: string);
    ///<summary>
    /// Replaces filename, but not extension(s) with the given filename </summary>
    procedure ReplaceFilenameOnly(const _FilenameOnly: string);
    ///<summary>
    /// Replaces the full extension with the given one.
    /// @param Extension is the new extension including the leading dot e.g. '.txt' </summary>
    procedure ReplaceExtension(const _Extension: string);
    ///<summary>
    /// Replaces the whole extension with the given array of extensions
    /// Example: ['.bla', '.blub', '.tmp'] will change the extension to '.bla.blub.tmp' </summary>
    procedure ReplaceExtensions(const _Extensions: TStringArray);
    ///<summary>
    /// Replaces the last extension of the filename with the given one
    /// @param Extension is the new extension including the leading dot e.g. '.txt'
    /// Example:
    /// Given the file name 'c:\bla\file.bla.txt'
    /// ReplaceLastExtension('.doc') will change the file name to 'c:\bla\file.bla.doc' </summary>
    procedure ReplaceLastExtension(const _Extension: string);
    ///<summary>
    /// @returns true, if the filename contains either a drive letter or is a UNC, false otherwise
    /// @param Root either contains '<drive>:\' or '\\<server>\<share>' if the result is true. </summary>
    function TryGetRootDir(out _Root: string): Boolean;
    ///<summary>
    /// @Returns the first Depth parts of the filename as separated by PathDelim characters, without
    ///          the trailing path delimiter.
    /// example: Full = 'c:\test\trallala'
    ///          Parts(>=3) = 'c:\test\trallala'
    ///          Parts(2) = 'c:\test'
    ///          Parts(1) = 'c:'
    ///          Parts(0) = ''
    ///          Parts(-1) = 'c:\test'
    ///          Parts(-2) = 'c:'
    ///          Parts(<=-3) = ''
    /// Note that every call will parse the filename again, so you should buffer this
    /// value if you need it multiple times. If you need all parts, consider using Split </summary>
    function Parts(_Depth: Integer): string;
    ///<summary>
    /// Returns the number of parts separated by PathDelim characters.
    /// Note that every call will calculate Depth again, so you should buffer this
    /// value if you need it multpile times. </summary>
    function Depth: Integer;
    ///<summary>
    /// Combines the functionality of Parts and Depth by returning all Parts in sl and the Depth
    /// as function result </summary>
    function GetParts(_sl: TStrings): Integer; deprecated; // use Split instead
    ///<summary>
    /// Splits all parts of the file name in a TStringArray
    /// @returns the number of parts </summary>
    function Split: TStringArray;
    class function Combine(_Parts: TStringArray): TFilename; static;
    ///<summary>
    /// Same as Init </summary>
    class operator Implicit(const _s: string): TFilename;
    ///<summary>
    /// Same as Full </summary>
    class operator Implicit(_a: TFilename): string;
    class operator Add(const _a: TFilename; const _b: string): string;
    class operator Add(const _a: string; const _b: TFilename): string;
  end;

type
  // TFilename might not be this record, because there are multiple declarations of this type
  // in the RTL and other libraries, so it depends on the units included and their order which
  // one is used. TdzFilename will always be this record.
  TdzFilename = TFilename;

type
  TSearchPath = record
  private
    FValue: string;
    function AsStringlist: TStringList;
  public
    function Value: string;
    ///<summary>
    /// searches the file in the path
    /// @param FilenameOnly is the file name without a directory
    /// @param is the full file name if the file, only valid it result = true
    /// @returns true, if the file has been found, false otherwise </summary>
    function TryFindFile(const _FilenameOnly: string; out _FoundFile: TFilename): Boolean;
    ///<summary>
    /// Calls TryFindFile and raises EFileNotFound if that call returns false </summary>
    function FindFile(const _FilenameOnly: string): TFilename;
    function AnyDirExists: Boolean;
    function AllDirsExist(out _FirstNonExistingDir: string): Boolean;
    procedure Init(const _s: string);
    function PartCount: Integer;
    function Part(_Idx: Integer): string;
    procedure GetParts(_Parts: TStrings); overload;
    function GetParts: TStringArray; overload;
    procedure AssignParts(_Parts: TStrings);
    class operator Implicit(_sl: TStrings): TSearchPath;
    class operator Implicit(const _s: string): TSearchPath;
    class operator Implicit(_a: TSearchPath): string;
    class operator Equal(_a, _b: TSearchPath): Boolean;
    class operator NotEqual(_a, _b: TSearchPath): Boolean;
    class operator Add(_a, _b: TSearchPath): TSearchPath;
    class operator Add(_a: TSearchPath; const _b: string): TSearchPath;
  end;
{$ENDIF}

type
  /// <summary>
  /// callback event for generating a filename for the given generation </summary>
  TOnGenerateFilename = procedure(_Sender: TObject; _Generation: Integer; var _Filename: string) of object;

type
  /// <summary>
  /// This class handles keeping generations of files, e.g. log files. The default
  /// is to keep 10 generations </summary>
  TFileGenerationHandler = class
  private
    FBaseName: string;
    FSuffix: string;
    FOnGenerateFilename: TOnGenerateFilename;
    FMaxGenerations: Integer;
    FResultContainsNumber: Boolean;
    FOldestIsHighest: Boolean;
    FPrependZeros: Integer;
    function GenerateFilename(_Generation: Integer): string;
  public
    /// <summary>
    /// @param BaseName is the base filename to which by default _<n> followed by
    ///                 the Suffix will be appended
    /// @param Suffix is the suffix for the filename, usually an extension which
    ///               must include the dot (.), but it is also possible to pass
    ///               an arbitrary string like '_backup'. </summary>
    constructor Create(const _BaseName, _Suffix: string);
    /// <summary>
    /// generates the filename and returns it </summary>
    function Execute(_KeepOriginal: Boolean): string;
    /// <summary>
    /// the maximum of file generations that should be kept </summary>
    property MaxGenerations: Integer read FMaxGenerations write FMaxGenerations default 5;
    /// <summary>
    /// should the resulting filename contain a number? </summary>
    property ResultContainsNumber: Boolean read FResultContainsNumber write FResultContainsNumber default False;
    /// <summary>
    /// does the oldest file have the highest number? </summary>
    property OldestIsHighest: Boolean read FOldestIsHighest write FOldestIsHighest default True;
    property PrependZeros: Integer read FPrependZeros write FPrependZeros default 0;
    /// <summary>
    /// allows read access to the file's base name as passed to the constructor </summary>
    property BaseName: string read FBaseName;
    property Suffix: string read FSuffix;
    /// <summary>
    /// callback event for generating a filename for the given generation </summary>
    property OnGenerateFilename: TOnGenerateFilename read FOnGenerateFilename write FOnGenerateFilename;
  end;

/// <summary>
/// This is an abbreviation for IncludeTrailingPathDelimiter </summary>
function itpd(const _DirName: string): string;

///<summary>
/// This is an abbreviation for ExcludeTrailingPathDelimiter </summary>
function etpd(const _DirName: string): string;

///<summary>
/// Assign a filename and open the file.
/// if Rewrite fails, File is zeroed
/// @raise exception, if  rewrite fails </summary>
procedure TextFile_AssignAndRewrite(var _File: TextFile; const _Filename: string);
function TextFile_IsOpen(const _File: TextFile): Boolean;
procedure TextFile_Close(var _File: TextFile);

implementation

uses
  StrUtils,
  Masks,
  u_dzMiscUtils,
  u_dzStringUtils,
  u_dzDateUtils,
  u_dzStringArrayUtils;

function _(const _s: string): string;
{$IFDEF SUPPORTS_INLINE}
inline;
{$ENDIF}
begin
  Result := dzDGetText(_s, 'dzlib');
end;

function itpd(const _DirName: string): string;
{$IFDEF SUPPORTS_INLINE}
inline;
{$ENDIF}
begin
  Result := IncludeTrailingPathDelimiter(_DirName);
end;

function etpd(const _DirName: string): string;
{$IFDEF SUPPORTS_INLINE}
inline;
{$ENDIF}
begin
  Result := ExcludeTrailingPathDelimiter(_DirName);
end;

{ TSimpleDirEnumerator }

constructor TSimpleDirEnumerator.Create(const _Mask: string;
  _MayHaveAttr: TFileAttributeSet = ALL_FILES_ATTRIB_SET);
begin
  FMask := _Mask;
  FMustHaveAttr := [];
  FMayHaveAttr := _MayHaveAttr;
end;

constructor TSimpleDirEnumerator.CreateForDirsOnly(const _Mask: string);
begin
  Create(_Mask, [dfaDirectory, dfaArchive]);
  FMustHaveAttr := [dfaDirectory];
end;

constructor TSimpleDirEnumerator.CreateForFilesOnly(const _Mask: string);
begin
  Create(_Mask, [dfaArchive]);
end;

destructor TSimpleDirEnumerator.Destroy;
begin
  Reset;
  inherited;
end;

class function TSimpleDirEnumerator.EnumDirsOnly(const _Mask: string; _List: TStrings;
  _IncludePath, _Sort: Boolean): Integer;
var
  enum: TSimpleDirEnumerator;
  List: TStringList;
begin
  enum := TSimpleDirEnumerator.CreateForDirsOnly(_Mask);
  try
    List := TStringList.Create;
    try
      Result := enum.FindAll(List, _IncludePath);
      if _Sort then
        List.Sort;
      _List.AddStrings(List);
    finally
      FreeAndNil(List);
    end;
  finally
    FreeAndNil(enum);
  end;
end;

class function TSimpleDirEnumerator.EnumDirsOnly(const _Mask: string; _IncludePath, _Sort: Boolean): TStringArray;
var
  dirs: TStringList;
begin
  dirs := TStringList.Create;
  try
    EnumDirsOnly(_Mask, dirs, _IncludePath, _Sort);
    Result := TStringArray_FromStrings(dirs);
  finally
    FreeAndNil(dirs);
  end;
end;

class procedure TSimpleDirEnumerator.EnumFilesOnly(const _Mask: string; _Callback: TOnFileEnumCallback;
  _IncludePath: Boolean = False; _Sort: Boolean = True);
var
  enum: TSimpleDirEnumerator;
  List: TStringList;
  i: Integer;
begin
  enum := TSimpleDirEnumerator.CreateForFilesOnly(_Mask);
  try
    List := TStringList.Create;
    try
      enum.FindAll(List, _IncludePath);
      if _Sort then
        List.Sort;
      for i := 0 to List.Count - 1 do
        _Callback(enum, List[i]);
    finally
      FreeAndNil(List);
    end;
  finally
    FreeAndNil(enum);
  end;
end;

class function TSimpleDirEnumerator.EnumFilesOnly(const _Mask: string; _IncludePath,
  _Sort: Boolean): TStringArray;
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    EnumFilesOnly(_Mask, sl, _IncludePath, _Sort);
    Result := TStringArray_FromStrings(sl);
  finally
    FreeAndNil(sl);
  end;
end;

class function TSimpleDirEnumerator.EnumFilesOnly(const _Mask: string; _List: TStrings;
  _IncludePath, _Sort: Boolean): Integer;
begin
  Result := Execute(_Mask, _List, [dfaArchive], _IncludePath, _Sort);
end;

class function TSimpleDirEnumerator.Execute(const _Mask: string; _List: TStrings;
  _MayHaveAttr: TFileAttributeSet = ALL_FILES_ATTRIB_SET;
  _IncludePath: Boolean = False; _Sort: Boolean = True): Integer;
var
  enum: TSimpleDirEnumerator;
  List: TStringList;
begin
  enum := TSimpleDirEnumerator.Create(_Mask, _MayHaveAttr);
  try
    List := TStringList.Create;
    try
      Result := enum.FindAll(List, _IncludePath);
      if Assigned(_List) then begin
        if _Sort then
          List.Sort;
        _List.AddStrings(List);
      end;
    finally
      FreeAndNil(List);
    end;
  finally
    FreeAndNil(enum);
  end;
end;

function TSimpleDirEnumerator.FindAll(_List: TStrings = nil; _IncludePath: Boolean = False): Integer;
var
  s: string;
  Path: string;
begin
  if _IncludePath then
    Path := ExtractFilePath(FMask)
  else
    Path := '';
  Result := 0;
  while FindNext(s) do begin
    Inc(Result);
    if Assigned(_List) then
      _List.Add(Path + s);
  end;
end;

function TSimpleDirEnumerator.FindNext(out _Filename: string; _IncludePath: Boolean = False): Boolean;
var
  Res: Integer;
  Attr: Integer;

  function AttrOk(_EnumAttr: TFileAttributes; _SysAttr: Integer): Boolean;
  begin
    Result := True;
    if _EnumAttr in FMustHaveAttr then
      if (Attr and _SysAttr) = 0 then
        Result := False;
  end;

  procedure CondAddAttr(_EnumAttr: TFileAttributes; _SysAttr: Integer);
  begin
    if _EnumAttr in FMayHaveAttr then
      Attr := Attr + _SysAttr;
  end;

var
  Path: string;
begin
  Path := ExtractFilePath(FMask);
  repeat
    if not FActive then begin
      FMatchCount := 0;
      Attr := 0;
      CondAddAttr(dfaReadonly, SysUtils.faReadOnly);
      CondAddAttr(dfaHidden, SysUtils.faHidden);
      CondAddAttr(dfaSysFile, SysUtils.faSysFile);
      CondAddAttr(dfaDirectory, SysUtils.faDirectory);
      CondAddAttr(dfaArchive, SysUtils.faArchive);
      Res := FindFirst(FMask, Attr, FSr);
      Result := (Res = 0);
      if Result then
        FActive := True;
    end else begin
      Res := SysUtils.FindNext(FSr);
      Result := (Res = 0);
    end;
    if not Result then
      Exit;
    if (Sr.Name = '.') or (Sr.Name = '..') then
      Continue;
    if FMustHaveAttr <> [] then begin
      Attr := FSr.Attr;
      if not AttrOk(dfaReadonly, SysUtils.faReadOnly) then
        Continue;
      if not AttrOk(dfaHidden, SysUtils.faHidden) then
        Continue;
      if not AttrOk(dfaSysFile, SysUtils.faSysFile) then
        Continue;
      if not AttrOk(dfaDirectory, SysUtils.faDirectory) then
        Continue;
      if not AttrOk(dfaArchive, SysUtils.faArchive) then
        Continue;
    end;
    Inc(FMatchCount);
    if _IncludePath then
      _Filename := Path + Sr.Name
    else
      _Filename := Sr.Name;
    Exit;
  until False;
end;

function TSimpleDirEnumerator.FindNext: Boolean;
var
  s: string;
begin
  Result := FindNext(s);
end;

procedure TSimpleDirEnumerator.Reset;
begin
  if FActive then
    FindClose(FSr);
  FActive := False;
end;

{ TFileSystem }

class function TFileSystem.GetTempPath: string;
var
  Res: Integer;
  LastError: Integer;
begin
  // according to MSDN the maximum length of the TEMP path is MAX_PATH+1 (261)
  // an AnsiString / WideString always has one additional character to the length for storing
  // the terminating 0
  SetLength(Result, MAX_PATH);
  Res := Windows.GetTempPath(MAX_PATH + 1, PChar(Result));
  if Res <= 0 then begin
    // GetLastError must be called before _(), otherwise the error code gets lost
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempPath: %1:s (code: %0:d) calling Windows.GetTempPath'));
  end;
  SetLength(Result, Res);
end;

// declared wrongly in WINDOWS

function GetVolumeInformation(lpRootPathName: PChar;
  lpVolumeNameBuffer: PChar; nVolumeNameSize: DWORD; lpVolumeSerialNumber: PDWORD;
  lpMaximumComponentLength, lpFileSystemFlags: LPDWORD;
  lpFileSystemNameBuffer: PChar; nFileSystemNameSize: DWORD): BOOL; stdcall; external kernel32 Name 'GetVolumeInformationA';

class function TFileSystem.GetVolumeName(_DriveLetter: Char): string;
begin
  Result := GetRemoteVolumeName(_DriveLetter + ':\');
end;

class function TFileSystem.HasFileExtFull(const _Filename, _Ext: string): Boolean;
begin
  Result := SameText(_Ext, ExtractFileExtFull(_Filename));
end;

class function TFileSystem.HasFileExtLast(const _Filename, _Ext: string): Boolean;
begin
  Result := SameText(_Ext, ExtractFileExtLast(_Filename));
end;

class function TFileSystem.GetRemoteVolumeName(const _Share: string): string;
var
  Res: LongBool;
begin
  SetLength(Result, MAX_PATH + 1);
  Res := GetVolumeInformation(PChar(itpd(_Share)), PChar(Result), Length(Result), nil, nil, nil, nil, 0);
  if Res then begin
    Result := PChar(Result);
  end else
    Result := '';
end;

class procedure TFileSystem.GetLocalVolumeNames(_sl: TStrings; _HdOnly: Boolean = False; _IgnoreEmpty: Boolean = True);
type
  TDriveType = (dtUnknown, dtNoDrive, dtFloppy, dtFixed, dtNetwork, dtCDROM, dtRAM);
var
  DriveBits: set of 0..25;
  DriveNum: Integer;
  DriveChar: Char;
  DriveType: TDriveType;
  s: string;
begin
  Integer(DriveBits) := Windows.GetLogicalDrives;
  for DriveNum := 0 to 25 do begin
    if not (DriveNum in DriveBits) then
      Continue;
    DriveChar := Char(DriveNum + Ord('a'));
    DriveType := TDriveType(Windows.GetDriveType(PChar(DriveChar + ':\')));
    if not _HdOnly or (DriveType = dtFixed) then begin
      s := GetVolumeName(DriveChar);
      if s <> '' then begin
        _sl.AddObject(s, Pointer(DriveNum)); //FI:W541 Casting from Integer to Pointer type (or vice versa)
      end else begin
        if not _IgnoreEmpty then begin
          s := _('<no volume name>');
          _sl.AddObject(s, Pointer(DriveNum)); //FI:W541 Casting from Integer to Pointer type (or vice versa)
        end;
      end;
    end;
  end;
end;

function re2ehe(_RaiseException: Boolean): TErrorHandlingEnum;
begin
  if _RaiseException then
    Result := ehRaiseException
  else
    Result := ehReturnFalse;
end;
{$IFNDEF DELPHI2005_UP}
// Delphi 6 and 7 do not understand that this calls the non deprecated overload
// I can't be bothered to add lots of ifdefs, so I turn this warning off for the rest of the unit.
{$WARN SYMBOL_DEPRECATED OFF}
{$ENDIF}

class function TFileSystem.CreateDir(const _DirectoryName: string; _RaiseException: Boolean): Boolean;
begin
  Result := Self.CreateDir(_DirectoryName, re2ehe(_RaiseException));
end;

class function TFileSystem.CreateDir(const _DirectoryName: string;
  _ErrorHandling: TErrorHandlingEnum): Boolean;
var
  LastError: Cardinal;
begin
  Result := SysUtils.CreateDir(_DirectoryName);
  if not Result then begin
    if _ErrorHandling = ehRaiseException then begin
      // GetLastError must be called before _(), otherwise the error code gets lost
      LastError := GetLastError;
      // duplicate % so they get passed through the format function
      RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) creating directory "%s"'), [_DirectoryName]));
    end;
  end;
end;

class procedure TFileSystem.CreateEmptyFile(const _fn: string);
var
  Handle: Integer;
begin
  Handle := SysUtils.FileCreate(_fn);
  SysUtils.FileClose(Handle);
end;

class function TFileSystem.CreateUniqueDirectory(_BaseDir: string = ''; const _Prefix: string = 'dz'): string;
var
  Pid: DWORD;
  Counter: Integer;
  Ok: Boolean;
  s: string;
begin
  Result := '';
  if _BaseDir = '' then
    _BaseDir := GetTempPath;
  Pid := GetCurrentProcessId;
  s := itpd(_BaseDir) + _Prefix + '_' + IntToStr(Pid) + '_';
  Counter := 0;
  Ok := False;
  while not Ok do begin
    Result := s + IntToStr(Counter);
    Ok := Self.CreateDir(Result, ehReturnFalse);
    if not Ok then begin
      Inc(Counter);
      if Counter > 1000 then
        raise ECreateUniqueDir.CreateFmt(_('Could not find a unique directory name based on "%s"'), [Result]);
    end;
  end;
end;

type
  TUniqueTempDir = class(TInterfacedObject, IUniqueTempDir)
  private
    FPath: string;
    FDeleteOnlyIfEmpty: Boolean;
    function Path: string;
    ///<summary> Path including trailing path delimiter </summary>
    function PathBS: string;
  public
    constructor Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
    destructor Destroy; override;
  end;

class function TFileSystem.CreateUniqueTempDir(_DeleteOnlyIfEmpty: Boolean = False;
  const _Prefix: string = 'dz'): IUniqueTempDir;
begin
  Result := CreateUniqueTempDir(GetTempPath, _DeleteOnlyIfEmpty, _Prefix);
end;

class function TFileSystem.CreateUniqueTempDir(const _BaseDir: string;
  _DeleteOnlyIfEmpty: Boolean = False; const _Prefix: string = 'dz'): IUniqueTempDir;
var
  s: string;
begin
  s := CreateUniqueDirectory(_BaseDir, _Prefix);
  Result := TUniqueTempDir.Create(s, _DeleteOnlyIfEmpty);
end;

class function TFileSystem.GetTempFileName(_Directory: string = ''; const _Prefix: string = 'dz';
  _Unique: Word = 0): string;
var
  Res: Integer;
  LastError: Cardinal;
begin
  if _Directory = '' then
    _Directory := GetTempPath;
  SetLength(Result, MAX_PATH);
  Res := Windows.GetTempFileName(PChar(_Directory), PChar(_Prefix), _Unique, PChar(Result));
  if Res = 0 then begin
    // GetLastError must be called before _(), otherwise the error code gets lost
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempFilename: %1:s (Code: %0:d) calling Windows.GetTempFileName'));
  end;
  Result := PChar(Result); // remove trailing characters
end;

//class function TFileSystem.GetTempFileNameEx(_Directory: string; const _Prefix,
//  _Ext: string): string;
//var
//  st: TdzFile;
//  i: Integer;
//begin
//  if _Directory = '' then
//    _Directory := GetTempPath;
//  for i := 0 to 256 * 16 - 1 do begin
//    Result := itpd(_Directory) + _Prefix + IntToHex(MainThreadID, 3) + IntToHex(Random(256 * 16), 2) + _Ext;
//    st := TdzFile.Create(Result);
//    try
//      st.AccessMode := faReadWrite;
//      st.ShareMode := fsNoSharing;
//      st.CreateDisposition := fcCreateFailIfExists;
//      if st.OpenNoException then
//        exit;
//    finally
//      FreeAndNil(st);
//    end;
//  end;
//  raise Exception.CreateFmt(_('Unable to create a temporary file from %s.'), [itpd(_Directory) + _Prefix + '*' + _Ext]);
//end;

class function TFileSystem.CheckAccessToFile(DesiredAccess: DWORD; const Filename: WideString): Boolean;
const
  GenericFileMapping: TGenericMapping = (
    GenericRead: FILE_GENERIC_READ;
    GenericWrite: FILE_GENERIC_WRITE;
    GenericExecute: FILE_GENERIC_EXECUTE;
    GenericAll: FILE_ALL_ACCESS
    );
var
  LastError: DWORD;
  LengthNeeded: DWORD;
  SecurityDescriptor: PSecurityDescriptor;
  ClientToken: THandle;
  AccessMask: DWORD;
  PrivilegeSet: TPrivilegeSet;
  PrivilegeSetLength: DWORD;
  GrantedAccess: DWORD;
  AccessStatus: BOOL;
begin
  Result := False;
  LastError := GetLastError;
  if not GetFileSecurityW(PWideChar(Filename), OWNER_SECURITY_INFORMATION or
    GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION, nil, 0,
    LengthNeeded) and (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
    Exit;
  SetLastError(LastError);
  Inc(LengthNeeded, $1000);
  SecurityDescriptor := PSecurityDescriptor(LocalAlloc(LPTR, LengthNeeded));
  if not Assigned(SecurityDescriptor) then
    Exit;
  try
    if not GetFileSecurityW(PWideChar(Filename), OWNER_SECURITY_INFORMATION or
      GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
      SecurityDescriptor, LengthNeeded, LengthNeeded) then
      Exit;
    if not ImpersonateSelf(SecurityImpersonation) then
      Exit;
    try
      if not OpenThreadToken(GetCurrentThread, TOKEN_QUERY or
        TOKEN_IMPERSONATE or TOKEN_DUPLICATE, False, ClientToken) then
        Exit;
      try
        AccessMask := DesiredAccess;
        MapGenericMask(AccessMask, GenericFileMapping);
        PrivilegeSetLength := SizeOf(TPrivilegeSet);
        if AccessCheck(SecurityDescriptor, ClientToken, AccessMask,
          GenericFileMapping, PrivilegeSet, PrivilegeSetLength, GrantedAccess,
          AccessStatus) then
          Result := AccessStatus;
      finally
        CloseHandle(ClientToken);
      end;
    finally
      RevertToSelf;
    end;
  finally
    LocalFree(HLOCAL(SecurityDescriptor));
  end;
end;

function _TouchFileTimes(FileHandle: THandle; lpSystemTime: PSystemTime):
  BOOL; stdcall; external 'IMAGEHLP.DLL' Name 'TouchFileTimes';

class procedure TFileSystem.TouchFileTimes(const _Filename: string; lpSystemTime: PSystemTime);
var
  Handle: THandle;
  Res: BOOL;
  LastError: DWORD;
begin
  Handle := CreateFile(PChar(_Filename), GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if Handle = INVALID_HANDLE_VALUE then
    raise EFileNotFound.CreateFmt(_('File not found: "%s"'), [_Filename]);
  try
    Res := _TouchFileTimes(Handle, lpSystemTime);
    if not Res then begin
      LastError := GetLastError;
      RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to change the date and time of "%s"'), [_Filename]));
    end;
  finally
    CloseHandle(Handle);
  end;
end;

class function TFileSystem.TryGetFileInfo(const _Filename: string;
  out _Info: TFileInfoRec): Boolean;
var
  Sr: TSearchRec;
  Res: Integer;
begin
  Res := FindFirst(_Filename, faAnyFile, Sr);
  Result := (Res = 0);
  if Result then begin
    try
      _Info.Filename := _Filename;
      _Info.Size := Sr.Size;
{$IFDEF RTL220_UP}
      _Info.Timestamp := Sr.Timestamp;
{$ELSE}
      _Info.Timestamp := FileDateToDateTime(Sr.Time);
{$ENDIF}
    finally
      FindClose(Sr);
    end;
  end;
end;

class function TFileSystem.GetFileInfo(const _Filename: string): TFileInfoRec;
begin
  if not TryGetFileInfo(_Filename, Result) then
    raise EFileNotFound.CreateFmt(_('File not found: "%s"'), [_Filename]);
end;

class function TFileSystem.GetFileSize(const _Filename: string): Int64;
begin
  if not TryGetFileSize(_Filename, Result) then
    raise EFileNotFound.CreateFmt(_('File not found: "%s"'), [_Filename]);
end;

class function TFileSystem.TryGetFileSize(const _Filename: string;
  out _Size: Int64): Boolean;
var
  Info: TFileInfoRec;
begin
  Result := TryGetFileInfo(_Filename, Info);
  if Result then
    _Size := Info.Size;
end;

class function TFileSystem.DiskFree(_DriveLetter: AnsiChar): Int64;
begin
  if _DriveLetter in ['a'..'z'] then
    _DriveLetter := UpCase(_DriveLetter);

  if not (_DriveLetter in ['A'..'Z']) then
    Result := -1
  else begin
    try
      Result := SysUtils.DiskFree(Ord(_DriveLetter) - Ord('A') + 1);
    except
      Result := -1;
    end;
  end;
end;

class function TFileSystem.DiskFree(const _Directory: string): Int64;
var
  TotalSpace: Int64;
begin
  try
    if not SysUtils.GetDiskFreeSpaceEx(PChar(_Directory), Result, TotalSpace, nil) then
      Result := -1;
  except
    Result := -1;
  end;
end;

class function TFileSystem.GetShortPathname(const _LongName: string): string;
var
  Res: Integer;
  LastError: Cardinal;
begin
  SetLength(Result, MAX_PATH);
  Res := Windows.GetShortPathname(PChar(_LongName), PChar(Result), Length(Result));
  if Res = 0 then begin
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('TFileSystem.GetShortPathname: %1:s (Code: %0:d) calling Windows.GetShortPathname'));
  end else if Res > MAX_PATH then
    raise EPathTooLong.CreateFmt(_('Short pathname is longer than MAX_PATH (%d) characters'), [MAX_PATH]);
  Result := PChar(Result); // truncate at first #0
end;

class function TFileSystem.MoveFile(const _Source, _Dest: string; _RaiseException: Boolean): Boolean;
begin
  Result := Self.MoveFile(_Source, _Dest, re2ehe(_RaiseException));
end;

class function TFileSystem.MoveFile(const _Source, _Dest: string;
  _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean;
var
  LastError: Cardinal;
begin
  Result := Windows.MoveFile(PChar(_Source), PChar(_Dest));
  if not Result and (_ErrorHandling = ehRaiseException) then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to move "%s" to "%s".'), [_Source, _Dest]));
  end;
end;

class function TFileSystem.MoveFileEx(const _Source, _Dest: string; _Flags: TMoveFileExFlagSet;
  _ErrorHandling: TErrorHandlingEnum = ehRaiseException): Boolean;
var
  LastError: Cardinal;
  Flags: DWORD;
begin
  Flags := 0;
  if mfCopyAllowed in _Flags then
    Flags := Flags or MOVEFILE_COPY_ALLOWED;
  if mfDelayUntilReboot in _Flags then
    Flags := Flags or MOVEFILE_DELAY_UNTIL_REBOOT;
  if mfFailIfNotTrackable in _Flags then
    Flags := Flags or MOVEFILE_FAIL_IF_NOT_TRACKABLE;
  if mfReplaceExisting in _Flags then
    Flags := Flags or MOVEFILE_REPLACE_EXISTING;
  if mfWriteThrough in _Flags then
    Flags := Flags or MOVEFILE_WRITE_THROUGH;

  Result := Windows.MoveFileEx(PChar(_Source), PChar(_Dest), Flags);
  if not Result and (_ErrorHandling = ehRaiseException) then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to move "%s" to "%s".'), [_Source, _Dest]));
  end;
end;

class function TFileSystem.MoveFileEx(const _Source, _Dest: string; _Flags: TMoveFileExFlagSet;
  _RaiseException: Boolean): Boolean;
begin
  Result := Self.MoveFileEx(_Source, _Dest, _Flags, re2ehe(_RaiseException));
end;

class function TFileSystem.SetReadonly(const _Filename: string; _Value: Boolean;
  _RaiseException: Boolean): Boolean;
begin
  Result := Self.SetReadonly(_Filename, _Value, re2ehe(_RaiseException));
end;

class function TFileSystem.SetReadonly(const _Filename: string; _Value: Boolean;
  _ErrorHandling: TErrorHandlingEnum): Boolean;
var
  Attr: Integer;
  LastError: Cardinal;
begin
  Attr := FileGetAttr(_Filename);
  if _Value then
    Attr := Attr or SysUtils.faReadOnly
  else
    Attr := Attr and not SysUtils.faReadOnly;
  if FileSetAttr(_Filename, Attr) <> 0 then begin
    if _ErrorHandling = ehRaiseException then begin
      LastError := GetLastError;
      // duplicate % so they get passed through the format function
      RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while changing the readonly flag of "%s"'), [_Filename]));
    end;
    Result := False
  end else
    Result := True;
end;

class function TFileSystem.CopyFile(const _Source, _Dest: string; _FailIfExists: Boolean;
  _RaiseException: Boolean = True; _ForceOverwrite: Boolean = False): Boolean;
var
  LastError: Cardinal;
begin
  Result := Windows.CopyFile(PChar(_Source), PChar(_Dest), _FailIfExists);
  if not Result and not _FailIfExists and _ForceOverwrite then begin
    Self.SetReadonly(_Dest, False, ehReturnFalse);
    Result := Windows.CopyFile(PChar(_Source), PChar(_Dest), _FailIfExists);
  end;
  if not Result and _RaiseException then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to copy "%s" to "%s".'), [_Source, _Dest]));
  end;
end;

class function TFileSystem.CopyFile(const _Source, _Dest: string; _IfExists: TCopyFileFlagIfExists;
  _ErrorHandling: TErrorHandlingEnum; _IfReadOnly: TCopyFileFlagOverwriteReadonly): Boolean;
var
  LastError: Cardinal;
begin
  Result := Windows.CopyFile(PChar(_Source), PChar(_Dest), (_IfExists = cfeFailIfExists));
  if not Result and (_IfExists = cfeOverwriteIfExists) and (_IfReadOnly = cforOverwriteReadonly) then begin
    Self.SetReadonly(_Dest, False, ehReturnFalse);
    Result := Windows.CopyFile(PChar(_Source), PChar(_Dest), (_IfExists = cfeFailIfExists));
  end;
  if not Result and (_ErrorHandling = ehRaiseException) then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to copy "%s" to "%s".'), [_Source, _Dest]));
  end;
end;

class function TFileSystem.CopyFile(const _Source, _Dest: string; _Flags: TCopyFileFlagSet): Boolean;
var
  IfExists: TCopyFileFlagIfExists;
  ErrorHandling: TErrorHandlingEnum;
  IfReadOnly: TCopyFileFlagOverwriteReadonly;
begin
  if cfFailIfExists in _Flags then
    IfExists := cfeFailIfExists
  else
    IfExists := cfeOverwriteIfExists;
  if cfRaiseException in _Flags then
    ErrorHandling := ehRaiseException
  else
    ErrorHandling := ehReturnFalse;
  if cfForceOverwrite in _Flags then
    IfReadOnly := cforOverwriteReadonly
  else
    IfReadOnly := cforDoNotOverwriteReadonly;
  Result := Self.CopyFile(_Source, _Dest,
    IfExists, ErrorHandling, IfReadOnly);
end;

class function TFileSystem.AppendDateTime(const _Filename: string; _DateTime: TDateTime;
  _IncludeTime: Boolean): string;
var
  BaseFilename: string;
  Ext: string;
begin
  Ext := ExtractFileExtFull(_Filename);
  BaseFilename := ChangeFileExtFull(_Filename, '');
  Result := BaseFilename + '_' + ReplaceChars(DateTime2Iso(_DateTime, _IncludeTime), ': ', '-_') + Ext;
end;

class function TFileSystem.AppendDate(const _Filename: string; _Date: TDateTime): string;
begin
  Result := AppendDateTime(_Filename, _Date, False);
end;

class function TFileSystem.AppendDate(const _Filename: string): string;
begin
  Result := AppendDateTime(_Filename, SysUtils.Date, False);
end;

class function TFileSystem.AppendDateAndTime(const _Filename: string): string;
begin
  Result := AppendDateTime(_Filename, Now, True);
end;

class function TFileSystem.AppendDateAndTime(const _Filename: string; _DateTime: TDateTime): string;
begin
  Result := AppendDateTime(_Filename, _DateTime, True);
end;

class function TFileSystem.GenerateBackupFilename(const _Filename: string; _BackupDir: string = ''): string;
var
  Ext: string;
  FilenameOnly: string;
  Base: string;
begin
  if _BackupDir = '' then
    _BackupDir := ExtractFilePath(_Filename);
  _BackupDir := itpd(_BackupDir);
  FilenameOnly := ExtractFileName(_Filename);
  Ext := ExtractFileExt(FilenameOnly);
  Base := ChangeFileExt(FilenameOnly, '');
  Result := _BackupDir + Base + '_' + ReplaceChars(DateTime2Iso(Now, True), ': ', '-_') + Ext;
end;

{$IFDEF SUPPORTS_ENHANCED_RECORDS}
class function TFileSystem.BackupFile(const _Filename: string; const _BackupDir: string = ''): string;
var
  i: Integer;
  BackupFilename: TFilename;
  LastError: Cardinal;
  ErrorMessage: string;
begin
  BackupFilename := GenerateBackupFilename(_Filename, _BackupDir);
  Result := BackupFilename;

  ForceDir(ExtractFilePath(BackupFilename));

  i := 0;
  while not Self.CopyFile(_Filename, Result, cfeFailIfExists, ehReturnFalse) do begin
    Inc(i);
    if i = 1000 then begin
      LastError := GetLastError;
      ErrorMessage := SysErrorMessage(LastError);
      raise EBackupFailed.CreateFmt(
        _('Failed to create a backup of "%s". Tried 1000 different file names based on "%s".')
        + ' (%s)',
        [_Filename, BackupFilename.Filename, ErrorMessage]);
    end;
    Result := BackupFilename.DirectoryBS
      + BackupFilename.FilenameOnly + '_' + IntToStr(i)
      + BackupFilename.Extension;
  end;
end;

class function TFileSystem.MoveFileToBackup(const _Filename: string; const _BackupDir: string = ''): string;
var
  i: Integer;
  BackupFilename: TFilename;
  LastError: Cardinal;
  ErrorMessage: string;
begin
  BackupFilename := GenerateBackupFilename(_Filename, _BackupDir);
  Result := BackupFilename;

  ForceDir(ExtractFilePath(BackupFilename));

  i := 0;
  while not Self.MoveFileEx(_Filename, Result, [mfCopyAllowed], ehReturnFalse) do begin
    Inc(i);
    if i = 1000 then begin
      LastError := GetLastError;
      ErrorMessage := SysErrorMessage(LastError);
      raise EBackupFailed.CreateFmt(
        _('Failed to create a backup of "%s". Tried 1000 different file names based on "%s".')
        + ' (%s)',
        [_Filename, BackupFilename.Filename, ErrorMessage]);
    end;
    Result := BackupFilename.DirectoryBS
      + BackupFilename.FilenameOnly + '_' + IntToStr(i)
      + BackupFilename.Extension;
  end;
end;

{$ENDIF}

class function TFileSystem.ContainsWildcard(const _Mask: string): Boolean;
begin
  Result := (Pos('?', _Mask) > 0) or (Pos('*', _Mask) > 0);
end;

class function TFileSystem.CopyDir_Old(const _SrcDir, _DestDir: string; _CreateIntermediate,
  _RaiseException: Boolean): Boolean;
var
  CreateIntermediate: TCopyDirCreateIntermediate;
begin
  if _CreateIntermediate then
    CreateIntermediate := cdciCreateIntermediate
  else
    CreateIntermediate := cdciDoNotCreateIntermediate;

  Result := Self.CopyDir(_SrcDir, _DestDir, CreateIntermediate, re2ehe(_RaiseException));
end;

class function TFileSystem.CopyDir(const _SrcDir, _DestDir: string;
  _CreateIntermediate: TCopyDirCreateIntermediate; _ErrorHandling: TErrorHandlingEnum): Boolean;
var
  SrcDirBs: string;
  ParentDir: string;
  Files: TStringList;
  i: Integer;
  DestDirBS: string;
begin
  Result := False;
  SrcDirBs := itpd(_SrcDir);
  ParentDir := ExtractFileDir(_DestDir);
  if (ParentDir <> '') and not DirExists(ParentDir) then begin
    if _CreateIntermediate = cdciDoNotCreateIntermediate then begin
      if _ErrorHandling = ehRaiseException then
        raise Exception.CreateFmt(_('Error copying directory "%s" to "%s": Destination path "%s" does not exist.'),
          [_SrcDir, _DestDir, ParentDir]);
      Exit;
    end;
  end;

  if TFileSystem.DirExists(_DestDir) then begin
    if _ErrorHandling = ehRaiseException then
      raise Exception.CreateFmt(_('Directory %s already exists.'), [_DestDir]);
    Exit;
  end;

  if not TFileSystem.ForceDir(_DestDir, _ErrorHandling) then
    Exit;

  DestDirBS := itpd(_DestDir);
  Files := TStringList.Create;
  try
    TSimpleDirEnumerator.Execute(SrcDirBs + '*', Files, [dfaArchive, dfaReadonly], False);
    for i := 0 to Files.Count - 1 do
      Self.CopyFile(SrcDirBs + Files[i], DestDirBS + Files[i], cfeFailIfExists, _ErrorHandling);
  finally
    FreeAndNil(Files);
  end;
end;

type
  TProgressRedir = class(TCopyProgressStatus)
  private
    FOnProgress: TCopyFileProgressEvt;
  private
    FCancelFlag: BOOL;
    // should an exception be raised within the OnProgress callback, it is stored here
    FExceptAddr: Pointer;
    FExceptMsg: string;
    FExceptClass: string;
    function doProgress(): TCopyProgressResult;
  public
    constructor Create(_OnProgress: TCopyFileProgressEvt);
  end;

//  PROGRESS_CONTINUE = 0;
//  PROGRESS_CANCEL = 1;
//  PROGRESS_STOP = 2;
//  PROGRESS_QUIET = 3;

//  CALLBACK_CHUNK_FINISHED = $00000000;
//  CALLBACK_STREAM_SWITCH = $00000001;

function ProgressCallback(
  _TotalFileSize, _TotalBytesTransferred, _StreamSize, _StreamBytesTransferred: LARGE_INTEGER;
  _StreamNumber, _CallbackReason: LongWord;
  _SourceFile, _DestinationFile: THandle; _Data: Pointer): LongWord; far; stdcall;
var
  Status: TProgressRedir;
begin
  try
    Status := TProgressRedir(_Data);
    Status.FTotalFileSize := _TotalFileSize;
    Status.FTotalBytesTransferred := _TotalBytesTransferred;
    Status.FStreamSize := _StreamSize;
    Status.FStreamBytesTransferred := _StreamBytesTransferred;
    Status.FStreamNumber := _StreamNumber;
    case _CallbackReason of
      CALLBACK_CHUNK_FINISHED: Status.FCallbackReason := prChunkFinished;
      CALLBACK_STREAM_SWITCH: Status.FCallbackReason := prStreamSwitch;
    else
    // Shouldn't happen, assume CALLBACK_CHUNK_FINISHED for now
      Status.FCallbackReason := prChunkFinished;
    end;
    Status.FSourceFile := _SourceFile;
    Status.FDestinationFile := _DestinationFile;
    case Status.doProgress() of
      prContinue: Result := PROGRESS_CONTINUE;
      prCancel: Result := PROGRESS_CANCEL;
      prStop: Result := PROGRESS_STOP;
      prQuiet: Result := PROGRESS_QUIET;
    else // should not happen, assume prContinue
      Result := PROGRESS_CONTINUE;
    end;
  except
    // Ignore exceptions here since the progess display should not affect the actual copying.
    // Any exceptions whithin doProgress should be handled there and communicated to the main
    // thread.
    Result := PROGRESS_CONTINUE;
  end;
end;

//  COPY_FILE_FAIL_IF_EXISTS = $00000001;
//  COPY_FILE_RESTARTABLE = $00000002;

class function TFileSystem.CopyFileWithProgress(const _Source, _Dest: string;
  _Progress: TCopyFileProgressEvt;
  _Flags: TCopyFileWithProgressFlagSet): TCopyFileWithProgressResult;
var
  IfExists: TCopyFileFlagIfExists;
  ErrorHandling: TErrorHandlingEnum;
  Restartable: TCopyFileWithProgressRestartable;
begin
  if cfwFailIfExists in _Flags then
    IfExists := cfeFailIfExists
  else
    IfExists := cfeOverwriteIfExists;

  if cfwRestartable in _Flags then
    Restartable := cfwrRestartable
  else
    Restartable := cfwrNotRestartable;

  if cfwRaiseException in _Flags then
    ErrorHandling := ehRaiseException
  else
    ErrorHandling := ehReturnFalse;

  Result := Self.CopyFileWithProgress(_Source, _Dest, _Progress, IfExists, ErrorHandling, Restartable);
end;

class function TFileSystem.CopyFileWithProgress(const _Source, _Dest: string;
  _Progress: TCopyFileProgressEvt; _IfExists: TCopyFileFlagIfExists;
  _ErrorHandling: TErrorHandlingEnum;
  _Restartable: TCopyFileWithProgressRestartable): TCopyFileWithProgressResult;
var
  Redir: TProgressRedir;
  Flags: DWORD;
  Res: BOOL;
  LastError: DWORD;
begin
  Result := cfwError;
  Redir := TProgressRedir.Create(_Progress);
  try
    Flags := 0;
    if _IfExists = cfeFailIfExists then
      Flags := Flags or COPY_FILE_FAIL_IF_EXISTS;
    if _Restartable = cfwrRestartable then
      Flags := Flags or COPY_FILE_RESTARTABLE;
    Res := Windows.CopyFileEx(PChar(_Source), PChar(_Dest), @ProgressCallback, Redir,
      @Redir.FCancelFlag, Flags);
    if Redir.FExceptAddr <> nil then begin
      if _ErrorHandling = ehRaiseException then begin
        raise Exception.CreateFmt(_('Error %s (%s) in progress callback while trying to copy "%s" to "%s".'),
          [Redir.FExceptMsg, Redir.FExceptClass, _Source, _Dest])at Redir.FExceptAddr;
      end;
      Exit; //==>
    end;
    if not Res then begin
      LastError := GetLastError;
      if LastError = ERROR_REQUEST_ABORTED then
        Result := cfwAborted
      else begin
        if _ErrorHandling = ehRaiseException then begin
          // duplicate % so they get passed through the format function
          RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to copy "%s" to "%s".'), [_Source, _Dest]));
        end;
      end;
    end else
      Result := cfwOK;
  finally
    FreeAndNil(Redir);
  end;
end;

class function TFileSystem.CopyMatchingFiles(const _Mask, _SrcDir, _DestDir: string;
  _Flags: TCopyFileFlagSet; _FilesSkipped: TStrings = nil): Integer;
var
  i: Integer;
  Files: TStringList;
  s: string;
  SrcDirBs: string;
  DestDirBS: string;
begin
  Result := 0;
  SrcDirBs := itpd(_SrcDir);
  DestDirBS := itpd(_DestDir);
  Files := TStringList.Create;
  try
    TSimpleDirEnumerator.Execute(SrcDirBs + _Mask, Files, [dfaHidden, dfaSysFile, dfaArchive]);
    for i := 0 to Files.Count - 1 do begin
      s := Files[i];
      if Self.CopyFile(SrcDirBs + s, DestDirBS + s, _Flags) then
        Inc(Result)
      else begin
        if Assigned(_FilesSkipped) then
          _FilesSkipped.Add(s);
      end;
    end;
  finally
    FreeAndNil(Files);
  end;
end;

//  MOVEFILE_REPLACE_EXISTING       = $00000001;
//  MOVEFILE_COPY_ALLOWED           = $00000002;
//  MOVEFILE_DELAY_UNTIL_REBOOT     = $00000004;
//  MOVEFILE_WRITE_THROUGH          = $00000008;
//  MOVEFILE_CREATE_HARDLINK        = $00000010;
//  MOVEFILE_FAIL_IF_NOT_TRACKABLE  = $00000020;

class function TFileSystem.MoveFileWithProgress(const _Source, _Dest: string;
  _Progress: TCopyFileProgressEvt;
  _Flags: TMoveFileWithProgressFlagSet = [mfwRaiseException]): TCopyFileWithProgressResult;
var
  Redir: TProgressRedir;
  Flags: DWORD;
  Res: BOOL;
  LastError: DWORD;
begin
  Redir := TProgressRedir.Create(_Progress);
  try
    Flags := MOVEFILE_REPLACE_EXISTING;
    if mfwFailIfExists in _Flags then
      Flags := Flags - MOVEFILE_REPLACE_EXISTING;
    if mfwAllowCopy in _Flags then
      Flags := Flags or MOVEFILE_COPY_ALLOWED;
    if mfwDelayUntilReboot in _Flags then
      Flags := Flags or MOVEFILE_DELAY_UNTIL_REBOOT;
    if mfwWriteThrough in _Flags then
      Flags := Flags or MOVEFILE_WRITE_THROUGH;
    if mfwFailIfNotTrackable in _Flags then
      Flags := Flags or MOVEFILE_FAIL_IF_NOT_TRACKABLE;
    Res := Windows.MoveFileWithProgress(PChar(_Source), PChar(_Dest),
      @ProgressCallback, Redir, Flags);
    if not Res then begin
      LastError := GetLastError;
      if mfwRaiseException in _Flags then begin
        // duplicate % so they get passed through the format function
        RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) while trying to move "%s" to "%s".'), [_Source, _Dest]));
      end;

      if LastError = ERROR_REQUEST_ABORTED then
        Result := cfwAborted
      else
        Result := cfwError;
    end else
      Result := cfwOK;
  finally
    FreeAndNil(Redir);
  end;
end;

class function TFileSystem.DeleteFile(const _Filename: string; _RaiseException: Boolean = True;
  _Force: Boolean = False): Boolean;
var
  Attr: Integer;
  LastError: Cardinal;
begin
  Result := SysUtils.DeleteFile(_Filename);
  if not Result and _Force then begin
    Attr := FileGetAttr(_Filename);
    Attr := Attr and not SysUtils.faReadOnly;
    FileSetAttr(_Filename, Attr);
    Result := SysUtils.DeleteFile(_Filename);
  end;
  if not Result and _RaiseException then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) deleting file "%s"'), [_Filename]));
  end;
end;

class function TFileSystem.DeleteMatchingFiles(const _Dir, _Mask: string;
  _RaiseException, _Force: Boolean): Integer;
begin
  Result := DeleteMatchingFiles(_Dir, _Mask, [], _RaiseException, _Force);
end;

class function TFileSystem.DeleteMatchingFiles(const _Dir, _Mask: string;
  const _ExceptMask: string; _RaiseException, _Force: Boolean): Integer;
begin
  Result := DeleteMatchingFiles(_Dir, _Mask, [_ExceptMask], _RaiseException, _Force);
end;

class function TFileSystem.DeleteMatchingFiles(const _Dir, _Mask: string;
  const _ExceptMasks: array of string; _RaiseException,
  _Force: Boolean): Integer;

  function MatchesAnyExceptMask(const _s: string): Boolean;
  var
    i: Integer;
    Mask: string;
  begin
    for i := Low(_ExceptMasks) to High(_ExceptMasks) do begin
      Mask := LowerCase(_ExceptMasks[i]);
      if MatchesMask(_s, Mask) then begin
        Result := True;
        Exit;
      end;
    end;
    Result := False;
  end;

var
  Sr: TSearchRec;
  dir: string;
begin
  Assert(_Dir <> '', 'Dir parameter must not be an empty string');
  Assert(_Mask <> '', 'Mask parameter must not be an empty string');

  Result := 0;
  dir := IncludeTrailingPathDelimiter(_Dir);
  if 0 = FindFirst(dir + _Mask, faAnyFile, Sr) then begin
    try
      repeat
        if (Sr.Name <> '.') and (Sr.Name <> '..') then
          if ((Sr.Attr and SysUtils.faDirectory) = 0) then
            if not MatchesAnyExceptMask(LowerCase(Sr.Name)) then
              if not DeleteFile(dir + Sr.Name, _RaiseException, _Force) then
                Inc(Result);
      until 0 <> FindNext(Sr);
    finally
      FindClose(Sr);
    end;
  end;
end;

class function TFileSystem.FileExists(const _Filename: string): Boolean;
var
  OldErrorMode: Cardinal;
begin
  OldErrorMode := SetErrorMode(SEM_NOOPENFILEERRORBOX);
  try
    Result := SysUtils.FileExists(_Filename);
  finally
    SetErrorMode(OldErrorMode)
  end;
end;

class function TFileSystem.FileExists(const _Filename: string; _RaiseException: Boolean): Boolean;
begin
  if _RaiseException then begin
    AssertFileExists(_Filename);
    Result := True;
  end else
    Result := FileExists(_Filename);
end;

class procedure TFileSystem.AssertFileExists(const _Filename: string);
begin
  if not FileExists(_Filename) then
    raise EFileNotFound.CreateFmt(_('File not found: %s'), [_Filename]);
end;

class procedure TFileSystem.AssertMatchingFileExists(const _Mask: string);
begin
  if FindMatchingFile(_Mask) <> mfFile then
    raise EFileNotFound.CreateFmt(_('Matching file not found: %s'), [_Mask]);
end;

class function TFileSystem.DirExists(const _DirName: string): Boolean;
begin
  Result := SysUtils.DirectoryExists(_DirName);
end;

class function TFileSystem.DirExists(const _DirName: string; _RaiseException: Boolean): Boolean;
begin
  Result := False;
  if _RaiseException then
    AssertDirExists(_DirName)
  else
    Result := DirExists(_DirName);
end;

class procedure TFileSystem.AssertDirExists(const _DirName: string);
begin
  if not SysUtils.DirectoryExists(_DirName) then
    raise EDirNotFound.CreateFmt(_('Directory not found: %s'), [_DirName]);
end;

class function TFileSystem.AllDirsExist(_DirNames: TStrings): Boolean;
var
  i: Integer;
begin
  Result := True;
  for i := 0 to _DirNames.Count - 1 do begin
    if not DirExists(_DirNames[i]) then begin
      Result := False;
      Exit; //==>
    end;
  end;
end;

class function TFileSystem.FindMatchingDirs(const _Mask: string; _sl: TStrings;
  _IncludePath: Boolean = False): Integer;
var
  enum: TSimpleDirEnumerator;
begin
  enum := TSimpleDirEnumerator.Create(_Mask, [dfaDirectory, dfaArchive]);
  try
    enum.MustHaveAttr := [dfaDirectory];
    Result := enum.FindAll(_sl, _IncludePath);
  finally
    FreeAndNil(enum);
  end;
end;

class function TFileSystem.FindMatchingDir(const _Mask: string; out _Dir: string): Boolean;
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    Result := FindMatchingDirs(_Mask, sl, True) > 0;
    if Result then
      _Dir := sl[0];
  finally
    FreeAndNil(sl);
  end;
end;

class function TFileSystem.FindMatchingFile(const _Mask: string): TMatchingFileResult;
var
  fn: string;
begin
  Result := FindMatchingFile(_Mask, fn);
end;

class function TFileSystem.FindMatchingFiles(const _Mask: string;
  _IncludePath: Boolean; _Sort: Boolean): TStringArray;
var
  Cnt: Integer;
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    Cnt := FindMatchingFiles(_Mask, sl, _IncludePath, _Sort);
    SetLength(Result, Cnt);
    for i := 0 to Cnt - 1 do
      Result[i] := sl[i];
  finally
    FreeAndNil(sl);
  end;
end;

class function TFileSystem.FindMatchingFiles(const _Mask: string; _sl: TStrings;
  _IncludePath: Boolean = False; _Sort: Boolean = True): Integer;
begin
  Result := TSimpleDirEnumerator.Execute(_Mask, _sl, [dfaArchive], _IncludePath, _Sort);
end;

class function TFileSystem.FindMatchingFile(const _Mask: string; out _Filename: string): TMatchingFileResult;
var
  Sr: TSearchRec;
begin
  Result := mfNotFound;
  if 0 = FindFirst(_Mask, faAnyFile, Sr) then
    try
      repeat
        if (Sr.Name <> '.') and (Sr.Name <> '..') then begin
          _Filename := Sr.Name;
          if (Sr.Attr and SysUtils.faDirectory) <> 0 then
            Result := mfDirectory
          else
            Result := mfFile;
          Exit;
        end;
      until 0 <> FindNext(Sr);
    finally
      FindClose(Sr);
    end;
end;

class function TFileSystem.ForceDir_old(const _DirectoryPath: string; _RaiseException: Boolean): Boolean;
begin
  Result := Self.ForceDir(_DirectoryPath, re2ehe(_RaiseException));
end;

class function TFileSystem.ForceDir(const _DirectoryPath: string;
  _ErrorHandling: TErrorHandlingEnum): Boolean;
var
  LastError: Cardinal;
begin
  try
    Result := SysUtils.ForceDirectories(_DirectoryPath);
  except
    on e: Exception do begin
      // ForceDirectories can raise EInOutError if the directory path contains empty parts
      if _ErrorHandling = ehRaiseException then
        raise Exception.CreateFmt(_('Error creating directory "%s": %s (%s)'), [_DirectoryPath, e.Message, e.ClassName]);
      Result := False;
      Exit;
    end;
  end;
  if not Result and (_ErrorHandling = ehRaiseException) then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) creating directory "%s"'), [_DirectoryPath]));
  end;
end;

class function TFileSystem.RemoveDir(const _DirName: string; _RaiseException: Boolean = True; _Force: Boolean = False): Boolean;
var
  Attr: Integer;
  LastError: Cardinal;
begin
  Result := SysUtils.RemoveDir(_DirName);
  if not Result and _Force then begin
    Attr := FileGetAttr(_DirName);
    Attr := Attr and not SysUtils.faReadOnly;
    FileSetAttr(_DirName, Attr);
    Result := SysUtils.RemoveDir(_DirName);
  end;
  if not Result and _RaiseException then begin
    LastError := GetLastError;
    // duplicate % so they get passed through the format function
    RaiseLastOSErrorEx(LastError, Format(_('Error %%1:s (%%0:d) deleting directory "%s"'), [_DirName]));
  end;
end;

class function TFileSystem.DelTree(const _DirName: string; _Force: Boolean = False; _RaiseException: Boolean = True): Boolean;
begin
  Result := DelDirTree(_DirName, _RaiseException, _Force);
end;

class function TFileSystem.DelDirTree(const _DirName: string; _RaiseException,
  _Force: Boolean): Boolean;
var
  Sr: TSearchRec;
  Filename: string;
begin
  Result := DirectoryExists(ExcludeTrailingPathDelimiter(_DirName));
  if not Result then begin
    if _RaiseException then
      raise EDirNotFound.CreateFmt(_('"%s" does not exist or is not a directory'), [_DirName]);
    Exit;
  end;
  if 0 = FindFirst(IncludeTrailingPathDelimiter(_DirName) + '*.*', faAnyFile, Sr) then
    try
      repeat
        if (Sr.Name = '.') or (Sr.Name = '..') then begin
            // ignore
        end else begin
          Filename := IncludeTrailingPathDelimiter(_DirName) + Sr.Name;
          if (Sr.Attr and SysUtils.faDirectory) <> 0 then begin
            Result := DelDirTree(Filename, _RaiseException, _Force);
            if not Result then
              Exit;
          end else begin
            Result := DeleteFile(Filename, _RaiseException, _Force);
            if not Result then
              Exit;
          end;
        end;
      until 0 <> FindNext(Sr);
    finally
      SysUtils.FindClose(Sr);
    end;
  Result := RemoveDir(_DirName, _RaiseException, _Force);
end;

class function TFileSystem.ReadTextFile(const _Filename: string): string;
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    sl.LoadFromFile(_Filename);
    Result := sl.Text;
  finally
    FreeAndNil(sl);
  end;
end;

class function TFileSystem.IsFileReadonly(const _Filename: string): Boolean;
var
  Attributes: Word;
begin
  Result := False;
  if FileExists(_Filename) then begin
    Attributes := FileGetAttr(_Filename);
    Result := ((Attributes and SysUtils.faReadOnly) <> 0);
  end;
end;

class function TFileSystem.IsFileWritable(const _Filename: string): Boolean;
var
  Attributes: Word;
  Res: THandle;
begin
  Result := False;
  if not FileExists(_Filename) then begin
      // File does not exit
    Exit; //==>
  end;
  Attributes := FileGetAttr(_Filename);
  if ((Attributes and SysUtils.faReadOnly) <> 0) then begin
    // File has ReadOnly flag
    Exit; //==>
  end;
  Res := CreateFile(PChar(_Filename), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE,
    nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if Res = INVALID_HANDLE_VALUE then begin
        // opening file failed
    Exit; //==>
  end;
  CloseHandle(Res);
  Result := True;
end;

class function TFileSystem.GetFullPathName(const _fn: string): string;
var
  Buffer: array[0..MAX_PATH] of Char;
  Res: DWORD;
  LastError: DWORD;
  NamePart: PChar;
begin
  Res := Windows.GetFullPathName(PChar(_fn), MAX_PATH, @Buffer[0], NamePart);
  if Res = 0 then begin
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('%s (code %d) calling Windows.GetFullPathName'));
  end;
  if Res >= MAX_PATH then
    raise Exception.CreateFmt(_('Windows.GetFullPathName returned a length > MAX_PATH (%d)'), [Res]);
  Result := PChar(@Buffer);
end;

class function TFileSystem.IsSameFilename(const _fn1, _fn2: string): Boolean;
var
  fn1: string;
  fn2: string;
begin
  fn1 := GetFullPathName(_fn1);
  fn2 := GetFullPathName(_fn2);
  Result := SameText(fn1, fn2);
end;

class function TFileSystem.IsValidFilename(const _s: string; out _ErrPos: Integer;
  _AllowDot: Boolean = True; _AllowPathChars: Boolean = False): Boolean;
var
  i: Integer;
  NotAllowed: TCharSet;
begin
  Result := False;

  if _s = '' then begin
    _ErrPos := 0;
    Exit; //==>
  end;

  if Length(_s) > MAX_PATH then begin
    _ErrPos := MAX_PATH;
    Exit; //==>
  end;

  NotAllowed := INVALID_FILENAME_CHARS;
  if not _AllowDot then
    Include(NotAllowed, '.');

  if _AllowPathChars then
    Exclude(NotAllowed, '\');

  for i := 1 to Length(_s) do begin
    if CharInSet(_s[i], NotAllowed) then begin
      if not _AllowPathChars or (i <> 2) or (_s[i] <> ':') or not CharInSet(UpCase(_s[1]), ['A'..'Z']) then begin
        _ErrPos := i;
        Exit; //==>
      end;
    end;
  end;
  Result := True;
end;

class function TFileSystem.IsValidFilename(const _s: string; _AllowDot: Boolean = True;
  _AllowPathChars: Boolean = False): Boolean;
var
  ErrPos: Integer;
begin
  Result := IsValidFilename(_s, ErrPos, _AllowDot, _AllowPathChars);
end;

class function TFileSystem.MakeValidFilename(const _s: string; _ReplaceChar: Char = '_';
  _AllowPathChars: Boolean = True): string;
var
  i: Integer;
  InvalidChars: set of AnsiChar;
begin
  Result := _s;
  InvalidChars := INVALID_FILENAME_CHARS;
  if _AllowPathChars then
    InvalidChars := InvalidChars - ['\'];
  for i := 1 to Length(Result) do begin
    if CharInSet(Result[i], InvalidChars) then begin
      if not _AllowPathChars or (i <> 2) or (Result[2] <> ':') or not CharInSet(UpCase(Result[1]), ['A'..'Z']) then
        Result[i] := _ReplaceChar;
    end;
  end;
end;

// taken from
// http://stackoverflow.com/a/5330691/49925
const
  shlwapi32 = 'shlwapi.dll';

function PathIsRelativeAPI(pszPath: PChar): BOOL; stdcall; external shlwapi32
{$IFDEF UNICODE}
Name 'PathIsRelativeW';
{$ELSE}
Name 'PathIsRelativeA';
{$ENDIF}

function PathCanonicalize(pszBuf: PChar; pszPath: PChar): BOOL; stdcall; external shlwapi32
{$IFDEF UNICODE}
Name 'PathCanonicalizeW';
{$ELSE}
Name 'PathCanonicalizeA';
{$ENDIF}

class function TFileSystem.PathIsRelative(const _Filename: string): Boolean;
begin
  Result := PathIsRelativeAPI(PChar(_Filename));
end;

class function TFileSystem.ExpandFileNameRelBaseDir(const _Filename, _BaseDir: string): string;
var
  Buffer: array[0..MAX_PATH - 1] of Char;
begin
  if PathIsRelative(_Filename) then begin
    Result := IncludeTrailingPathDelimiter(_BaseDir) + _Filename;
  end else begin
    Result := _Filename;
  end;
  if PathCanonicalize(@Buffer[0], PChar(Result)) then begin
    Result := Buffer;
  end;
end;

class function TFileSystem.ExtractFileExtFull(const _Filename: string; _IncludeDot: Boolean = True): string;
var
  p: Integer;
  fn: string;
begin
  fn := SysUtils.ExtractFileName(_Filename);
  p := Pos('.', fn);
  if p = 0 then
    Result := ''
  else begin
    if _IncludeDot then
      Result := TailStr(fn, p)
    else
      Result := TailStr(fn, p + 1);
  end;
end;

class function TFileSystem.ExtractFileExtLast(const _Filename: string; _IncludeDot: Boolean = True): string;
begin
  Result := SysUtils.ExtractFileExt(_Filename);
  if not _IncludeDot then
    Result := TailStr(Result, 2);
end;

class function TFileSystem.RemoveFileExtFull(const _Filename: string): string;
var
  Path: string;
  fn: string;
  p: Integer;
begin
  Path := ExtractFilePath(_Filename);
  fn := ExtractFileName(_Filename);
  p := Pos('.', fn);
  if p = 0 then
    Result := fn
  else
    Result := LeftStr(fn, p - 1);

  if Path <> '' then begin
    itpd(Path);
    Result := Path + Result;
  end;
end;

class function TFileSystem.RemoveFileExtLast(const _Filename: string): string;
begin
  Result := ChangeFileExtLast(_Filename, '');
end;

class function TFileSystem.ChangeFileExtFull(const _Filename: string; const _NewExt: string): string;
begin
  Result := RemoveFileExtFull(_Filename) + _NewExt;
end;

class function TFileSystem.ChangeFileExtLast(const _Filename, _NewExt: string): string;
begin
  Result := SysUtils.ChangeFileExt(_Filename, _NewExt);
end;

{ TProgressRedir }

constructor TProgressRedir.Create(_OnProgress: TCopyFileProgressEvt);
begin
  inherited Create;
  FOnProgress := _OnProgress;
end;

function TProgressRedir.doProgress(): TCopyProgressResult;
begin
  Result := prContinue;
  try
    if Assigned(FOnProgress) then
      FOnProgress(Self, Result);
  except
    on e: Exception do begin
      FExceptAddr := ExceptAddr;
      FExceptMsg := e.Message;
      FExceptClass := e.ClassName;
    end;
  end;
end;

{ TFileGenerationHandler }

constructor TFileGenerationHandler.Create(const _BaseName, _Suffix: string);
begin
  inherited Create;
  FMaxGenerations := 5;
  FOldestIsHighest := True;
  FResultContainsNumber := False;
  FPrependZeros := 0;
  FBaseName := _BaseName;
  FSuffix := _Suffix;
end;

function TFileGenerationHandler.Execute(_KeepOriginal: Boolean): string;

  function doNoNumberOldIsHighest(): string;
  var
    i: Integer;
    dst: string;
    src: string;
    MaxGen: Integer;
  begin
    MaxGen := FMaxGenerations - 1;
    for i := MaxGen - 1 downto 1 do begin
      dst := GenerateFilename(i + 1);
      if FileExists(dst) then
        TFileSystem.DeleteFile(dst);
      src := GenerateFilename(i);
      if FileExists(src) then
        TFileSystem.MoveFile(src, dst);
    end;
    dst := GenerateFilename(1);
    Result := GenerateFilename(0);
    if FileExists(dst) then
      TFileSystem.DeleteFile(dst);
    if FileExists(Result) then begin
      if _KeepOriginal then
        TFileSystem.CopyFile(Result, dst, cfeFailIfExists)
      else
        TFileSystem.MoveFile(Result, dst);
    end;
  end;

  function doNumberOldIsHighest(): string;
  var
    i: Integer;
    dst: string;
    src: string;
    MaxGen: Integer;
  begin
    MaxGen := FMaxGenerations;
    for i := MaxGen - 1 downto 1 do begin
      dst := GenerateFilename(i + 1);
      if FileExists(dst) then
        TFileSystem.DeleteFile(dst);
      src := GenerateFilename(i);
      if FileExists(src) then
        TFileSystem.MoveFile(src, dst);
    end;
    Result := GenerateFilename(1);
  end;

  function doNoNumberOldIsLowest(): string;
  var
    i: Integer;
    MaxGen: Integer;
    src: string;
    dst: string;
    SlotFound: Boolean;
  begin
    Result := GenerateFilename(0);
    if not FileExists(Result) then
      Exit;

    SlotFound := False;
    MaxGen := FMaxGenerations - 1;
    for i := 1 to MaxGen do begin
      dst := GenerateFilename(i);
      if not FileExists(dst) then begin
        SlotFound := True;
        Break;
      end;
    end;

    if not SlotFound then begin
      dst := GenerateFilename(1);
      if FileExists(dst) then
        TFileSystem.DeleteFile(dst);
      for i := 2 to MaxGen do begin
        src := GenerateFilename(i);
        if FileExists(src) then
          TFileSystem.MoveFile(src, dst);
        dst := src;
      end;
    end;

    if _KeepOriginal then
      TFileSystem.CopyFile(Result, dst, cfeFailIfExists)
    else
      TFileSystem.MoveFile(Result, dst);
  end;

  function doNumberOldIsLowest(): string;
  var
    i: Integer;
    MaxGen: Integer;
  begin
    MaxGen := FMaxGenerations;
    for i := 1 to MaxGen do begin
      Result := GenerateFilename(i);
      if not FileExists(Result) then
        Exit;
    end;

    TFileSystem.DeleteFile(GenerateFilename(1));
    for i := 2 to MaxGen do begin
      TFileSystem.MoveFile(GenerateFilename(i), GenerateFilename(i - 1));
    end;
    Result := GenerateFilename(MaxGen);
    if _KeepOriginal then
      TFileSystem.CopyFile(GenerateFilename(MaxGen - 1), Result, cfeFailIfExists);
  end;

begin
  if FResultContainsNumber then begin
    if _KeepOriginal then
      raise EInvalidPropertyCombination.Create(_('Combination of ResultContainsNumber and KeepOriginal is not allowed'));
    if FOldestIsHighest then begin
      Result := doNumberOldIsHighest();
    end else begin
      Result := doNumberOldIsLowest();
    end;
  end else begin
    if FOldestIsHighest then begin
      Result := doNoNumberOldIsHighest();
    end else begin
      Result := doNoNumberOldIsLowest();
    end;
  end;
end;

function TFileGenerationHandler.GenerateFilename(_Generation: Integer): string;
begin
  if _Generation = 0 then
    Result := FBaseName + FSuffix
  else begin
    if FPrependZeros = 0 then
      Result := FBaseName + '_' + IntToStr(_Generation) + FSuffix
    else
      Result := Format('%s_%.*u%s', [FBaseName, FPrependZeros, _Generation, FSuffix]);
  end;
  if Assigned(FOnGenerateFilename) then
    FOnGenerateFilename(Self, _Generation, Result);
end;

{ TDirectorySync }

//procedure TDirectorySync.doOnDifferentFileExists(const _Filename: string; var _Action: TFileExistsAction);
//begin
//  _Action := feaIgnore;
//  if Assigned(FOnDifferentFileExists) then
//    FOnDifferentFileExists(_Filename, _Action);
//end;

function TDirectorySync.doOnFileExists(const _SrcDir, _DstDir, _Filename: string): TFileExistsAction;
var
  src: TFileInfoRec;
  dst: TFileInfoRec;
begin
  Result := feaIgnore;
  if not Assigned(FOnFileExists) then
    Exit;
  if not TFileSystem.TryGetFileInfo(_SrcDir + _Filename, src) then
    Exit;
  if not TFileSystem.TryGetFileInfo(_DstDir + _Filename, dst) then
    Exit;

  FOnFileExists(Self, src, dst, Result);
end;

function TDirectorySync.doOnQueryFileSync(const _SrcFile, _DstFile: string): TQueryFileSyncAction;
var
  src: TFileInfoRec;
begin
  Result := fsaCopy;
  if not Assigned(FOnQueryFileSync) then
    Exit;

  if not TFileSystem.TryGetFileInfo(_SrcFile, src) then begin
    // File vanished
    Result := fsaSkip;
    Exit;
  end;
  FOnQueryFileSync(Self, src, _DstFile, Result);
end;

procedure TDirectorySync.doOnSyncingDir(const _SrcDir, _DstDir: string);
begin
  if Assigned(FOnSyncingDir) then
    FOnSyncingDir(Self, _SrcDir, _DstDir);
end;

procedure TDirectorySync.doOnSyncingFile(const _SrcFile, _DstFile: string; _Total, _Done: Int64);
begin
  if Assigned(FOnSyncingFile) then
    FOnSyncingFile(Self, _SrcFile, _DstFile, _Total, _Done);
end;

procedure TDirectorySync.ProgressStatusCallback(_Status: TCopyProgressStatus;
  var _Continue: TCopyProgressResult);
begin
  try
    doOnSyncingFile(FCurrentSource, FCurrentDest, _Status.TotalFileSize.QuadPart, _Status.TotalBytesTransferred.QuadPart);
  except
    on e: EAbort do
      _Continue := prCancel;
  end;
end;

procedure TDirectorySync.CheckOneWay(const _SrcDir, _DstDir: string);
var
  Filename: string;
  EnumA: TSimpleDirEnumerator;
  DstDirBS: string;
  SrcDirBs: string;
begin
  doOnSyncingDir(_SrcDir, _DstDir);
  SrcDirBs := itpd(_SrcDir);
  DstDirBS := itpd(_DstDir);
  EnumA := TSimpleDirEnumerator.Create(SrcDirBs + '*.*');
  try
    while EnumA.FindNext(Filename) do begin
      if (EnumA.Sr.Attr and SysUtils.faDirectory) <> 0 then begin
        CheckOneWay(SrcDirBs + Filename, DstDirBS + Filename);
      end else if FileExists(DstDirBS + Filename) then begin
        doOnFileExists(SrcDirBs, DstDirBS, Filename);
      end else begin
        doOnSyncingFile(SrcDirBs + Filename, DstDirBS + Filename, EnumA.Sr.Size, 0);
      end;
    end;
  finally
    FreeAndNil(EnumA);
  end;
end;

procedure TDirectorySync.SyncOneWay(const _SrcDir, _DstDir: string; _FlattenDirHierarchy: Boolean = False);
const
  TMPEXT = '.tmp';
var
  Filename: string;
  EnumA: TSimpleDirEnumerator;
  DstDirBS: string;
  SrcDirBs: string;
  Destination: string;
  TempFile: string;
begin
  doOnSyncingDir(_SrcDir, _DstDir);
  SrcDirBs := itpd(_SrcDir);
  DstDirBS := itpd(_DstDir);
  if not DirectoryExists(DstDirBS) then
    TFileSystem.ForceDir(DstDirBS);
  EnumA := TSimpleDirEnumerator.Create(SrcDirBs + '*.*');
  try
    while EnumA.FindNext(Filename) do begin
      FCurrentSource := SrcDirBs + Filename;
      if (EnumA.Sr.Attr and SysUtils.faDirectory) <> 0 then begin
        if _FlattenDirHierarchy then
          FCurrentDest := _DstDir
        else begin
          FCurrentDest := DstDirBS + Filename;
        end;
        SyncOneWay(FCurrentSource, FCurrentDest, _FlattenDirHierarchy);
      end else begin
        if not SameText(ExtractFileExt(Filename), TMPEXT) then begin // do not sync .tmp files
          Destination := DstDirBS + Filename;
          TempFile := Destination + TMPEXT;
          if FileExists(TempFile) then
            TFileSystem.DeleteFile(TempFile, False);

          if FileExists(Destination) then begin
            if doOnFileExists(SrcDirBs, DstDirBS, Filename) = feaOverwrite then begin
              TFileSystem.DeleteFile(Destination, False);
              // copy to temp
              FCurrentDest := TempFile;
              doOnSyncingFile(FCurrentSource, FCurrentDest, EnumA.Sr.Size, 0);
              if cfwOK <> TFileSystem.CopyFileWithProgress(FCurrentSource, FCurrentDest, ProgressStatusCallback, []) then
                SysUtils.Abort;

              // move to dest
              FCurrentSource := TempFile;
              FCurrentDest := Destination;
              doOnSyncingFile(FCurrentSource, FCurrentDest, EnumA.Sr.Size, 0);
              if cfwOK <> TFileSystem.MoveFileWithProgress(FCurrentSource, FCurrentDest, ProgressStatusCallback, []) then
                SysUtils.Abort;
            end;
          end else if doOnQueryFileSync(FCurrentSource, FCurrentDest) = fsaCopy then begin
            // copy to temp
            FCurrentDest := TempFile;
            doOnSyncingFile(FCurrentSource, FCurrentDest, EnumA.Sr.Size, 0);
            if cfwOK <> TFileSystem.CopyFileWithProgress(FCurrentSource, FCurrentDest, ProgressStatusCallback, []) then
              SysUtils.Abort;
            // move to dest
            FCurrentSource := TempFile;
            FCurrentDest := Destination;
            // todo: Should that really be displayed? Usually the move operation is so fast that you can't see this anyway.
            doOnSyncingFile(FCurrentSource, FCurrentDest, EnumA.Sr.Size, 0);
            if cfwOK <> TFileSystem.MoveFileWithProgress(FCurrentSource, FCurrentDest, ProgressStatusCallback, [mfwFailIfExists]) then
              SysUtils.Abort;
          end;
        end;
      end;
    end;
  finally
    FreeAndNil(EnumA);
  end;
end;

procedure TDirectorySync.SyncBothWays(const _DirA, _DirB: string);
begin
  SyncOneWay(_DirA, _DirB);
  SyncOneWay(_DirB, _DirA);
end;

{ TUniqueTempDir }

constructor TUniqueTempDir.Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
begin
  inherited Create;
  FPath := _Path;
  FDeleteOnlyIfEmpty := _DeleteOnlyIfEmpty;
end;

destructor TUniqueTempDir.Destroy;
begin
  // delete directory, fail silently on errors
  if FDeleteOnlyIfEmpty then
    TFileSystem.RemoveDir(FPath, False)
  else
    TFileSystem.DelDirTree(FPath, False);
  inherited;
end;

function TUniqueTempDir.Path: string;
begin
  Result := FPath;
end;

function TUniqueTempDir.PathBS: string;
begin
  Result := itpd(FPath);
end;

procedure TextFile_AssignAndRewrite(var _File: TextFile; const _Filename: string);
begin
  ZeroMemory(@_File, SizeOf(_File));
  AssignFile(_File, _Filename);
{$I-}
  Rewrite(_File);
{$I+}
  if IOResult <> 0 then begin
    ZeroMemory(@_File, SizeOf(_File));
    raise Exception.CreateFmt(_('Could not open file "%s" for writing.'), [_Filename]);
  end;
end;

function TextFile_IsOpen(const _File: TextFile): Boolean;
begin
  Result := (TTextRec(_File).Mode <> 0);
end;

procedure TextFile_Close(var _File: TextFile);
begin
  CloseFile(_File);
  TTextRec(_File).Mode := 0;
end;

{$IFDEF SUPPORTS_ENHANCED_RECORDS}
{ TFilename }

class operator TFilename.Add(const _a: TFilename; const _b: string): string;
begin
  Result := _a.Full + _b;
end;

class operator TFilename.Add(const _a: string; const _b: TFilename): string;
begin
  Result := _a + _b.Full;
end;

class operator TFilename.Implicit(const _s: string): TFilename;
begin
  Result.Init(_s);
end;

class operator TFilename.Implicit(_a: TFilename): string;
begin
  Result := _a.Full;
end;

procedure TFilename.Init(const _Full: string);
begin
  FFull := _Full;
end;

function TFilename.Drive: string;
begin
  Result := ExtractFileDrive(FFull);
end;

function TFilename.Directory: string;
begin
  Result := ExtractFileDir(FFull);
end;

function TFilename.DirectoryBS: string;
begin
  Result := Directory;
  if Result <> '' then
    Result := itpd(Directory);
end;

function TFilename.Filename: string;
begin
  Result := ExtractFileName(FFull);
end;

function TFilename.FilenameOnly: string;
begin
  Result := TFileSystem.RemoveFileExtFull(Filename);
end;

function TFilename.Extension: string;
begin
  Result := TFileSystem.ExtractFileExtFull(FFull);
end;

function TFilename.LastExtension: string;
begin
  Result := TFileSystem.ExtractFileExtLast(FFull);
end;

function TFilename.GetExtensions: TStringArray;
var
  Ext: string;
  p: Integer;
  len: Integer;
  i: Integer;
begin
  SetLength(Result, 0);

  Ext := Extension;
  len := 0;
  p := RPosStr('.', Ext);
  while p > 0 do begin
    Inc(len);
    SetLength(Result, len);
    for i := len - 1 downto 1 do
      Result[i] := Result[i - 1];
    Result[0] := TailStr(Ext, p);
    Ext := LeftStr(Ext, p - 1);
    p := RPosStr('.', Ext);
  end;
end;

function TFilename.GetParts(_sl: TStrings): Integer;
var
  s: string;
  p: Integer;
  Part: string;
begin
  Assert(Assigned(_sl), 'sl parameter not assigned');

  _sl.Clear;
  s := FFull;
  Result := 0;
  Part := '';
  while s <> '' do begin
    if Result = 0 then begin
      if StartsStr(PathDelim + PathDelim, s) then begin //FI:W510
        // UNC path
        p := PosEx(PathDelim, s, 3);
        if p > 0 then
          p := PosEx(PathDelim, s, p + 1);
        if p > 0 then begin
          _sl.Add(Copy(s, 1, p - 1));
          s := Copy(s, p + 1);
        end else begin
          _sl.Add(s);
          s := '';
        end;
      end else begin
        // no UNC, might be a drive, but could also be '\bla\blub' or 'bla\blub'
        _sl.Add(ExtractStr(s, PathDelim));
      end;
    end else begin
      _sl.Add(ExtractStr(s, PathDelim));
    end;
    Inc(Result);
  end;
  Assert(_sl.Count = Result);
end;

function TFilename.Split: TStringArray;

  procedure Append(const _s: string);
  var
    len: Integer;
  begin
    len := Length(Result);
    SetLength(Result, len + 1);
    Result[len] := _s;
  end;

var
  s: string;
  p: Integer;
  Part: string;
begin
  SetLength(Result, 0);
  s := FFull;
  Part := '';
  while s <> '' do begin
    if Length(Result) = 0 then begin
      if StartsStr(PathDelim + PathDelim, s) then begin //FI:W510
        // UNC path
        p := PosEx(PathDelim, s, 3);
        if p > 0 then
          p := PosEx(PathDelim, s, p + 1);
        if p > 0 then begin
          Append(Copy(s, 1, p - 1));
          s := Copy(s, p + 1);
        end else begin
          Append(s);
          s := '';
        end;
      end else begin
        // no UNC, might be a drive, but could also be '\bla\blub' or 'bla\blub'
        Append(ExtractStr(s, PathDelim));
      end;
    end else begin
      Append(ExtractStr(s, PathDelim));
    end;
  end;
end;

function TFilename.BaseName: string;
begin
  Result := TFileSystem.RemoveFileExtFull(FFull);
end;

class function TFilename.Combine(_Parts: TStringArray): TFilename;
var
  len: Integer;
  i: Integer;
  s: string;
begin
  len := Length(_Parts);
  if len = 0 then
    Result := ''
  else begin
    s := _Parts[0];
    for i := 1 to len - 1 do
      s := s + '\' + _Parts[i];
    Result := s;
  end;
end;

function TFilename.Depth: Integer;
begin
  Result := Length(Split);
end;

function TFilename.Parts(_Depth: Integer): string;
var
  sa: TStringArray;
  len: Integer;
  i: Integer;
begin
  sa := Split;
  len := Length(sa);
  if _Depth < 0 then
    _Depth := len + _Depth;
  if _Depth < 0 then begin
    Result := '';
    Exit; //==>
  end;
  Result := '';
  if _Depth > len then
    _Depth := len;
  for i := 0 to _Depth - 1 do begin
    if Result <> '' then
      Result := Result + PathDelim + sa[i]
    else
      Result := sa[i];
  end;
end;

procedure TFilename.ReplaceDrive(const _NewDrive: string);
var
  drv: string;
begin
  drv := ExtractFileDrive(FFull);
  if drv = '' then
    raise Exception.CreateFmt(_('Path %s does not contain a drive, cannot replace it.'), [FFull]);
  FFull := _NewDrive + TailStr(FFull, Length(drv) + 1);
end;

procedure TFilename.ReplaceDirectory(const _NewDir: string);
var
  s: string;
begin
  s := _NewDir;
  if s <> '' then
    s := itpd(s);
  FFull := s + Filename;
end;

procedure TFilename.ReplaceExtension(const _Extension: string);
begin
  FFull := TFileSystem.ChangeFileExtFull(FFull, _Extension);
end;

procedure TFilename.ReplaceExtensions(const _Extensions: TStringArray);
var
  i: Integer;
  Ext: string;
begin
  Ext := '';
  for i := 0 to Length(_Extensions) - 1 do begin
    Ext := Ext + _Extensions[i];
  end;
  ReplaceExtension(Ext);
end;

procedure TFilename.ReplaceFilename(const _Filename: string);
begin
  FFull := DirectoryBS + _Filename;
end;

procedure TFilename.ReplaceFilenameOnly(const _FilenameOnly: string);
begin
  FFull := DirectoryBS + _FilenameOnly + Extension;
end;

procedure TFilename.ReplaceLastExtension(const _Extension: string);
begin
  FFull := TFileSystem.ChangeFileExtLast(FFull, _Extension);
end;

function TFilename.TryGetRootDir(out _Root: string): Boolean;
begin
  Result := Length(FFull) >= 3;
  if Result and StartsStr(PathDelim + PathDelim, FFull) then begin //FI:W510
    Result := True;
    _Root := Parts(1);
  end else if Result and CharInSet(UpCase(FFull[1]), ['A'..'Z']) and (FFull[2] = ':') and (FFull[3] = PathDelim) then begin
    Result := True;
    _Root := itpd(Parts(1));
  end else
    Result := False;
end;

{ TSearchPath }

class operator TSearchPath.Implicit(const _s: string): TSearchPath;
begin
  Result.Init(_s);
end;

class operator TSearchPath.Implicit(_a: TSearchPath): string;
begin
  Result := _a.Value;
end;

class operator TSearchPath.Implicit(_sl: TStrings): TSearchPath;
begin
  Result.AssignParts(_sl);
end;

class operator TSearchPath.Add(_a, _b: TSearchPath): TSearchPath;
var
  sla: TStringList;
  slb: TStringList;
begin
  slb := nil;
  sla := _a.AsStringlist;
  try
    slb := _b.AsStringlist;
    sla.AddStrings(slb);
    Result.FValue := sla.DelimitedText;
  finally
    FreeAndNil(slb);
    FreeAndNil(sla);
  end;
end;

class operator TSearchPath.Add(_a: TSearchPath; const _b: string): TSearchPath;
var
  sl: TStringList;
begin
  sl := _a.AsStringlist;
  try
    sl.Add(_b);
    Result.FValue := sl.DelimitedText;
  finally
    FreeAndNil(sl);
  end;
end;

function TSearchPath.AnyDirExists: Boolean;
var
  sl: TStringList;
  i: Integer;
begin
  Result := True;
  sl := AsStringlist;
  try
    for i := 0 to sl.Count - 1 do begin
      if sl[i] <> '' then
        if TFileSystem.DirExists(sl[i]) then
          Exit; //==>
    end;
  finally
    FreeAndNil(sl);
  end;
  Result := False;
end;

function TSearchPath.AllDirsExist(out _FirstNonExistingDir: string): Boolean;
var
  i: Integer;
  sl: TStringList;
begin
  sl := AsStringlist;
  try
    for i := 0 to sl.Count - 1 do begin
      if sl[i] <> '' then
        if not TFileSystem.DirExists(sl[i]) then begin
          Result := False;
          _FirstNonExistingDir := sl[i];
          Exit; //=>
        end;
    end;
  finally
    FreeAndNil(sl);
  end;
  Result := True;
end;

procedure TSearchPath.AssignParts(_Parts: TStrings);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    sl.StrictDelimiter := True;
    sl.QuoteChar := '"';
    sl.Delimiter := ';';
    sl.AddStrings(_Parts);
    FValue := sl.DelimitedText;
  finally
    FreeAndNil(sl);
  end;
end;

function TSearchPath.AsStringlist: TStringList;
begin
  Result := TStringList.Create;
  Result.StrictDelimiter := True;
  Result.QuoteChar := '"';
  Result.Delimiter := ';';
  Result.DelimitedText := Value;
end;

class operator TSearchPath.Equal(_a, _b: TSearchPath): Boolean;
begin
  Result := _a.Value = _b.Value;
end;

class operator TSearchPath.NotEqual(_a, _b: TSearchPath): Boolean;
begin
  Result := not (_a = _b);
end;

function TSearchPath.Part(_Idx: Integer): string;
var
  sl: TStringList;
begin
  sl := AsStringlist;
  try
    Result := sl[_Idx];
  finally
    FreeAndNil(sl);
  end;
end;

function TSearchPath.PartCount: Integer;
var
  sl: TStringList;
begin
  sl := AsStringlist;
  try
    Result := sl.Count;
  finally
    FreeAndNil(sl);
  end;
end;

function TSearchPath.TryFindFile(const _FilenameOnly: string; out _FoundFile: TFilename): Boolean;
var
  sl: TStringList;
  i: Integer;
  fn: TFilename;
  dir: string;
begin
  fn := _FilenameOnly;
  sl := AsStringlist;
  try
    for i := 0 to sl.Count - 1 do begin
      dir := sl[i];
      if dir <> '' then begin
        fn.ReplaceDirectory(dir);
        Result := TFileSystem.FileExists(fn);
        if Result then begin
          _FoundFile := fn;
          Exit;
        end;
      end;
    end;
  finally
    FreeAndNil(sl);
  end;
  Result := False;
end;

function TSearchPath.FindFile(const _FilenameOnly: string): TFilename;
begin
  if not TryFindFile(_FilenameOnly, Result) then
    raise EFileNotFound.CreateFmt(_('Could not find file "%s" in search path "%s".'),
      [_FilenameOnly, Value]);
end;

procedure TSearchPath.GetParts(_Parts: TStrings);
var
  sl: TStringList;
begin
  sl := AsStringlist;
  try
    _Parts.Assign(sl);
  finally
    FreeAndNil(sl);
  end;
end;

function TSearchPath.GetParts: TStringArray;
var
  sl: TStringList;
begin
  sl := AsStringlist;
  try
    Result := TStringArray_FromStrings(sl);
  finally
    FreeAndNil(sl);
  end;
end;

procedure TSearchPath.Init(const _s: string);
begin
  FValue := _s;
end;

function TSearchPath.Value: string;
begin
  Result := FValue;
end;
{$ENDIF}

initialization
  // according to MSDN:
  // Best practice is that all applications call the process-wide SetErrorMode function
  // with a parameter of SEM_FAILCRITICALERRORS at startup. This is to prevent error
  // mode dialogs from hanging the application.
  SetErrorMode(SEM_FAILCRITICALERRORS);
end.
Show on old repository browser