Dxbx/Libraries/Pascal/Jcl/Commen/JclFileUtils.pas

6842 lines
209 KiB
ObjectPascal

{**************************************************************************************************}
{ }
{ Project JEDI Code Library (JCL) }
{ }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
{ License at http://www.mozilla.org/MPL/ }
{ }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
{ and limitations under the License. }
{ }
{ The Original Code is JclFileUtils.pas. }
{ }
{ The Initial Developer of the Original Code is Marcel van Brakel. }
{ Portions created by Marcel van Brakel are Copyright (C) Marcel van Brakel. All rights reserved. }
{ }
{ Contributors: }
{ Andre Snepvangers (asnepvangers) }
{ Andreas Hausladen (ahuser) }
{ Anthony Steele }
{ Rik Barker (rikbarker) }
{ Azret Botash }
{ Charlie Calvert }
{ David Hervieux }
{ Florent Ouchet (outchy) }
{ Jean-Fabien Connault (cycocrew) }
{ Jens Fudickar (jfudickar) }
{ JohnML }
{ John Molyneux }
{ Marcel Bestebroer }
{ Marcel van Brakel }
{ Massimo Maria Ghisalberti }
{ Matthias Thoma (mthoma) }
{ Olivier Sannier (obones) }
{ Pelle F. S. Liljendal }
{ Robert Marquardt (marquardt) }
{ Robert Rossmair (rrossmair) }
{ Rudy Velthuis }
{ Scott Price }
{ Wim De Cleen }
{ }
{**************************************************************************************************}
{ }
{ This unit contains routines and classes for working with files, directories and path strings. }
{ Additionally it contains wrapper classes for file mapping objects and version resources. }
{ Generically speaking, everything that has to do with files and directories. Note that filesystem }
{ specific functionality has been extracted into external units, for example JclNTFS which }
{ contains NTFS specific utility routines, and that the JclShell unit contains some file related }
{ routines as well but they are specific to the Windows shell. }
{ }
{**************************************************************************************************}
{ }
{ Last modified: $Date:: 2009-10-11 18:54:42 +0200 (zo, 11 okt 2009) $ }
{ Revision: $Rev:: 3043 $ }
{ Author: $Author:: twm $ }
{ }
{**************************************************************************************************}
unit JclFileUtils;
{$I jcl.inc}
{$I crossplatform.inc}
interface
uses
{$IFDEF UNITVERSIONING}
JclUnitVersioning,
{$ENDIF UNITVERSIONING}
{$IFDEF HAS_UNIT_LIBC}
Libc,
{$ENDIF HAS_UNIT_LIBC}
{$IFDEF MSWINDOWS}
Windows, JclWin32,
{$ENDIF MSWINDOWS}
Classes, SysUtils,
JclBase;
// Path Manipulation
//
// Various support routines for working with path strings. For example, building a path from
// elements or extracting the elements from a path, interpretation of paths and transformations of
// paths.
const
{$IFDEF UNIX}
{$IFDEF KEEP_DEPRECATED}
PathSeparator = '/';
{$ENDIF KEEP_DEPRECATED}
DirDelimiter = '/';
DirSeparator = ':';
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
PathDevicePrefix = '\\.\';
{$IFDEF KEEP_DEPRECATED}
PathSeparator = '\';
{$ENDIF KEEP_DEPRECATED}
DirDelimiter = '\';
DirSeparator = ';';
PathUncPrefix = '\\';
{$ENDIF MSWINDOWS}
faSymLink = $00000040 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF}; // defined since D7
faNormalFile = $00000080;
faTemporary = $00000100 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faSparseFile = $00000200 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faReparsePoint = $00000400 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faCompressed = $00000800 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faOffline = $00001000 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faNotContentIndexed = $00002000 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
faEncrypted = $00004000 {$IFDEF SUPPORTS_PLATFORM} platform {$ENDIF};
// Note: faVolumeID is potentially dangerous and its usage has been discontinued
// Please see QC report 6003 for details, available online at this URL:
// http://qc.embarcadero.com/wc/qcmain.aspx?d=6003
faRejectedByDefault = faHidden + faSysFile + faDirectory;
faWindowsSpecific = faArchive + faTemporary + faSparseFile + faReparsePoint +
faCompressed + faOffline + faNotContentIndexed + faEncrypted;
faUnixSpecific = faSymLink;
type
TCompactPath = ({cpBegin, }cpCenter, cpEnd);
function CharIsDriveLetter(const C: char): Boolean;
function PathAddSeparator(const Path: string): string;
function PathAddExtension(const Path, Extension: string): string;
function PathAppend(const Path, Append: string): string;
function PathBuildRoot(const Drive: Byte): string;
function PathCanonicalize(const Path: string): string;
function PathCommonPrefix(const Path1, Path2: string): Integer;
{$IFDEF MSWINDOWS}
function PathCompactPath(const DC: HDC; const Path: string; const Width: Integer;
CmpFmt: TCompactPath): string;
{$ENDIF MSWINDOWS}
procedure PathExtractElements(const Source: string; var Drive, Path, FileName, Ext: string);
function PathExtractFileDirFixed(const S: string): string;
function PathExtractFileNameNoExt(const Path: string): string;
function PathExtractPathDepth(const Path: string; Depth: Integer): string;
function PathGetDepth(const Path: string): Integer;
{$IFDEF MSWINDOWS}
function PathGetLongName(const Path: string): string;
function PathGetShortName(const Path: string): string;
{$ENDIF MSWINDOWS}
function PathGetRelativePath(Origin, Destination: string): string;
function PathGetTempPath: string;
function PathIsAbsolute(const Path: string): Boolean;
function PathIsChild(const Path, Base: string): Boolean;
function PathIsDiskDevice(const Path: string): Boolean;
function PathIsUNC(const Path: string): Boolean;
function PathRemoveSeparator(const Path: string): string;
function PathRemoveExtension(const Path: string): string;
// Windows Vista uses localized path names in the Windows Explorer but these
// folders do not really exist on disk. This causes all I/O operations to fail
// if the user specifies such a localized directory like "C:\Benutzer\MyName\Bilder"
// instead of the physical folder "C:\Users\MyName\Pictures".
// These two functions allow to convert the user's input from localized to
// physical paths and vice versa.
function PathGetPhysicalPath(const LocalizedPath: string): string;
function PathGetLocalizedPath(const PhysicalPath: string): string;
// Files and Directories
//
// Routines for working with files and directories. Includes routines to extract various file
// attributes or update them, volume locking and routines for creating temporary files.
type
TDelTreeProgress = function (const FileName: string; Attr: DWORD): Boolean;
TFileListOption = (flFullNames, flRecursive, flMaskedSubfolders);
TFileListOptions = set of TFileListOption;
TJclAttributeMatch = (amAny, amExact, amSubSetOf, amSuperSetOf, amCustom);
TFileMatchFunc = function(const Attr: Integer; const FileInfo: TSearchRec): Boolean;
TFileHandler = procedure (const FileName: string) of object;
TFileHandlerEx = procedure (const Directory: string; const FileInfo: TSearchRec) of object;
TFileInfoHandlerEx = procedure (const FileInfo: TSearchRec) of object;
function BuildFileList(const Path: string; const Attr: Integer; const List: TStrings): Boolean;
function AdvBuildFileList(const Path: string; const Attr: Integer; const Files: TStrings;
const AttributeMatch: TJclAttributeMatch = amSuperSetOf; const Options: TFileListOptions = [];
const SubfoldersMask: string = ''; const FileMatchFunc: TFileMatchFunc = nil): Boolean;
function VerifyFileAttributeMask(var RejectedAttributes, RequiredAttributes: Integer): Boolean;
function IsFileAttributeMatch(FileAttributes, RejectedAttributes,
RequiredAttributes: Integer): Boolean;
function FileAttributesStr(const FileInfo: TSearchRec): string;
function IsFileNameMatch(FileName: string; const Mask: string;
const CaseSensitive: Boolean = {$IFDEF MSWINDOWS} False {$ELSE} True {$ENDIF}): Boolean;
procedure EnumFiles(const Path: string; HandleFile: TFileHandlerEx;
RejectedAttributes: Integer = faRejectedByDefault; RequiredAttributes: Integer = 0;
Abort: PBoolean = nil); overload;
procedure EnumFiles(const Path: string; HandleFile: TFileInfoHandlerEx;
RejectedAttributes: Integer = faRejectedByDefault; RequiredAttributes: Integer = 0;
Abort: PBoolean = nil); overload;
procedure EnumDirectories(const Root: string; const HandleDirectory: TFileHandler;
const IncludeHiddenDirectories: Boolean = False; const SubDirectoriesMask: string = '';
Abort: PBoolean = nil {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF});
{$IFDEF MSWINDOWS}
procedure CreateEmptyFile(const FileName: string);
function CloseVolume(var Volume: THandle): Boolean;
{$IFNDEF FPC}
function DeleteDirectory(const DirectoryName: string; MoveToRecycleBin: Boolean): Boolean;
function CopyDirectory(ExistingDirectoryName, NewDirectoryName: string): Boolean;
function MoveDirectory(ExistingDirectoryName, NewDirectoryName: string): Boolean;
{$ENDIF ~FPC}
function DelTree(const Path: string): Boolean;
function DelTreeEx(const Path: string; AbortOnFailure: Boolean; Progress: TDelTreeProgress): Boolean;
function DiskInDrive(Drive: Char): Boolean;
{$ENDIF MSWINDOWS}
function DirectoryExists(const Name: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): Boolean;
function FileCreateTemp(var Prefix: string): THandle;
function FileBackup(const FileName: string; Move: Boolean = False): Boolean;
function FileCopy(const ExistingFileName, NewFileName: string; ReplaceExisting: Boolean = False): Boolean;
function FileDelete(const FileName: string; MoveToRecycleBin: Boolean = False): Boolean;
function FileExists(const FileName: string): Boolean;
function FileMove(const ExistingFileName, NewFileName: string; ReplaceExisting: Boolean = False): Boolean;
function FileRestore(const FileName: string): Boolean;
function GetBackupFileName(const FileName: string): string;
function IsBackupFileName(const FileName: string): Boolean;
function FileGetDisplayName(const FileName: string): string;
function FileGetGroupName(const FileName: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): string;
function FileGetOwnerName(const FileName: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): string;
function FileGetSize(const FileName: string): Int64;
function FileGetTempName(const Prefix: string): string;
{$IFDEF MSWINDOWS}
function FileGetTypeName(const FileName: string): string;
{$ENDIF MSWINDOWS}
function FindUnusedFileName(FileName: string; const FileExt: string; NumberPrefix: string = ''): string;
function ForceDirectories(Name: string): Boolean;
function GetDirectorySize(const Path: string): Int64;
{$IFDEF MSWINDOWS}
function GetDriveTypeStr(const Drive: Char): string;
function GetFileAgeCoherence(const FileName: string): Boolean;
{$ENDIF MSWINDOWS}
procedure GetFileAttributeList(const Items: TStrings; const Attr: Integer);
{$IFDEF MSWINDOWS}
procedure GetFileAttributeListEx(const Items: TStrings; const Attr: Integer);
{$ENDIF MSWINDOWS}
function GetFileInformation(const FileName: string; out FileInfo: TSearchRec): Boolean; overload;
function GetFileInformation(const FileName: string): TSearchRec; overload;
{$IFDEF UNIX}
function GetFileStatus(const FileName: string; out StatBuf: TStatBuf64;
const ResolveSymLinks: Boolean): Integer;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function GetFileLastWrite(const FileName: string): TFileTime; overload;
function GetFileLastWrite(const FileName: string; out LocalTime: TDateTime): Boolean; overload;
function GetFileLastAccess(const FileName: string): TFileTime; overload;
function GetFileLastAccess(const FileName: string; out LocalTime: TDateTime): Boolean; overload;
function GetFileCreation(const FileName: string): TFileTime; overload;
function GetFileCreation(const FileName: string; out LocalTime: TDateTime): Boolean; overload;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function GetFileLastWrite(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastWrite(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastWrite(const FileName: string; ResolveSymLinks: Boolean = True): Integer; overload;
function GetFileLastAccess(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastAccess(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastAccess(const FileName: string; ResolveSymLinks: Boolean = True): Integer; overload;
function GetFileLastAttrChange(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastAttrChange(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean = True): Boolean; overload;
function GetFileLastAttrChange(const FileName: string; ResolveSymLinks: Boolean = True): Integer; overload;
{$ENDIF UNIX}
function GetModulePath(const Module: HMODULE): string;
function GetSizeOfFile(const FileName: string): Int64; overload;
function GetSizeOfFile(const FileInfo: TSearchRec): Int64; overload;
{$IFDEF MSWINDOWS}
function GetSizeOfFile(Handle: THandle): Int64; overload;
function GetStandardFileInfo(const FileName: string): TWin32FileAttributeData;
{$ENDIF MSWINDOWS}
function IsDirectory(const FileName: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): Boolean;
function IsRootDirectory(const CanonicFileName: string): Boolean;
{$IFDEF MSWINDOWS}
function LockVolume(const Volume: string; var Handle: THandle): Boolean;
function OpenVolume(const Drive: Char): THandle;
function SetDirLastWrite(const DirName: string; const DateTime: TDateTime): Boolean;
function SetDirLastAccess(const DirName: string; const DateTime: TDateTime): Boolean;
function SetDirCreation(const DirName: string; const DateTime: TDateTime): Boolean;
{$ENDIF MSWINDOWS}
function SetFileLastWrite(const FileName: string; const DateTime: TDateTime): Boolean;
function SetFileLastAccess(const FileName: string; const DateTime: TDateTime): Boolean;
{$IFDEF MSWINDOWS}
function SetFileCreation(const FileName: string; const DateTime: TDateTime): Boolean;
procedure ShredFile(const FileName: string; Times: Integer = 1);
function UnlockVolume(var Handle: THandle): Boolean;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function CreateSymbolicLink(const Name, Target: string): Boolean;
{ This function gets the value of the symbolic link filename. }
function SymbolicLinkTarget(const Name: string): string;
{$ENDIF UNIX}
// TJclFileAttributeMask
//
// File search helper class, allows to specify required/rejected attributes
type
TAttributeInterest = (aiIgnored, aiRejected, aiRequired);
TJclCustomFileAttrMask = class(TPersistent)
private
FRequiredAttr: Integer;
FRejectedAttr: Integer;
function GetAttr(Index: Integer): TAttributeInterest;
procedure SetAttr(Index: Integer; const Value: TAttributeInterest);
procedure ReadRequiredAttributes(Reader: TReader);
procedure ReadRejectedAttributes(Reader: TReader);
procedure WriteRequiredAttributes(Writer: TWriter);
procedure WriteRejectedAttributes(Writer: TWriter);
protected
procedure DefineProperties(Filer: TFiler); override;
property ReadOnly: TAttributeInterest index faReadOnly
read GetAttr write SetAttr stored False;
property Hidden: TAttributeInterest index faHidden
read GetAttr write SetAttr stored False;
property System: TAttributeInterest index faSysFile
read GetAttr write SetAttr stored False;
property Directory: TAttributeInterest index faDirectory
read GetAttr write SetAttr stored False;
property SymLink: TAttributeInterest index faSymLink
read GetAttr write SetAttr stored False;
property Normal: TAttributeInterest index faNormalFile
read GetAttr write SetAttr stored False;
property Archive: TAttributeInterest index faArchive
read GetAttr write SetAttr stored False;
property Temporary: TAttributeInterest index faTemporary
read GetAttr write SetAttr stored False;
property SparseFile: TAttributeInterest index faSparseFile
read GetAttr write SetAttr stored False;
property ReparsePoint: TAttributeInterest index faReparsePoint
read GetAttr write SetAttr stored False;
property Compressed: TAttributeInterest index faCompressed
read GetAttr write SetAttr stored False;
property OffLine: TAttributeInterest index faOffline
read GetAttr write SetAttr stored False;
property NotContentIndexed: TAttributeInterest index faNotContentIndexed
read GetAttr write SetAttr stored False;
property Encrypted: TAttributeInterest index faEncrypted
read GetAttr write SetAttr stored False;
public
constructor Create;
procedure Assign(Source: TPersistent); override;
procedure Clear;
function Match(FileAttributes: Integer): Boolean; overload;
function Match(const FileInfo: TSearchRec): Boolean; overload;
property Required: Integer read FRequiredAttr write FRequiredAttr;
property Rejected: Integer read FRejectedAttr write FRejectedAttr;
property Attribute[Index: Integer]: TAttributeInterest read GetAttr write SetAttr; default;
end;
TJclFileAttributeMask = class(TJclCustomFileAttrMask)
private
procedure ReadVolumeID(Reader: TReader);
protected
procedure DefineProperties(Filer: TFiler); override;
published
property ReadOnly;
property Hidden;
property System;
property Directory;
property Normal;
{$IFDEF UNIX}
property SymLink;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
property Archive;
property Temporary;
property SparseFile;
property ReparsePoint;
property Compressed;
property OffLine;
property NotContentIndexed;
property Encrypted;
{$ENDIF MSWINDOWS}
end;
// IJclFileEnumerator / TJclFileEnumerator
//
// Interface / class for thread-based file search
type
TFileSearchOption = (fsIncludeSubDirectories, fsIncludeHiddenSubDirectories, fsLastChangeAfter,
fsLastChangeBefore, fsMaxSize, fsMinSize);
TFileSearchOptions = set of TFileSearchOption;
TFileSearchTaskID = Integer;
TFileSearchTerminationEvent = procedure (const ID: TFileSearchTaskID; const Aborted: Boolean) of object;
TFileEnumeratorSyncMode = (smPerFile, smPerDirectory);
IJclFileEnumerator = interface
['{F7E747ED-1C41-441F-B25B-BB314E00C4E9}']
// property access methods
function GetAttributeMask: TJclFileAttributeMask;
function GetCaseSensitiveSearch: Boolean;
function GetRootDirectories: TStrings;
function GetRootDirectory: string;
function GetFileMask: string;
function GetFileMasks: TStrings;
function GetFileSizeMax: Int64;
function GetFileSizeMin: Int64;
function GetIncludeSubDirectories: Boolean;
function GetIncludeHiddenSubDirectories: Boolean;
function GetLastChangeAfter: TDateTime;
function GetLastChangeBefore: TDateTime;
function GetLastChangeAfterStr: string;
function GetLastChangeBeforeStr: string;
function GetRunningTasks: Integer;
function GetSubDirectoryMask: string;
function GetSynchronizationMode: TFileEnumeratorSyncMode;
function GetOnEnterDirectory: TFileHandler;
function GetOnTerminateTask: TFileSearchTerminationEvent;
function GetOption(const Option: TFileSearchOption): Boolean;
function GetOptions: TFileSearchoptions;
procedure SetAttributeMask(const Value: TJclFileAttributeMask);
procedure SetCaseSensitiveSearch(const Value: Boolean);
procedure SetRootDirectories(const Value: TStrings);
procedure SetRootDirectory(const Value: string);
procedure SetFileMask(const Value: string);
procedure SetFileMasks(const Value: TStrings);
procedure SetFileSizeMax(const Value: Int64);
procedure SetFileSizeMin(const Value: Int64);
procedure SetIncludeSubDirectories(const Value: Boolean);
procedure SetIncludeHiddenSubDirectories(const Value: Boolean);
procedure SetLastChangeAfter(const Value: TDateTime);
procedure SetLastChangeBefore(const Value: TDateTime);
procedure SetLastChangeAfterStr(const Value: string);
procedure SetLastChangeBeforeStr(const Value: string);
procedure SetOption(const Option: TFileSearchOption; const Value: Boolean);
procedure SetOptions(const Value: TFileSearchOptions);
procedure SetSubDirectoryMask(const Value: string);
procedure SetSynchronizationMode(const Value: TFileEnumeratorSyncMode);
procedure SetOnEnterDirectory(const Value: TFileHandler);
procedure SetOnTerminateTask(const Value: TFileSearchTerminationEvent);
// other methods
function FillList(List: TStrings): TFileSearchTaskID;
function ForEach(Handler: TFileHandler): TFileSearchTaskID; overload;
function ForEach(Handler: TFileHandlerEx): TFileSearchTaskID; overload;
procedure StopTask(ID: TFileSearchTaskID);
procedure StopAllTasks(Silently: Boolean = False); // Silently: Don't call OnTerminateTask
// properties
property CaseSensitiveSearch: Boolean read GetCaseSensitiveSearch write SetCaseSensitiveSearch;
property RootDirectories: TStrings read GetRootDirectories write SetRootDirectories;
property RootDirectory: string read GetRootDirectory write SetRootDirectory;
property FileMask: string read GetFileMask write SetFileMask;
property SubDirectoryMask: string read GetSubDirectoryMask write SetSubDirectoryMask;
property AttributeMask: TJclFileAttributeMask read GetAttributeMask write SetAttributeMask;
property FileSizeMin: Int64 read GetFileSizeMin write SetFileSizeMin;
property FileSizeMax: Int64 read GetFileSizeMax write SetFileSizeMax; // default InvalidFileSize;
property LastChangeAfter: TDateTime read GetLastChangeAfter write SetLastChangeAfter;
property LastChangeBefore: TDateTime read GetLastChangeBefore write SetLastChangeBefore;
property LastChangeAfterAsString: string read GetLastChangeAfterStr write SetLastChangeAfterStr;
property LastChangeBeforeAsString: string read GetLastChangeBeforeStr write SetLastChangeBeforeStr;
property IncludeSubDirectories: Boolean read GetIncludeSubDirectories
write SetIncludeSubDirectories;
property IncludeHiddenSubDirectories: Boolean read GetIncludeHiddenSubDirectories
write SetIncludeHiddenSubDirectories;
property RunningTasks: Integer read GetRunningTasks;
property SynchronizationMode: TFileEnumeratorSyncMode read GetSynchronizationMode
write SetSynchronizationMode;
property OnEnterDirectory: TFileHandler read GetOnEnterDirectory write SetOnEnterDirectory;
property OnTerminateTask: TFileSearchTerminationEvent read GetOnTerminateTask
write SetOnTerminateTask;
end;
TJclFileEnumerator = class(TPersistent, IJclFileEnumerator)
private
FOwnerInterface: IInterface;
FTasks: TList;
FFileMasks: TStringList;
FRootDirectories: TStringList;
FSubDirectoryMask: string;
FOnEnterDirectory: TFileHandler;
FOnTerminateTask: TFileSearchTerminationEvent;
FNextTaskID: TFileSearchTaskID;
FAttributeMask: TJclFileAttributeMask;
FSynchronizationMode: TFileEnumeratorSyncMode;
FFileSizeMin: Int64;
FFileSizeMax: Int64;
FLastChangeBefore: TDateTime;
FLastChangeAfter: TDateTime;
FOptions: TFileSearchOptions;
FCaseSensitiveSearch: Boolean;
function IsLastChangeAfterStored: Boolean;
function IsLastChangeBeforeStored: Boolean;
function GetNextTaskID: TFileSearchTaskID;
function GetCaseSensitiveSearch: Boolean;
procedure SetCaseSensitiveSearch(const Value: Boolean);
protected
FRefCount: Integer;
function CreateTask: TThread;
procedure TaskTerminated(Sender: TObject);
property NextTaskID: TFileSearchTaskID read GetNextTaskID;
public
constructor Create;
destructor Destroy; override;
{ IInterface }
function QueryInterface(const IID: TGUID; out Obj): HRESULT; virtual; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{ IJclFileEnumerator }
function GetAttributeMask: TJclFileAttributeMask;
function GetRootDirectories: TStrings;
function GetRootDirectory: string;
function GetFileMask: string;
function GetFileMasks: TStrings;
function GetFileSizeMax: Int64;
function GetFileSizeMin: Int64;
function GetIncludeSubDirectories: Boolean;
function GetIncludeHiddenSubDirectories: Boolean;
function GetLastChangeAfter: TDateTime;
function GetLastChangeBefore: TDateTime;
function GetLastChangeAfterStr: string;
function GetLastChangeBeforeStr: string;
function GetOption(const Option: TFileSearchOption): Boolean;
function GetOptions: TFileSearchoptions;
function GetRunningTasks: Integer;
function GetSubDirectoryMask: string;
function GetSynchronizationMode: TFileEnumeratorSyncMode;
function GetOnEnterDirectory: TFileHandler;
function GetOnTerminateTask: TFileSearchTerminationEvent;
procedure SetAttributeMask(const Value: TJclFileAttributeMask);
procedure SetRootDirectories(const Value: TStrings);
procedure SetRootDirectory(const Value: string);
procedure SetFileMask(const Value: string);
procedure SetFileMasks(const Value: TStrings);
procedure SetFileSizeMax(const Value: Int64);
procedure SetFileSizeMin(const Value: Int64);
procedure SetIncludeSubDirectories(const Value: Boolean);
procedure SetIncludeHiddenSubDirectories(const Value: Boolean);
procedure SetLastChangeAfter(const Value: TDateTime);
procedure SetLastChangeBefore(const Value: TDateTime);
procedure SetLastChangeAfterStr(const Value: string);
procedure SetLastChangeBeforeStr(const Value: string);
procedure SetOption(const Option: TFileSearchOption; const Value: Boolean);
procedure SetOptions(const Value: TFileSearchOptions);
procedure SetSubDirectoryMask(const Value: string);
procedure SetSynchronizationMode(const Value: TFileEnumeratorSyncMode);
procedure SetOnEnterDirectory(const Value: TFileHandler);
procedure SetOnTerminateTask(const Value: TFileSearchTerminationEvent);
procedure AfterConstruction; override;
procedure Assign(Source: TPersistent); override;
function FillList(List: TStrings): TFileSearchTaskID;
function ForEach(Handler: TFileHandler): TFileSearchTaskID; overload;
function ForEach(Handler: TFileHandlerEx): TFileSearchTaskID; overload;
procedure StopTask(ID: TFileSearchTaskID);
procedure StopAllTasks(Silently: Boolean = False); // Silently: Don't call OnTerminateTask
property FileMask: string read GetFileMask write SetFileMask;
property IncludeSubDirectories: Boolean
read GetIncludeSubDirectories write SetIncludeSubDirectories;
property IncludeHiddenSubDirectories: Boolean
read GetIncludeHiddenSubDirectories write SetIncludeHiddenSubDirectories;
property SearchOption[const Option: TFileSearchOption]: Boolean read GetOption write SetOption;
property LastChangeAfterAsString: string read GetLastChangeAfterStr write SetLastChangeAfterStr;
property LastChangeBeforeAsString: string read GetLastChangeBeforeStr write SetLastChangeBeforeStr;
published
property CaseSensitiveSearch: Boolean read GetCaseSensitiveSearch write SetCaseSensitiveSearch
default {$IFDEF MSWINDOWS} False {$ELSE} True {$ENDIF};
property FileMasks: TStrings read GetFileMasks write SetFileMasks;
property RootDirectories: TStrings read GetRootDirectories write SetRootDirectories;
property RootDirectory: string read GetRootDirectory write SetRootDirectory;
property SubDirectoryMask: string read FSubDirectoryMask write FSubDirectoryMask;
property AttributeMask: TJclFileAttributeMask read FAttributeMask write SetAttributeMask;
property FileSizeMin: Int64 read FFileSizeMin write FFileSizeMin;
property FileSizeMax: Int64 read FFileSizeMax write FFileSizeMax;
property LastChangeAfter: TDateTime read FLastChangeAfter write FLastChangeAfter
stored IsLastChangeAfterStored;
property LastChangeBefore: TDateTime read FLastChangeBefore write FLastChangeBefore
stored IsLastChangeBeforeStored;
property Options: TFileSearchOptions read FOptions write FOptions
default [fsIncludeSubDirectories];
property RunningTasks: Integer read GetRunningTasks;
property SynchronizationMode: TFileEnumeratorSyncMode read FSynchronizationMode write FSynchronizationMode
default smPerDirectory;
property OnEnterDirectory: TFileHandler read FOnEnterDirectory write FOnEnterDirectory;
property OnTerminateTask: TFileSearchTerminationEvent read FOnTerminateTask write FOnTerminateTask;
end;
function FileSearch: IJclFileEnumerator;
{$IFDEF MSWINDOWS}
// TFileVersionInfo
//
// Class that enables reading the version information stored in a PE file.
type
TFileFlag = (ffDebug, ffInfoInferred, ffPatched, ffPreRelease, ffPrivateBuild, ffSpecialBuild);
TFileFlags = set of TFileFlag;
PLangIdRec = ^TLangIdRec;
TLangIdRec = packed record
case Integer of
0: (
LangId: Word;
CodePage: Word);
1: (
Pair: DWORD);
end;
EJclFileVersionInfoError = class(EJclError);
TJclFileVersionInfo = class(TObject)
private
FBuffer: AnsiString;
FFixedInfo: PVSFixedFileInfo;
FFileFlags: TFileFlags;
FItemList: TStringList;
FItems: TStringList;
FLanguages: array of TLangIdRec;
FLanguageIndex: Integer;
FTranslations: array of TLangIdRec;
function GetFixedInfo: TVSFixedFileInfo;
function GetItems: TStrings;
function GetLanguageCount: Integer;
function GetLanguageIds(Index: Integer): string;
function GetLanguageNames(Index: Integer): string;
function GetLanguages(Index: Integer): TLangIdRec;
function GetTranslationCount: Integer;
function GetTranslations(Index: Integer): TLangIdRec;
procedure SetLanguageIndex(const Value: Integer);
protected
procedure CreateItemsForLanguage;
procedure CheckLanguageIndex(Value: Integer);
procedure ExtractData;
procedure ExtractFlags;
function GetBinFileVersion: string;
function GetBinProductVersion: string;
function GetCustomFieldValue(const FieldName: string): string;
function GetFileOS: DWORD;
function GetFileSubType: DWORD;
function GetFileType: DWORD;
function GetFileVersionBuild: string;
function GetFileVersionMajor: string;
function GetFileVersionMinor: string;
function GetFileVersionRelease: string;
function GetProductVersionBuild: string;
function GetProductVersionMajor: string;
function GetProductVersionMinor: string;
function GetProductVersionRelease: string;
function GetVersionKeyValue(Index: Integer): string;
public
constructor Attach(VersionInfoData: Pointer; Size: Integer);
constructor Create(const FileName: string); overload;
{$IFDEF MSWINDOWS}
{$IFDEF FPC}
constructor Create(const Window: HWND; Dummy: Pointer = nil); overload;
{$ELSE}
constructor Create(const Window: HWND); overload;
{$ENDIF}
constructor Create(const Module: HMODULE); overload;
{$ENDIF MSWINDOWS}
destructor Destroy; override;
class function VersionLanguageId(const LangIdRec: TLangIdRec): string;
class function VersionLanguageName(const LangId: Word): string;
function TranslationMatchesLanguages(Exact: Boolean = True): Boolean;
property BinFileVersion: string read GetBinFileVersion;
property BinProductVersion: string read GetBinProductVersion;
property Comments: string index 1 read GetVersionKeyValue;
property CompanyName: string index 2 read GetVersionKeyValue;
property FileDescription: string index 3 read GetVersionKeyValue;
property FixedInfo: TVSFixedFileInfo read GetFixedInfo;
property FileFlags: TFileFlags read FFileFlags;
property FileOS: DWORD read GetFileOS;
property FileSubType: DWORD read GetFileSubType;
property FileType: DWORD read GetFileType;
property FileVersion: string index 4 read GetVersionKeyValue;
property FileVersionBuild: string read GetFileVersionBuild;
property FileVersionMajor: string read GetFileVersionMajor;
property FileVersionMinor: string read GetFileVersionMinor;
property FileVersionRelease: string read GetFileVersionRelease;
property Items: TStrings read GetItems;
property InternalName: string index 5 read GetVersionKeyValue;
property LanguageCount: Integer read GetLanguageCount;
property LanguageIds[Index: Integer]: string read GetLanguageIds;
property LanguageIndex: Integer read FLanguageIndex write SetLanguageIndex;
property Languages[Index: Integer]: TLangIdRec read GetLanguages;
property LanguageNames[Index: Integer]: string read GetLanguageNames;
property LegalCopyright: string index 6 read GetVersionKeyValue;
property LegalTradeMarks: string index 7 read GetVersionKeyValue;
property OriginalFilename: string index 8 read GetVersionKeyValue;
property PrivateBuild: string index 12 read GetVersionKeyValue;
property ProductName: string index 9 read GetVersionKeyValue;
property ProductVersion: string index 10 read GetVersionKeyValue;
property ProductVersionBuild: string read GetProductVersionBuild;
property ProductVersionMajor: string read GetProductVersionMajor;
property ProductVersionMinor: string read GetProductVersionMinor;
property ProductVersionRelease: string read GetProductVersionRelease;
property SpecialBuild: string index 11 read GetVersionKeyValue;
property TranslationCount: Integer read GetTranslationCount;
property Translations[Index: Integer]: TLangIdRec read GetTranslations;
end;
function OSIdentToString(const OSIdent: DWORD): string;
function OSFileTypeToString(const OSFileType: DWORD; const OSFileSubType: DWORD = 0): string;
function VersionResourceAvailable(const FileName: string): Boolean;
{$ENDIF MSWINDOWS}
// Version Info formatting
type
TFileVersionFormat = (vfMajorMinor, vfFull);
function FormatVersionString(const HiV, LoV: Word): string; overload;
function FormatVersionString(const Major, Minor, Build, Revision: Word): string; overload;
{$IFDEF MSWINDOWS}
function FormatVersionString(const FixedInfo: TVSFixedFileInfo; VersionFormat: TFileVersionFormat = vfFull): string; overload;
// Version Info extracting
procedure VersionExtractFileInfo(const FixedInfo: TVSFixedFileInfo; var Major, Minor, Build, Revision: Word);
procedure VersionExtractProductInfo(const FixedInfo: TVSFixedFileInfo; var Major, Minor, Build, Revision: Word);
// Fixed Version Info routines
function VersionFixedFileInfo(const FileName: string; var FixedInfo: TVSFixedFileInfo): Boolean;
function VersionFixedFileInfoString(const FileName: string; VersionFormat: TFileVersionFormat = vfFull;
const NotAvailableText: string = ''): string;
{$ENDIF MSWINDOWS}
// Streams
//
// TStream descendent classes for dealing with temporary files and for using file mapping objects.
type
TJclTempFileStream = class(THandleStream)
private
FFileName: string;
public
constructor Create(const Prefix: string);
destructor Destroy; override;
property FileName: string read FFileName;
end;
{$IFDEF MSWINDOWS}
TJclCustomFileMapping = class;
TJclFileMappingView = class(TCustomMemoryStream)
private
FFileMapping: TJclCustomFileMapping;
FOffsetHigh: Cardinal;
FOffsetLow: Cardinal;
function GetIndex: Integer;
function GetOffset: Int64;
public
constructor Create(const FileMap: TJclCustomFileMapping;
Access, Size: Cardinal; ViewOffset: Int64);
constructor CreateAt(FileMap: TJclCustomFileMapping; Access,
Size: Cardinal; ViewOffset: Int64; Address: Pointer);
destructor Destroy; override;
function Flush(const Count: Cardinal): Boolean;
procedure LoadFromStream(const Stream: TStream);
procedure LoadFromFile(const FileName: string);
function Write(const Buffer; Count: Longint): Longint; override;
property Index: Integer read GetIndex;
property FileMapping: TJclCustomFileMapping read FFileMapping;
property Offset: Int64 read GetOffset;
end;
TJclFileMappingRoundOffset = (rvDown, rvUp);
TJclCustomFileMapping = class(TObject)
private
FExisted: Boolean;
FHandle: THandle;
FName: string;
FRoundViewOffset: TJclFileMappingRoundOffset;
FViews: TList;
function GetCount: Integer;
function GetView(Index: Integer): TJclFileMappingView;
protected
procedure ClearViews;
procedure InternalCreate(const FileHandle: THandle; const Name: string;
const Protect: Cardinal; MaximumSize: Int64; SecAttr: PSecurityAttributes);
procedure InternalOpen(const Name: string; const InheritHandle: Boolean;
const DesiredAccess: Cardinal);
public
constructor Create;
constructor Open(const Name: string; const InheritHandle: Boolean; const DesiredAccess: Cardinal);
destructor Destroy; override;
function Add(const Access, Count: Cardinal; const Offset: Int64): Integer;
function AddAt(const Access, Count: Cardinal; const Offset: Int64; const Address: Pointer): Integer;
procedure Delete(const Index: Integer);
function IndexOf(const View: TJclFileMappingView): Integer;
property Count: Integer read GetCount;
property Existed: Boolean read FExisted;
property Handle: THandle read FHandle;
property Name: string read FName;
property RoundViewOffset: TJclFileMappingRoundOffset read FRoundViewOffset write FRoundViewOffset;
property Views[index: Integer]: TJclFileMappingView read GetView;
end;
TJclFileMapping = class(TJclCustomFileMapping)
private
FFileHandle: THandle;
public
constructor Create(const FileName: string; FileMode: Cardinal;
const Name: string; Protect: Cardinal; const MaximumSize: Int64;
SecAttr: PSecurityAttributes); overload;
constructor Create(const FileHandle: THandle; const Name: string;
Protect: Cardinal; const MaximumSize: Int64;
SecAttr: PSecurityAttributes); overload;
destructor Destroy; override;
property FileHandle: THandle read FFileHandle;
end;
TJclSwapFileMapping = class(TJclCustomFileMapping)
public
constructor Create(const Name: string; Protect: Cardinal;
const MaximumSize: Int64; SecAttr: PSecurityAttributes);
end;
TJclFileMappingStream = class(TCustomMemoryStream)
private
FFileHandle: THandle;
FMapping: THandle;
protected
procedure Close;
public
constructor Create(const FileName: string; FileMode: Word = fmOpenRead or fmShareDenyWrite);
destructor Destroy; override;
function Write(const Buffer; Count: Longint): Longint; override;
end;
{$ENDIF MSWINDOWS}
TJclMappedTextReaderIndex = (tiNoIndex, tiFull);
PPAnsiCharArray = ^TPAnsiCharArray;
TPAnsiCharArray = array [0..0] of PAnsiChar;
TJclAnsiMappedTextReader = class(TPersistent)
private
FContent: PAnsiChar;
FEnd: PAnsiChar;
FIndex: PPAnsiCharArray;
FIndexOption: TJclMappedTextReaderIndex;
FFreeStream: Boolean;
FLastLineNumber: Integer;
FLastPosition: PAnsiChar;
FLineCount: Integer;
FMemoryStream: TCustomMemoryStream;
FPosition: PAnsiChar;
FSize: Integer;
function GetAsString: AnsiString;
function GetEof: Boolean;
function GetChars(Index: Integer): AnsiChar;
function GetLineCount: Integer;
function GetLines(LineNumber: Integer): AnsiString;
function GetPosition: Integer;
function GetPositionFromLine(LineNumber: Integer): Integer;
procedure SetPosition(const Value: Integer);
protected
procedure AssignTo(Dest: TPersistent); override;
procedure CreateIndex;
procedure Init;
function PtrFromLine(LineNumber: Integer): PAnsiChar;
function StringFromPosition(var StartPos: PAnsiChar): AnsiString;
public
constructor Create(MemoryStream: TCustomMemoryStream; FreeStream: Boolean = True;
const AIndexOption: TJclMappedTextReaderIndex = tiNoIndex); overload;
constructor Create(const FileName: TFileName;
const AIndexOption: TJclMappedTextReaderIndex = tiNoIndex); overload;
destructor Destroy; override;
procedure GoBegin;
function Read: AnsiChar;
function ReadLn: AnsiString;
property AsString: AnsiString read GetAsString;
property Chars[Index: Integer]: AnsiChar read GetChars;
property Content: PAnsiChar read FContent;
property Eof: Boolean read GetEof;
property IndexOption: TJclMappedTextReaderIndex read FIndexOption;
property Lines[LineNumber: Integer]: AnsiString read GetLines;
property LineCount: Integer read GetLineCount;
property PositionFromLine[LineNumber: Integer]: Integer read GetPositionFromLine;
property Position: Integer read GetPosition write SetPosition;
property Size: Integer read FSize;
end;
PPWideCharArray = ^TPWideCharArray;
TPWideCharArray = array [0..0] of PWideChar;
TJclWideMappedTextReader = class(TPersistent)
private
FContent: PWideChar;
FEnd: PWideChar;
FIndex: PPWideCharArray;
FIndexOption: TJclMappedTextReaderIndex;
FFreeStream: Boolean;
FLastLineNumber: Integer;
FLastPosition: PWideChar;
FLineCount: Integer;
FMemoryStream: TCustomMemoryStream;
FPosition: PWideChar;
FSize: Integer;
function GetAsString: WideString;
function GetEof: Boolean;
function GetChars(Index: Integer): WideChar;
function GetLineCount: Integer;
function GetLines(LineNumber: Integer): WideString;
function GetPosition: Integer;
function GetPositionFromLine(LineNumber: Integer): Integer;
procedure SetPosition(const Value: Integer);
protected
procedure AssignTo(Dest: TPersistent); override;
procedure CreateIndex;
procedure Init;
function PtrFromLine(LineNumber: Integer): PWideChar;
function StringFromPosition(var StartPos: PWideChar): WideString;
public
constructor Create(MemoryStream: TCustomMemoryStream; FreeStream: Boolean = True;
const AIndexOption: TJclMappedTextReaderIndex = tiNoIndex); overload;
constructor Create(const FileName: TFileName;
const AIndexOption: TJclMappedTextReaderIndex = tiNoIndex); overload;
destructor Destroy; override;
procedure GoBegin;
function Read: WideChar;
function ReadLn: WideString;
property AsString: WideString read GetAsString;
property Chars[Index: Integer]: WideChar read GetChars;
property Content: PWideChar read FContent;
property Eof: Boolean read GetEof;
property IndexOption: TJclMappedTextReaderIndex read FIndexOption;
property Lines[LineNumber: Integer]: WideString read GetLines;
property LineCount: Integer read GetLineCount;
property PositionFromLine[LineNumber: Integer]: Integer read GetPositionFromLine;
property Position: Integer read GetPosition write SetPosition;
property Size: Integer read FSize;
end;
{ TODO : UNTESTED/UNDOCUMENTED }
type
TJclFileMaskComparator = class(TObject)
private
FFileMask: string;
FExts: array of string;
FNames: array of string;
FWildChars: array of Byte;
FSeparator: Char;
procedure CreateMultiMasks;
function GetCount: Integer;
function GetExts(Index: Integer): string;
function GetMasks(Index: Integer): string;
function GetNames(Index: Integer): string;
procedure SetFileMask(const Value: string);
procedure SetSeparator(const Value: Char);
public
constructor Create;
function Compare(const NameExt: string): Boolean;
property Count: Integer read GetCount;
property Exts[Index: Integer]: string read GetExts;
property FileMask: string read FFileMask write SetFileMask;
property Masks[Index: Integer]: string read GetMasks;
property Names[Index: Integer]: string read GetNames;
property Separator: Char read FSeparator write SetSeparator;
end;
EJclPathError = class(EJclError);
EJclFileUtilsError = class(EJclError);
{$IFDEF UNIX}
EJclTempFileStreamError = class(EJclFileUtilsError);
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
EJclTempFileStreamError = class(EJclWin32Error);
EJclFileMappingError = class(EJclWin32Error);
EJclFileMappingViewError = class(EJclWin32Error);
{$ENDIF MSWINDOWS}
{$IFDEF KEEP_DEPRECATED}
// Deprecated, do not use
{$IFDEF MSWINDOWS}
function PathGetLongName2(const Path: string): string;
{$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
{$IFNDEF FPC}
function Win32DeleteFile(const FileName: string; MoveToRecycleBin: Boolean): Boolean;
{$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
{$ENDIF ~FPC}
function Win32MoveFileReplaceExisting(const SrcFileName, DstFileName: string): Boolean;
{$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function Win32BackupFile(const FileName: string; Move: Boolean): Boolean;
{$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function Win32RestoreFile(const FileName: string): Boolean;
{$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
{$ENDIF MSWINDOWS}
{$ENDIF KEEP_DEPRECATED}
function SamePath(const Path1, Path2: string): Boolean;
// functions to add/delete paths from a separated list of paths
// on windows the separator is a semi-colon ';'
// on linux the separator is a colon ':'
// add items at the end
procedure PathListAddItems(var List: string; const Items: string);
// add items at the end if they are not present
procedure PathListIncludeItems(var List: string; const Items: string);
// delete multiple items
procedure PathListDelItems(var List: string; const Items: string);
// delete one item
procedure PathListDelItem(var List: string; const Index: Integer);
// return the number of item
function PathListItemCount(const List: string): Integer;
// return the Nth item
function PathListGetItem(const List: string; const Index: Integer): string;
// set the Nth item
procedure PathListSetItem(var List: string; const Index: Integer; const Value: string);
// return the index of an item
function PathListItemIndex(const List, Item: string): Integer;
// additional functions to access the commandline parameters of an application
// returns the name of the command line parameter at position index, which is
// separated by the given separator, if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamName (Index : Integer; const Separator : string = '=';
const AllowedPrefixCharacters : string = '-/'; TrimName : Boolean = true) : string;
// returns the value of the command line parameter at position index, which is
// separated by the given separator
function ParamValue (Index : Integer; const Separator : string = '='; TrimValue : Boolean = true) : string; overload;
// seaches a command line parameter where the namepart is the searchname
// and returns the value which is which by the given separator.
// CaseSensitive defines the search type. if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamValue (const SearchName : string; const Separator : string = '=';
CaseSensitive : Boolean = False;
const AllowedPrefixCharacters : string = '-/'; TrimValue : Boolean = true) : string; overload;
// seaches a command line parameter where the namepart is the searchname
// and returns the position index. if no separator is defined, the full paramstr is compared.
// CaseSensitive defines the search type. if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamPos (const SearchName : string; const Separator : string = '=';
CaseSensitive : Boolean = False;
const AllowedPrefixCharacters : string = '-/'): Integer;
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL: https://jcl.svn.sourceforge.net/svnroot/jcl/trunk/jcl/source/common/JclFileUtils.pas $';
Revision: '$Revision: 3043 $';
Date: '$Date: 2009-10-11 18:54:42 +0200 (zo, 11 okt 2009) $';
LogPath: 'JCL\source\common';
Extra: '';
Data: nil
);
{$ENDIF UNITVERSIONING}
implementation
uses
{$IFDEF MSWINDOWS}
ShellApi, ActiveX, ComObj, ShlObj,
JclShell, JclSysInfo, JclSecurity,
{$ENDIF MSWINDOWS}
JclSysUtils, JclDateTime, JclResources,
JclStrings;
{ Some general notes:
This unit redeclares some functions from FileCtrl.pas to avoid a dependency on that unit in the
JCL. The problem is that FileCtrl.pas uses some units (eg Forms.pas) which have ridiculous
initialization requirements. They add 4KB (!) to the executable and roughly 1 second of startup.
That initialization is only necessary for GUI applications and is unacceptable for high
performance services or console apps.
The routines which query files or directories for their attributes deliberately use FindFirst
even though there may be easier ways to get at the required information. This is because FindFirst
is about the only routine which doesn't cause the file's last modification/accessed time to be
changed which is usually an undesired side-effect. }
{$IFDEF UNIX}
const
ERROR_NO_MORE_FILES = -1;
INVALID_HANDLE_VALUE = THandle(-1);
{$ENDIF UNIX}
//=== { TJclTempFileStream } =================================================
constructor TJclTempFileStream.Create(const Prefix: string);
var
FileHandle: THandle;
begin
FFileName := Prefix;
FileHandle := FileCreateTemp(FFileName);
// (rom) is it really wise to throw an exception before calling inherited?
if FileHandle = INVALID_HANDLE_VALUE then
raise EJclTempFileStreamError.CreateRes(@RsFileStreamCreate);
inherited Create(FileHandle);
end;
destructor TJclTempFileStream.Destroy;
begin
if THandle(Handle) <> INVALID_HANDLE_VALUE then
FileClose(Handle);
inherited Destroy;
end;
//=== { TJclFileMappingView } ================================================
{$IFDEF MSWINDOWS}
constructor TJclFileMappingView.Create(const FileMap: TJclCustomFileMapping;
Access, Size: Cardinal; ViewOffset: Int64);
var
BaseAddress: Pointer;
OffsetLow, OffsetHigh: Cardinal;
begin
inherited Create;
if FileMap = nil then
raise EJclFileMappingViewError.CreateRes(@RsViewNeedsMapping);
FFileMapping := FileMap;
// Offset must be a multiple of system memory allocation granularity
RoundToAllocGranularity64(ViewOffset, FFileMapping.RoundViewOffset = rvUp);
I64ToCardinals(ViewOffset, OffsetLow, OffsetHigh);
FOffsetHigh := OffsetHigh;
FOffsetLow := OffsetLow;
BaseAddress := MapViewOfFile(FFileMapping.Handle, Access, FOffsetHigh, FOffsetLow, Size);
if BaseAddress = nil then
raise EJclFileMappingViewError.CreateRes(@RsCreateFileMappingView);
// If we are mapping a file and size = 0 then MapViewOfFile has mapped the entire file. We must
// figure out the size ourselves before we can call SetPointer. Since in case of failure to
// retrieve the size we raise an exception, we also have to explicitly unmap the view which
// otherwise would have been done by the destructor.
if (Size = 0) and (FileMap is TJclFileMapping) then
begin
Size := GetFileSize(TJclFileMapping(FileMap).FFileHandle, nil);
if Size = DWORD(-1) then
begin
UnMapViewOfFile(BaseAddress);
raise EJclFileMappingViewError.CreateRes(@RsFailedToObtainSize);
end;
end;
SetPointer(BaseAddress, Size);
FFileMapping.FViews.Add(Self);
end;
constructor TJclFileMappingView.CreateAt(FileMap: TJclCustomFileMapping;
Access, Size: Cardinal; ViewOffset: Int64; Address: Pointer);
var
BaseAddress: Pointer;
OffsetLow, OffsetHigh: Cardinal;
begin
inherited Create;
if FileMap = nil then
raise EJclFileMappingViewError.CreateRes(@RsViewNeedsMapping);
FFileMapping := FileMap;
// Offset must be a multiple of system memory allocation granularity
RoundToAllocGranularity64(ViewOffset, FFileMapping.RoundViewOffset = rvUp);
RoundToAllocGranularityPtr(Address, FFileMapping.RoundViewOffset = rvUp);
I64ToCardinals(ViewOffset, OffsetLow, OffsetHigh);
FOffsetHigh := OffsetHigh;
FOffsetLow := OffsetLow;
BaseAddress := MapViewOfFileEx(FFileMapping.Handle, Access, FOffsetHigh,
FOffsetLow, Size, Address);
if BaseAddress = nil then
raise EJclFileMappingViewError.CreateRes(@RsCreateFileMappingView);
// If we are mapping a file and size = 0 then MapViewOfFile has mapped the entire file. We must
// figure out the size ourselves before we can call SetPointer. Since in case of failure to
// retrieve the size we raise an exception, we also have to explicitly unmap the view which
// otherwise would have been done by the destructor.
if (Size = 0) and (FileMap is TJclFileMapping) then
begin
Size := GetFileSize(TJclFileMapping(FileMap).FFileHandle, nil);
if Size = DWORD(-1) then
begin
UnMapViewOfFile(BaseAddress);
raise EJclFileMappingViewError.CreateRes(@RsFailedToObtainSize);
end;
end;
SetPointer(BaseAddress, Size);
FFileMapping.FViews.Add(Self);
end;
destructor TJclFileMappingView.Destroy;
var
IndexOfSelf: Integer;
begin
if Memory <> nil then
begin
UnMapViewOfFile(Memory);
SetPointer(nil, 0);
end;
if FFileMapping <> nil then
begin
IndexOfSelf := FFileMapping.IndexOf(Self);
if IndexOfSelf <> -1 then
FFileMapping.FViews.Delete(IndexOfSelf);
end;
inherited Destroy;
end;
function TJclFileMappingView.Flush(const Count: Cardinal): Boolean;
begin
Result := FlushViewOfFile(Memory, Count);
end;
function TJclFileMappingView.GetIndex: Integer;
begin
Result := FFileMapping.IndexOf(Self);
end;
function TJclFileMappingView.GetOffset: Int64;
begin
CardinalsToI64(Result, FOffsetLow, FOffsetHigh);
end;
procedure TJclFileMappingView.LoadFromFile(const FileName: string);
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
FreeAndNil(Stream);
end;
end;
procedure TJclFileMappingView.LoadFromStream(const Stream: TStream);
begin
if Stream.Size > Size then
raise EJclFileMappingViewError.CreateRes(@RsLoadFromStreamSize);
Stream.Position := 0;
Stream.ReadBuffer(Memory^, Stream.Size);
end;
function TJclFileMappingView.Write(const Buffer; Count: Integer): Longint;
begin
Result := 0;
if (Size - Position) >= Count then
begin
System.Move(Buffer, Pointer(TJclAddr(Memory) + TJclAddr(Position))^, Count);
Position := Position + Count;
Result := Count;
end;
end;
//=== { TJclCustomFileMapping } ==============================================
constructor TJclCustomFileMapping.Create;
begin
inherited Create;
FViews := TList.Create;
FRoundViewOffset := rvDown;
end;
constructor TJclCustomFileMapping.Open(const Name: string;
const InheritHandle: Boolean; const DesiredAccess: Cardinal);
begin
Create;
InternalOpen(Name, InheritHandle, DesiredAccess);
end;
destructor TJclCustomFileMapping.Destroy;
begin
ClearViews;
if FHandle <> 0 then
CloseHandle(FHandle);
FreeAndNil(FViews);
inherited Destroy;
end;
function TJclCustomFileMapping.Add(const Access, Count: Cardinal; const Offset: Int64): Integer;
var
View: TJclFileMappingView;
begin
// The view adds itself to the FViews list
View := TJclFileMappingView.Create(Self, Access, Count, Offset);
Result := View.Index;
end;
function TJclCustomFileMapping.AddAt(const Access, Count: Cardinal;
const Offset: Int64; const Address: Pointer): Integer;
var
View: TJclFileMappingView;
begin
// The view adds itself to the FViews list
View := TJclFileMappingView.CreateAt(Self, Access, Count, Offset, Address);
Result := View.Index;
end;
procedure TJclCustomFileMapping.ClearViews;
var
I: Integer;
begin
// Note that the view destructor removes the view object from the FViews list so we must loop
// downwards from count to 0
for I := FViews.Count - 1 downto 0 do
TJclFileMappingView(FViews[I]).Free;
end;
procedure TJclCustomFileMapping.Delete(const Index: Integer);
begin
// Note that the view destructor removes itself from FViews
TJclFileMappingView(FViews[Index]).Free;
end;
function TJclCustomFileMapping.GetCount: Integer;
begin
Result := FViews.Count;
end;
function TJclCustomFileMapping.GetView(Index: Integer): TJclFileMappingView;
begin
Result := TJclFileMappingView(FViews.Items[index]);
end;
function TJclCustomFileMapping.IndexOf(const View: TJclFileMappingView): Integer;
begin
Result := FViews.IndexOf(View);
end;
procedure TJclCustomFileMapping.InternalCreate(const FileHandle: THandle;
const Name: string; const Protect: Cardinal; MaximumSize: Int64;
SecAttr: PSecurityAttributes);
var
MaximumSizeLow, MaximumSizeHigh: Cardinal;
begin
FName := Name;
I64ToCardinals(MaximumSize, MaximumSizeLow, MaximumSizeHigh);
FHandle := CreateFileMapping(FileHandle, SecAttr, Protect, MaximumSizeHigh,
MaximumSizeLow, PChar(Name));
if FHandle = 0 then
raise EJclFileMappingError.CreateRes(@RsCreateFileMapping);
FExisted := GetLastError = ERROR_ALREADY_EXISTS;
end;
procedure TJclCustomFileMapping.InternalOpen(const Name: string;
const InheritHandle: Boolean; const DesiredAccess: Cardinal);
begin
FExisted := True;
FName := Name;
FHandle := OpenFileMapping(DesiredAccess, InheritHandle, PChar(Name));
if FHandle = 0 then
raise EJclFileMappingError.CreateRes(@RsCreateFileMapping);
end;
//=== { TJclFileMapping } ====================================================
constructor TJclFileMapping.Create(const FileName: string; FileMode: Cardinal;
const Name: string; Protect: Cardinal; const MaximumSize: Int64;
SecAttr: PSecurityAttributes);
begin
FFileHandle := INVALID_HANDLE_VALUE;
inherited Create;
FFileHandle := THandle(FileOpen(FileName, FileMode));
if FFileHandle = INVALID_HANDLE_VALUE then
raise EJclFileMappingError.CreateRes(@RsFileMappingOpenFile);
InternalCreate(FFileHandle, Name, Protect, MaximumSize, SecAttr);
end;
constructor TJclFileMapping.Create(const FileHandle: THandle; const Name: string;
Protect: Cardinal; const MaximumSize: Int64; SecAttr: PSecurityAttributes);
begin
FFileHandle := INVALID_HANDLE_VALUE;
inherited Create;
if FileHandle = INVALID_HANDLE_VALUE then
raise EJclFileMappingError.CreateRes(@RsFileMappingInvalidHandle);
InternalCreate(FileHandle, Name, Protect, MaximumSize, SecAttr);
// Duplicate the handle into FFileHandle as opposed to assigning it directly. This will cause
// FFileHandle to retrieve a unique copy which is independent of FileHandle. This makes the
// remainder of the class, especially the destructor, easier. The caller will have to close it's
// own copy of the handle explicitly.
DuplicateHandle(GetCurrentProcess, FileHandle, GetCurrentProcess,
@FFileHandle, 0, False, DUPLICATE_SAME_ACCESS);
end;
destructor TJclFileMapping.Destroy;
begin
if FFileHandle <> INVALID_HANDLE_VALUE then
CloseHandle(FFileHandle);
inherited Destroy;
end;
//=== { TJclSwapFileMapping } ================================================
constructor TJclSwapFileMapping.Create(const Name: string; Protect: Cardinal;
const MaximumSize: Int64; SecAttr: PSecurityAttributes);
begin
inherited Create;
InternalCreate(INVALID_HANDLE_VALUE, Name, Protect, MaximumSize, SecAttr);
end;
//=== { TJclFileMappingStream } ==============================================
constructor TJclFileMappingStream.Create(const FileName: string; FileMode: Word);
var
Protect, Access, Size: DWORD;
BaseAddress: Pointer;
begin
inherited Create;
FFileHandle := THandle(FileOpen(FileName, FileMode));
if FFileHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError;
if (FileMode and $0F) = fmOpenReadWrite then
begin
Protect := PAGE_WRITECOPY;
Access := FILE_MAP_COPY;
end
else
begin
Protect := PAGE_READONLY;
Access := FILE_MAP_READ;
end;
FMapping := CreateFileMapping(FFileHandle, nil, Protect, 0, 0, nil);
if FMapping = 0 then
begin
Close;
raise EJclFileMappingError.CreateRes(@RsCreateFileMapping);
end;
BaseAddress := MapViewOfFile(FMapping, Access, 0, 0, 0);
if BaseAddress = nil then
begin
Close;
raise EJclFileMappingViewError.CreateRes(@RsCreateFileMappingView);
end;
Size := GetFileSize(FFileHandle, nil);
if Size = DWORD(-1) then
begin
UnMapViewOfFile(BaseAddress);
Close;
raise EJclFileMappingViewError.CreateRes(@RsFailedToObtainSize);
end;
SetPointer(BaseAddress, Size);
end;
destructor TJclFileMappingStream.Destroy;
begin
Close;
inherited Destroy;
end;
procedure TJclFileMappingStream.Close;
begin
if Memory <> nil then
begin
UnMapViewOfFile(Memory);
SetPointer(nil, 0);
end;
if FMapping <> 0 then
begin
CloseHandle(FMapping);
FMapping := 0;
end;
if FFileHandle <> INVALID_HANDLE_VALUE then
begin
FileClose(FFileHandle);
FFileHandle := INVALID_HANDLE_VALUE;
end;
end;
function TJclFileMappingStream.Write(const Buffer; Count: Integer): Longint;
begin
Result := 0;
if (Size - Position) >= Count then
begin
System.Move(Buffer, Pointer(TJclAddr(Memory) + TJclAddr(Position))^, Count);
Position := Position + Count;
Result := Count;
end;
end;
{$ENDIF MSWINDOWS}
//=== { TJclAnsiMappedTextReader } ===========================================
constructor TJclAnsiMappedTextReader.Create(MemoryStream: TCustomMemoryStream; FreeStream: Boolean;
const AIndexOption: TJclMappedTextReaderIndex);
begin
inherited Create;
FMemoryStream := MemoryStream;
FFreeStream := FreeStream;
FIndexOption := AIndexOption;
Init;
end;
constructor TJclAnsiMappedTextReader.Create(const FileName: TFileName;
const AIndexOption: TJclMappedTextReaderIndex);
begin
inherited Create;
{$IFDEF MSWINDOWS}
FMemoryStream := TJclFileMappingStream.Create(FileName);
{$ELSE ~ MSWINDOWS}
FMemoryStream := TMemoryStream.Create;
TMemoryStream(FMemoryStream).LoadFromFile(FileName);
{$ENDIF ~ MSWINDOWS}
FFreeStream := True;
FIndexOption := AIndexOption;
Init;
end;
destructor TJclAnsiMappedTextReader.Destroy;
begin
if FFreeStream then
FMemoryStream.Free;
FreeMem(FIndex);
inherited Destroy;
end;
procedure TJclAnsiMappedTextReader.AssignTo(Dest: TPersistent);
begin
if Dest is TStrings then
begin
GoBegin;
TStrings(Dest).BeginUpdate;
try
while not Eof do
TStrings(Dest).Add(string(ReadLn));
finally
TStrings(Dest).EndUpdate;
end;
end
else
inherited AssignTo(Dest);
end;
procedure TJclAnsiMappedTextReader.CreateIndex;
var
P, LastLineStart: PAnsiChar;
I: Integer;
begin
{$RANGECHECKS OFF}
P := FContent;
I := 0;
LastLineStart := P;
while P < FEnd do
begin
// CRLF, CR, LF and LFCR are seen as valid sets of chars for EOL marker
if CharIsReturn(Char(P^)) then
begin
if I and $FFFF = 0 then
ReallocMem(FIndex, (I + $10000) * SizeOf(Pointer));
FIndex[I] := LastLineStart;
Inc(I);
case P^ of
NativeLineFeed:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
end;
LastLineStart := P;
end
else
Inc(P);
end;
if P > LastLineStart then
begin
ReallocMem(FIndex, (I + 1) * SizeOf(Pointer));
FIndex[I] := LastLineStart;
Inc(I);
end
else
ReallocMem(FIndex, I * SizeOf(Pointer));
FLineCount := I;
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
end;
function TJclAnsiMappedTextReader.GetEof: Boolean;
begin
Result := FPosition >= FEnd;
end;
function TJclAnsiMappedTextReader.GetAsString: AnsiString;
begin
SetString(Result, Content, Size);
end;
function TJclAnsiMappedTextReader.GetChars(Index: Integer): AnsiChar;
begin
if (Index < 0) or (Index >= Size) then
raise EJclError.CreateRes(@RsFileIndexOutOfRange);
Result := AnsiChar(PByte(FContent + Index)^);
end;
function TJclAnsiMappedTextReader.GetLineCount: Integer;
var
P: PAnsiChar;
begin
if FLineCount = -1 then
begin
FLineCount := 0;
if FContent < FEnd then
begin
P := FContent;
while P < FEnd do
begin
case P^ of
NativeLineFeed:
begin
Inc(FLineCount);
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(FLineCount);
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
else
Inc(P);
end;
end;
if (P = FEnd) and (P > FContent) and not CharIsReturn(Char((P-1)^)) then
Inc(FLineCount);
end;
end;
Result := FLineCount;
end;
function TJclAnsiMappedTextReader.GetLines(LineNumber: Integer): AnsiString;
var
P: PAnsiChar;
begin
P := PtrFromLine(LineNumber);
Result := StringFromPosition(P);
end;
function TJclAnsiMappedTextReader.GetPosition: Integer;
begin
Result := FPosition - FContent;
end;
procedure TJclAnsiMappedTextReader.GoBegin;
begin
Position := 0;
end;
procedure TJclAnsiMappedTextReader.Init;
begin
FContent := FMemoryStream.Memory;
FSize := FMemoryStream.Size;
FEnd := FContent + FSize;
FPosition := FContent;
FLineCount := -1;
FLastLineNumber := 0;
FLastPosition := FContent;
if IndexOption = tiFull then
CreateIndex;
end;
function TJclAnsiMappedTextReader.GetPositionFromLine(LineNumber: Integer): Integer;
var
P: PAnsiChar;
begin
P := PtrFromLine(LineNumber);
if P = nil then
Result := -1
else
Result := P - FContent;
end;
function TJclAnsiMappedTextReader.PtrFromLine(LineNumber: Integer): PAnsiChar;
var
LineOffset: Integer;
begin
Result := nil;
{$RANGECHECKS OFF}
if (IndexOption <> tiNoIndex) and (LineNumber < FLineCount) and (FIndex[LineNumber] <> nil) then
Result := FIndex[LineNumber]
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
else
begin
LineOffset := LineNumber - FLastLineNumber;
if (FLineCount <> -1) and (LineNumber > 0) then
begin
if -LineOffset > LineNumber then
begin
FLastLineNumber := 0;
FLastPosition := FContent;
LineOffset := LineNumber;
end
else
if LineOffset > FLineCount - LineNumber then
begin
FLastLineNumber := FLineCount;
FLastPosition := FEnd;
LineOffset := LineNumber - FLineCount;
end;
end;
if LineNumber <= 0 then
Result := FContent
else
if LineOffset = 0 then
Result := FLastPosition
else
if LineOffset > 0 then
begin
Result := FLastPosition;
while (Result < FEnd) and (LineOffset > 0) do
begin
case Result^ of
NativeLineFeed:
begin
Dec(LineOffset);
Inc(Result);
if (Result < FEnd) and (Result^ = NativeCarriageReturn) then
Inc(Result);
end;
NativeCarriageReturn:
begin
Dec(LineOffset);
Inc(Result);
if (Result < FEnd) and (Result^ = NativeLineFeed) then
Inc(Result);
end;
else
Inc(Result);
end;
end;
end
else
if LineOffset < 0 then
begin
Result := FLastPosition;
while (Result > FContent) and (LineOffset < 1) do
begin
Dec(Result);
case Result^ of
NativeLineFeed:
begin
Inc(LineOffset);
if LineOffset >= 1 then
Inc(Result)
else
if (Result > FContent) and ((Result-1)^ = NativeCarriageReturn) then
Dec(Result);
end;
NativeCarriageReturn:
begin
Inc(LineOffset);
if LineOffset >= 1 then
Inc(Result)
else
if (Result > FContent) and ((Result-1)^ = NativeLineFeed) then
Dec(Result);
end;
end;
end;
end;
FLastLineNumber := LineNumber;
FLastPosition := Result;
end;
end;
function TJclAnsiMappedTextReader.Read: AnsiChar;
begin
if FPosition >= FEnd then
Result := #0
else
begin
Result := FPosition^;
Inc(FPosition);
end;
end;
function TJclAnsiMappedTextReader.ReadLn: AnsiString;
begin
Result := StringFromPosition(FPosition);
end;
procedure TJclAnsiMappedTextReader.SetPosition(const Value: Integer);
begin
FPosition := FContent + Value;
end;
function TJclAnsiMappedTextReader.StringFromPosition(var StartPos: PAnsiChar): AnsiString;
var
P: PAnsiChar;
begin
if (StartPos = nil) or (StartPos >= FEnd) then
Result := ''
else
begin
P := StartPos;
while (P < FEnd) and (not CharIsReturn(Char(P^))) do
Inc(P);
SetString(Result, StartPos, P - StartPos);
if P < FEnd then
begin
case P^ of
NativeLineFeed:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
end;
end;
StartPos := P;
end;
end;
//=== { TJclWideMappedTextReader } ===========================================
constructor TJclWideMappedTextReader.Create(MemoryStream: TCustomMemoryStream; FreeStream: Boolean;
const AIndexOption: TJclMappedTextReaderIndex);
begin
inherited Create;
FMemoryStream := MemoryStream;
FFreeStream := FreeStream;
FIndexOption := AIndexOption;
Init;
end;
constructor TJclWideMappedTextReader.Create(const FileName: TFileName;
const AIndexOption: TJclMappedTextReaderIndex);
begin
inherited Create;
{$IFDEF MSWINDOWS}
FMemoryStream := TJclFileMappingStream.Create(FileName);
{$ELSE ~ MSWINDOWS}
FMemoryStream := TMemoryStream.Create;
TMemoryStream(FMemoryStream).LoadFromFile(FileName);
{$ENDIF ~ MSWINDOWS}
FFreeStream := True;
FIndexOption := AIndexOption;
Init;
end;
destructor TJclWideMappedTextReader.Destroy;
begin
if FFreeStream then
FMemoryStream.Free;
FreeMem(FIndex);
inherited Destroy;
end;
procedure TJclWideMappedTextReader.AssignTo(Dest: TPersistent);
begin
if Dest is TStrings then
begin
GoBegin;
TStrings(Dest).BeginUpdate;
try
while not Eof do
TStrings(Dest).Add(string(ReadLn));
finally
TStrings(Dest).EndUpdate;
end;
end
else
inherited AssignTo(Dest);
end;
procedure TJclWideMappedTextReader.CreateIndex;
var
P, LastLineStart: PWideChar;
I: Integer;
begin
{$RANGECHECKS OFF}
P := FContent;
I := 0;
LastLineStart := P;
while P < FEnd do
begin
// CRLF, CR, LF and LFCR are seen as valid sets of chars for EOL marker
if CharIsReturn(Char(P^)) then
begin
if I and $FFFF = 0 then
ReallocMem(FIndex, (I + $10000) * SizeOf(Pointer));
FIndex[I] := LastLineStart;
Inc(I);
case P^ of
NativeLineFeed:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
end;
LastLineStart := P;
end
else
Inc(P);
end;
if P > LastLineStart then
begin
ReallocMem(FIndex, (I + 1) * SizeOf(Pointer));
FIndex[I] := LastLineStart;
Inc(I);
end
else
ReallocMem(FIndex, I * SizeOf(Pointer));
FLineCount := I;
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
end;
function TJclWideMappedTextReader.GetEof: Boolean;
begin
Result := FPosition >= FEnd;
end;
function TJclWideMappedTextReader.GetAsString: WideString;
begin
SetString(Result, Content, Size);
end;
function TJclWideMappedTextReader.GetChars(Index: Integer): WideChar;
begin
if (Index < 0) or (Index >= Size) then
raise EJclError.CreateRes(@RsFileIndexOutOfRange);
Result := WideChar(PByte(FContent + Index)^);
end;
function TJclWideMappedTextReader.GetLineCount: Integer;
var
P: PWideChar;
begin
if FLineCount = -1 then
begin
FLineCount := 0;
if FContent < FEnd then
begin
P := FContent;
while P < FEnd do
begin
case P^ of
NativeLineFeed:
begin
Inc(FLineCount);
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(FLineCount);
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
else
Inc(P);
end;
end;
if (P = FEnd) and (P > FContent) and not CharIsReturn(Char((P-1)^)) then
Inc(FLineCount);
end;
end;
Result := FLineCount;
end;
function TJclWideMappedTextReader.GetLines(LineNumber: Integer): WideString;
var
P: PWideChar;
begin
P := PtrFromLine(LineNumber);
Result := StringFromPosition(P);
end;
function TJclWideMappedTextReader.GetPosition: Integer;
begin
Result := FPosition - FContent;
end;
procedure TJclWideMappedTextReader.GoBegin;
begin
Position := 0;
end;
procedure TJclWideMappedTextReader.Init;
begin
FContent := FMemoryStream.Memory;
FSize := FMemoryStream.Size;
FEnd := FContent + FSize;
FPosition := FContent;
FLineCount := -1;
FLastLineNumber := 0;
FLastPosition := FContent;
if IndexOption = tiFull then
CreateIndex;
end;
function TJclWideMappedTextReader.GetPositionFromLine(LineNumber: Integer): Integer;
var
P: PWideChar;
begin
P := PtrFromLine(LineNumber);
if P = nil then
Result := -1
else
Result := P - FContent;
end;
function TJclWideMappedTextReader.PtrFromLine(LineNumber: Integer): PWideChar;
var
LineOffset: Integer;
begin
Result := nil;
{$RANGECHECKS OFF}
if (IndexOption <> tiNoIndex) and (LineNumber < FLineCount) and (FIndex[LineNumber] <> nil) then
Result := FIndex[LineNumber]
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
else
begin
LineOffset := LineNumber - FLastLineNumber;
if (FLineCount <> -1) and (LineNumber > 0) then
begin
if -LineOffset > LineNumber then
begin
FLastLineNumber := 0;
FLastPosition := FContent;
LineOffset := LineNumber;
end
else
if LineOffset > FLineCount - LineNumber then
begin
FLastLineNumber := FLineCount;
FLastPosition := FEnd;
LineOffset := LineNumber - FLineCount;
end;
end;
if LineNumber <= 0 then
Result := FContent
else
if LineOffset = 0 then
Result := FLastPosition
else
if LineOffset > 0 then
begin
Result := FLastPosition;
while (Result < FEnd) and (LineOffset > 0) do
begin
case Result^ of
NativeLineFeed:
begin
Dec(LineOffset);
Inc(Result);
if (Result < FEnd) and (Result^ = NativeCarriageReturn) then
Inc(Result);
end;
NativeCarriageReturn:
begin
Dec(LineOffset);
Inc(Result);
if (Result < FEnd) and (Result^ = NativeLineFeed) then
Inc(Result);
end;
else
Inc(Result);
end;
end;
end
else
if LineOffset < 0 then
begin
Result := FLastPosition;
while (Result > FContent) and (LineOffset < 1) do
begin
Dec(Result);
case Result^ of
NativeLineFeed:
begin
Inc(LineOffset);
if LineOffset >= 1 then
Inc(Result)
else
if (Result > FContent) and ((Result-1)^ = NativeCarriageReturn) then
Dec(Result);
end;
NativeCarriageReturn:
begin
Inc(LineOffset);
if LineOffset >= 1 then
Inc(Result)
else
if (Result > FContent) and ((Result-1)^ = NativeLineFeed) then
Dec(Result);
end;
end;
end;
end;
FLastLineNumber := LineNumber;
FLastPosition := Result;
end;
end;
function TJclWideMappedTextReader.Read: WideChar;
begin
if FPosition >= FEnd then
Result := #0
else
begin
Result := FPosition^;
Inc(FPosition);
end;
end;
function TJclWideMappedTextReader.ReadLn: WideString;
begin
Result := StringFromPosition(FPosition);
end;
procedure TJclWideMappedTextReader.SetPosition(const Value: Integer);
begin
FPosition := FContent + Value;
end;
function TJclWideMappedTextReader.StringFromPosition(var StartPos: PWideChar): WideString;
var
P: PWideChar;
begin
if (StartPos = nil) or (StartPos >= FEnd) then
Result := ''
else
begin
P := StartPos;
while (P < FEnd) and (not CharIsReturn(Char(P^))) do
Inc(P);
SetString(Result, StartPos, P - StartPos);
if P < FEnd then
begin
case P^ of
NativeLineFeed:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeCarriageReturn) then
Inc(P);
end;
NativeCarriageReturn:
begin
Inc(P);
if (P < FEnd) and (P^ = NativeLineFeed) then
Inc(P);
end;
end;
end;
StartPos := P;
end;
end;
function CharIsDriveLetter(const C: Char): Boolean;
begin
case C of
'a'..'z',
'A'..'Z':
Result := True;
else
Result := False;
end;
end;
//=== Path manipulation ======================================================
function PathAddSeparator(const Path: string): string;
begin
Result := Path;
if (Path = '') or (Path[Length(Path)] <> DirDelimiter) then
Result := Path + DirDelimiter;
end;
function PathAddExtension(const Path, Extension: string): string;
begin
Result := Path;
// (obones) Extension may not contain the leading dot while ExtractFileExt
// always returns it. Hence the need to use StrEnsurePrefix for the SameText
// test to return an accurate value.
if (Path <> '') and (Extension <> '') and
not SameText(ExtractFileExt(Path), StrEnsurePrefix('.', Extension)) then
begin
if Path[Length(Path)] = '.' then
Delete(Result, Length(Path), 1);
if Extension[1] = '.' then
Result := Result + Extension
else
Result := Result + '.' + Extension;
end;
end;
function PathAppend(const Path, Append: string): string;
var
PathLength: Integer;
B1, B2: Boolean;
begin
if Append = '' then
Result := Path
else
begin
PathLength := Length(Path);
if PathLength = 0 then
Result := Append
else
begin
// The following code may look a bit complex but all it does is add Append to Path ensuring
// that there is one and only one path separator character between them
B1 := Path[PathLength] = DirDelimiter;
B2 := Append[1] = DirDelimiter;
if B1 and B2 then
Result := Copy(Path, 1, PathLength - 1) + Append
else
begin
if not (B1 or B2) then
Result := Path + DirDelimiter + Append
else
Result := Path + Append;
end;
end;
end;
end;
function PathBuildRoot(const Drive: Byte): string;
begin
{$IFDEF UNIX}
Result := DirDelimiter;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
// Remember, Win32 only allows 'a' to 'z' as drive letters (mapped to 0..25)
if Drive < 26 then
Result := Char(Drive + 65) + ':\'
else
raise EJclPathError.CreateResFmt(@RsPathInvalidDrive, [IntToStr(Drive)]);
{$ENDIF MSWINDOWS}
end;
function PathCanonicalize(const Path: string): string;
var
List: TStringList;
S: string;
I, K: Integer;
IsAbsolute: Boolean;
begin
I := Pos(':', Path); // for Windows' sake
K := Pos(DirDelimiter, Path);
IsAbsolute := K - I = 1;
if IsAbsolute then begin
if Copy(Path, 1, Length(PathUncPrefix)) = PathUncPrefix then // UNC path
K := 2;
end else
K := I;
if K = 0 then
S := Path
else
S := Copy(Path, K + 1, Length(Path));
List := TStringList.Create;
try
StrIToStrings(S, DirDelimiter, List, True);
I := 0;
while I < List.Count do
begin
if List[I] = '.' then
List.Delete(I)
else
if (IsAbsolute or (I > 0) and not (List[I-1] = '..')) and (List[I] = '..') then
begin
List.Delete(I);
if I > 0 then
begin
Dec(I);
List.Delete(I);
end;
end
else Inc(I);
end;
Result := StringsToStr(List, DirDelimiter, True);
finally
List.Free;
end;
if K > 0 then
Result := Copy(Path, 1, K) + Result
else
if Result = '' then
Result := '.';
end;
function PathCommonPrefix(const Path1, Path2: string): Integer;
var
Index1, Index2: Integer;
LastSeparator, LenS1: Integer;
S1, S2: string;
begin
Result := 0;
if (Path1 <> '') and (Path2 <> '') then
begin
// Initialize P1 to the shortest of the two paths so that the actual comparison loop below can
// use the terminating #0 of that string to terminate the loop.
if Length(Path1) <= Length(Path2) then
begin
S1 := Path1;
S2 := Path2;
end
else
begin
S1 := Path2;
S2 := Path1;
end;
Index1 := 1;
Index2 := 1;
LenS1 := Length(S1);
LastSeparator := 0;
while (S1[Index1] = S2[Index2]) and (Index1 <= LenS1) do
begin
Inc(Result);
if (S1[Index1] = DirDelimiter) or (S1[Index1] = ':') then
LastSeparator := Result;
Inc(Index1);
Inc(Index2);
end;
if (LastSeparator < Result) and (Index1 <= LenS1) then
Result := LastSeparator;
end;
end;
{$IFDEF MSWINDOWS}
function PathCompactPath(const DC: HDC; const Path: string;
const Width: Integer; CmpFmt: TCompactPath): string;
const
Compacts: array [TCompactPath] of Cardinal = (DT_PATH_ELLIPSIS, DT_END_ELLIPSIS);
var
TextRect: TRect;
Fmt: Cardinal;
begin
Result := '';
if (DC <> 0) and (Path <> '') and (Width > 0) then
begin
{ Here's a note from the Platform SDK to explain the + 5 in the call below:
"If dwDTFormat includes DT_MODIFYSTRING, the function could add up to four additional characters
to this string. The buffer containing the string should be large enough to accommodate these
extra characters." }
SetString(Result, PChar(Path), Length(Path) + 4);
TextRect := Rect(0, 0, Width, 255);
Fmt := DT_MODIFYSTRING or DT_CALCRECT or Compacts[CmpFmt];
if DrawTextEx(DC, PChar(Result), -1, TextRect, Fmt, nil) <> 0 then
StrResetLength(Result)
else
Result := ''; // in case of error
end;
end;
{$ENDIF MSWINDOWS}
procedure PathExtractElements(const Source: string; var Drive, Path, FileName, Ext: string);
begin
Drive := ExtractFileDrive(Source);
Path := ExtractFilePath(Source);
// Path includes drive so remove that
if Drive <> '' then
Delete(Path, 1, Length(Drive));
// add/remove separators
Drive := PathAddSeparator(Drive);
Path := PathRemoveSeparator(Path);
if (Path <> '') and (Path[1] = DirDelimiter) then
Delete(Path, 1, 1);
// and extract the remaining elements
FileName := PathExtractFileNameNoExt(Source);
Ext := ExtractFileExt(Source);
end;
function PathExtractFileDirFixed(const S: string): string;
begin
Result := PathAddSeparator(ExtractFileDir(S));
end;
function PathExtractFileNameNoExt(const Path: string): string;
begin
Result := PathRemoveExtension(ExtractFileName(Path));
end;
function PathExtractPathDepth(const Path: string; Depth: Integer): string;
var
List: TStringList;
LocalPath: string;
I: Integer;
begin
List := TStringList.Create;
try
if IsDirectory(Path) then
LocalPath := Path
else
LocalPath := ExtractFilePath(Path);
StrIToStrings(LocalPath, DirDelimiter, List, True);
I := Depth + 1;
if PathIsUNC(LocalPath) then
I := I + 2;
while I < List.Count do
List.Delete(I);
Result := PathAddSeparator(StringsToStr(List, DirDelimiter, True));
finally
List.Free;
end;
end;
// Notes: maybe this function should first apply PathCanonicalize() ?
function PathGetDepth(const Path: string): Integer;
var
List: TStringList;
LocalPath: string;
I, Start: Integer;
begin
Result := 0;
List := TStringList.Create;
try
if IsDirectory(Path) then
LocalPath := Path
else
LocalPath := ExtractFilePath(Path);
StrIToStrings(LocalPath, DirDelimiter, List, False);
if PathIsUNC(LocalPath) then
Start := 1
else
Start := 0;
for I := Start to List.Count - 1 do
begin
if Pos(':', List[I]) = 0 then
Inc(Result);
end;
finally
List.Free;
end;
end;
{$IFDEF MSWINDOWS}
{$IFDEF KEEP_DEPRECATED}
function PathGetLongName2(const Path: string): string;
begin
Result := PathGetLongName(Path);
end;
{$ENDIF KEEP_DEPRECATED}
function ShellGetLongPathName(const Path: string): string;
{$IFDEF FPC}
// As of 2004-10-17, FPC's ShlObj unit is just a dummy
begin
Result := Path;
end;
{$ElSE ~FPC}
var
PIDL: PItemIDList;
Desktop: IShellFolder;
{$IFNDEF SUPPORTS_UNICODE}
AnsiName: string;
WideName: array [0..MAX_PATH] of WideChar;
{$ENDIF ~SUPPORTS_UNICODE}
Eaten, Attr: ULONG; // both unused but API requires them (incorrect translation)
begin
Result := Path;
if Path <> '' then
begin
if Succeeded(SHGetDesktopFolder(Desktop)) then
begin
{$IFDEF SUPPORTS_UNICODE}
if Succeeded(Desktop.ParseDisplayName(0, nil, PChar(Path), Eaten, PIDL, Attr)) then
try
SetLength(Result, MAX_PATH);
if SHGetPathFromIDList(PIDL, PChar(Result)) then
StrResetLength(Result);
finally
CoTaskMemFree(PIDL);
end;
{$ELSE ~SUPPORTS_UNICODE}
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PAnsiChar(Path), -1, WideName, MAX_PATH);
if Succeeded(Desktop.ParseDisplayName(0, nil, WideName, Eaten, PIDL, Attr)) then
try
SetLength(AnsiName, MAX_PATH);
if SHGetPathFromIDList(PIDL, PChar(AnsiName)) then
StrResetLength(AnsiName);
Result := AnsiName;
finally
CoTaskMemFree(PIDL);
end;
{$ENDIF ~SUPPORTS_UNICODE}
end;
end;
end;
{$ENDIF ~FPC}
{ TODO : Move RTDL code over to JclWin32 when JclWin32 gets overhauled. }
var
_Kernel32Handle: TModuleHandle = INVALID_MODULEHANDLE_VALUE;
_GetLongPathName: function (lpszShortPath: PChar; lpszLongPath: PChar;
cchBuffer: DWORD): DWORD; stdcall;
function Kernel32Handle: HMODULE;
begin
JclSysUtils.LoadModule(_Kernel32Handle, kernel32);
Result := _Kernel32Handle;
end;
function RtdlGetLongPathName(const Path: string): string;
begin
Result := Path;
if not Assigned(_GetLongPathName) then
_GetLongPathName := GetModuleSymbol(Kernel32Handle, 'GetLongPathName' + AWSuffix);
if not Assigned(_GetLongPathName) then
Result := ShellGetLongPathName(Path)
else
begin
SetLength(Result, MAX_PATH);
SetLength(Result, _GetLongPathName(PChar(Path), PChar(Result), MAX_PATH));
end;
end;
function PathGetLongName(const Path: string): string;
begin
if Pos('::', Path) > 0 then // Path contains '::{<GUID>}'
Result := ShellGetLongPathName(Path)
else
Result := RtdlGetLongPathName(Path);
if Result = '' then
Result := Path;
end;
function PathGetShortName(const Path: string): string;
var
Required: Integer;
begin
Result := Path;
Required := GetShortPathName(PChar(Path), nil, 0);
if Required <> 0 then
begin
SetLength(Result, Required);
Required := GetShortPathName(PChar(Path), PChar(Result), Required);
if (Required <> 0) and (Required = Length(Result) - 1) then
SetLength(Result, Required)
else
Result := Path;
end;
end;
{$ENDIF MSWINDOWS}
function PathGetRelativePath(Origin, Destination: string): string;
var
{$IFDEF MSWINDOWS}
OrigDrive: string;
DestDrive: string;
{$ENDIF MSWINDOWS}
OrigList: TStringList;
DestList: TStringList;
DiffIndex: Integer;
I: Integer;
function StartsFromRoot(const Path: string): Boolean;
{$IFDEF MSWINDOWS}
var
I: Integer;
begin
I := Length(ExtractFileDrive(Path));
Result := (Length(Path) > I) and (Path[I + 1] = DirDelimiter);
end;
{$ELSE ~MSWINDOWS}
begin
Result := Pos(DirDelimiter, Path) = 1;
end;
{$ENDIF ~MSWINDOWS}
function Equal(const Path1, Path2: string): Boolean;
begin
{$IFDEF MSWINDOWS} // case insensitive
Result := StrSame(Path1, Path2);
{$ELSE ~MSWINDOWS} // case sensitive
Result := Path1 = Path2;
{$ENDIF ~MSWINDOWS}
end;
begin
Origin := PathCanonicalize(Origin);
Destination := PathCanonicalize(Destination);
{$IFDEF MSWINDOWS}
OrigDrive := ExtractFileDrive(Origin);
DestDrive := ExtractFileDrive(Destination);
{$ENDIF MSWINDOWS}
if Equal(Origin, Destination) or (Destination = '') then
Result := '.'
else
if Origin = '' then
Result := Destination
else
{$IFDEF MSWINDOWS}
if (DestDrive <> '') and ((OrigDrive = '') or ((OrigDrive <> '') and not Equal(OrigDrive, DestDrive))) then
Result := Destination
else
if (OrigDrive <> '') and (Pos(DirDelimiter, Destination) = 1)
and not Equal(PathUncPrefix,Copy(Destination,1,Length(PathUncPrefix))) then
Result := OrigDrive + Destination // prepend drive part from Origin
else
{$ENDIF MSWINDOWS}
if StartsFromRoot(Origin) and not StartsFromRoot(Destination) then
Result := StrEnsureSuffix(DirDelimiter, Origin) +
StrEnsureNoPrefix(DirDelimiter, Destination)
else
begin
// create a list of paths as separate strings
OrigList := TStringList.Create;
DestList := TStringList.Create;
try
// NOTE: DO NOT USE DELIMITER AND DELIMITEDTEXT FROM
// TSTRINGS, THEY WILL SPLIT PATHS WITH SPACES !!!!
StrToStrings(Origin, DirDelimiter, OrigList);
StrToStrings(Destination, DirDelimiter, DestList);
begin
// find the first directory that is not the same
DiffIndex := OrigList.Count;
if DestList.Count < DiffIndex then
DiffIndex := DestList.Count;
for I := 0 to DiffIndex - 1 do
if not Equal(OrigList[I], DestList[I]) then
begin
DiffIndex := I;
Break;
end;
Result := StrRepeat('..' + DirDelimiter, OrigList.Count - DiffIndex);
Result := PathRemoveSeparator(Result);
for I := DiffIndex to DestList.Count - 1 do
begin
if Result <> '' then
Result := Result + DirDelimiter;
Result := Result + DestList[i];
end;
end;
finally
DestList.Free;
OrigList.Free;
end;
end;
end;
function PathGetTempPath: string;
{$IFDEF MSWINDOWS}
var
BufSize: Cardinal;
begin
BufSize := Windows.GetTempPath(0, nil);
SetLength(Result, BufSize);
{ TODO : Check length (-1 or not) }
Windows.GetTempPath(BufSize, PChar(Result));
StrResetLength(Result);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
begin
Result := GetEnvironmentVariable('TMPDIR');
end;
{$ENDIF UNIX}
function PathIsAbsolute(const Path: string): Boolean;
{$IFDEF MSWINDOWS}
var
I: Integer;
{$ENDIF MSWINDOWS}
begin
Result := False;
if Path <> '' then
begin
{$IFDEF UNIX}
Result := (Path[1] = DirDelimiter);
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
if not PathIsUnc(Path) then
begin
I := 0;
if PathIsDiskDevice(Path) then
I := Length(PathDevicePrefix);
Result := (Length(Path) > I + 2) and CharIsDriveLetter(Path[I + 1]) and
(Path[I + 2] = ':') and (Path[I + 3] = DirDelimiter);
end
else
Result := True;
{$ENDIF MSWINDOWS}
end;
end;
function PathIsChild(const Path, Base: string): Boolean;
var
L: Integer;
B, P: string;
begin
Result := False;
B := PathRemoveSeparator(Base);
P := PathRemoveSeparator(Path);
// an empty path or one that's not longer than base cannot be a subdirectory
L := Length(B);
if (P = '') or (L >= Length(P)) then
Exit;
{$IFDEF MSWINDOWS}
Result := AnsiSameText(StrLeft(P, L), B) and (P[L+1] = DirDelimiter);
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
Result := AnsiSameStr(StrLeft(P, L), B) and (P[L+1] = DirDelimiter);
{$ENDIF UNIX}
end;
function PathIsDiskDevice(const Path: string): Boolean;
{$IFDEF UNIX}
var
FullPath: string;
F: PIOFile;
Buffer: array [0..255] of Char;
MountEntry: TMountEntry;
FsTypes: TStringList;
procedure GetAvailableFileSystems(const List: TStrings);
var
F: TextFile;
S: string;
begin
AssignFile(F, '/proc/filesystems');
Reset(F);
repeat
Readln(F, S);
if Pos('nodev', S) = 0 then // how portable is this ?
List.Add(Trim(S));
until Eof(F);
List.Add('supermount');
CloseFile(F);
end;
begin
Result := False;
SetLength(FullPath, _POSIX_PATH_MAX);
if realpath(PChar(Path), PChar(FullPath)) = nil then
RaiseLastOSError;
StrResetLength(FullPath);
FsTypes := TStringList.Create;
try
GetAvailableFileSystems(FsTypes);
F := setmntent(_PATH_MOUNTED, 'r'); // PATH_MOUNTED is deprecated,
// but PATH_MNTTAB is defective in Libc.pas
try
// get drives from mtab
while not Result and (getmntent_r(F, MountEntry, Buffer, SizeOf(Buffer)) <> nil) do
if FsTypes.IndexOf(MountEntry.mnt_type) <> -1 then
Result := MountEntry.mnt_dir = FullPath;
finally
endmntent(F);
end;
finally
FsTypes.Free;
end;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
begin
Result := Copy(Path, 1, Length(PathDevicePrefix)) = PathDevicePrefix;
end;
{$ENDIF MSWINDOWS}
function CharIsMachineName(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}
begin
case C of
'a'..'z',
'A'..'Z',
'-', '_', '.':
Result := True;
else
Result := False;
end;
end;
function CharIsInvalidPathCharacter(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}
begin
case C of
'<', '>', '?', '/', ',', '*', '+', '=', '[', ']', '|', ':', ';', '"', '''':
Result := True;
else
Result := False;
end;
end;
function PathIsUNC(const Path: string): Boolean;
{$IFDEF MSWINDOWS}
const
cUNCSuffix = '?\UNC';
var
P: PChar;
function AbsorbSeparator: Boolean;
begin
Result := (P <> nil) and (P^ = DirDelimiter);
if Result then
Inc(P);
end;
function AbsorbMachineName: Boolean;
var
NonDigitFound: Boolean;
begin
// a valid machine name is a string composed of the set [a-z, A-Z, 0-9, -, _] but it may not
// consist entirely out of numbers
Result := True;
NonDigitFound := False;
while (P^ <> #0) and (P^ <> DirDelimiter) do
begin
if CharIsMachineName(P^) then
begin
NonDigitFound := True;
Inc(P);
end
else
if CharIsDigit(P^) then
Inc(P)
else
begin
Result := False;
Break;
end;
end;
Result := Result and NonDigitFound;
end;
function AbsorbShareName: Boolean;
begin
// a valid share name is a string composed of a set the set !InvalidCharacters note that a
// leading '$' is valid (indicates a hidden share)
Result := True;
while (P^ <> #0) and (P^ <> DirDelimiter) do
begin
if CharIsInvalidPathCharacter(P^) then
begin
Result := False;
Break;
end;
Inc(P);
end;
end;
begin
Result := Copy(Path, 1, Length(PathUncPrefix)) = PathUncPrefix;
if Result then
begin
if Copy(Path, 1, Length(PathUncPrefix + cUNCSuffix)) = PathUncPrefix + cUNCSuffix then
P := @Path[Length(PathUncPrefix + cUNCSuffix)]
else
begin
P := @Path[Length(PathUncPrefix)];
Result := AbsorbSeparator and AbsorbMachineName;
end;
Result := Result and AbsorbSeparator;
if Result then
begin
Result := AbsorbShareName;
// remaining, if anything, is path and or filename (optional) check those?
end;
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
begin
Result := False;
end;
{$ENDIF UNIX}
function PathRemoveSeparator(const Path: string): string;
var
L: Integer;
begin
L := Length(Path);
if (L <> 0) and (Path[Length(Path)] = DirDelimiter) then
Result := Copy(Path, 1, L - 1)
else
Result := Path;
end;
function PathRemoveExtension(const Path: string): string;
var
I: Integer;
begin
I := LastDelimiter(':.' + DirDelimiter, Path);
if (I > 0) and (Path[I] = '.') then
Result := Copy(Path, 1, I - 1)
else
Result := Path;
end;
{$IFDEF MSWINDOWS}
function SHGetDisplayName(ShellFolder: IShellFolder; PIDL: PItemIDList; ForParsing: Boolean): string;
const
Flags: array[Boolean] of DWORD = (SHGDN_NORMAL, SHGDN_FORPARSING);
var
StrRet: TStrRet;
P: PChar;
begin
Result := '';
StrRet.utype := 0;
ShellFolder.GetDisplayNameOf(PIDL, Flags[ForParsing], StrRet);
case StrRet.uType of
STRRET_CSTR:
SetString(Result, StrRet.cStr, lstrlenA(StrRet.cStr));
STRRET_OFFSET:
begin
P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];
SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);
end;
STRRET_WSTR:
Result := StrRet.pOleStr;
end;
Result := Copy(Result, 1, lstrlen(PChar(Result)));
end;
function CutFirstDirectory(var Path: string): string;
var
ps: Integer;
begin
ps := AnsiPos(DirDelimiter, Path);
if ps > 0 then
begin
Result := Copy(Path, 1, ps - 1);
Path := Copy(Path, ps + 1, Length(Path));
end
else
begin
Result := Path;
Path := '';
end;
end;
function PathGetPhysicalPath(const LocalizedPath: string): string;
var
Malloc: IMalloc;
DesktopFolder: IShellFolder;
RootFolder: IShellFolder;
Eaten: Cardinal;
Attributes: Cardinal;
pidl: PItemIDList;
EnumIDL: IEnumIDList;
Drive: WideString;
Featched: Cardinal;
ParsePath: WideString;
Path, Name: string;
Found: Boolean;
begin
if StrCompareRange('\\', LocalizedPath, 1, 2) = 0 then
begin
Result := LocalizedPath;
Exit;
end;
Drive := ExtractFileDrive(LocalizedPath);
if Drive = '' then
begin
Result := LocalizedPath;
Exit;
end;
Path := Copy(LocalizedPath, Length(Drive) + 2, Length(LocalizedPath));
ParsePath := Drive;
OLECheck( SHGetMalloc(Malloc) );
OleCheck( SHGetDesktopFolder(DesktopFolder) );
while Path <> '' do
begin
Name := CutFirstDirectory(Path);
Found := False;
pidl := nil;
Attributes := 0;
if Succeeded( DesktopFolder.ParseDisplayName(0, nil, PWideChar(ParsePath), Eaten, pidl, Attributes) ) then
begin
OleCheck( DesktopFolder.BindToObject(pidl, nil, IShellFolder, RootFolder) );
Malloc.Free(pidl);
OleCheck( RootFolder.EnumObjects(0, SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN, EnumIDL) );
Featched := 0;
while EnumIDL.Next(1, pidl, Featched) = NOERROR do
begin
if AnsiCompareText(Name, SHGetDisplayName(RootFolder, pidl, False)) = 0 then
begin
ParsePath := SHGetDisplayName(RootFolder, pidl, True);
Malloc.Free(pidl);
Found := True;
Break;
end;
Malloc.Free(pidl);
end;
EnumIDL := nil;
RootFolder := nil;
end;
if not Found then
ParsePath := ParsePath + DirDelimiter + Name;
end;
Result := ParsePath;
end;
function PathGetLocalizedPath(const PhysicalPath: string): string;
var
Malloc: IMalloc;
DesktopFolder: IShellFolder;
RootFolder: IShellFolder;
Eaten: Cardinal;
Attributes: Cardinal;
pidl: PItemIDList;
EnumIDL: IEnumIDList;
Drive: WideString;
Featched: Cardinal;
ParsePath: WideString;
Path, Name, ParseName, DisplayName: string;
Found: Boolean;
begin
if StrCompareRange('\\', PhysicalPath, 1, 2) = 0 then
begin
Result := PhysicalPath;
Exit;
end;
Drive := ExtractFileDrive(PhysicalPath);
if Drive = '' then
begin
Result := PhysicalPath;
Exit;
end;
Path := Copy(PhysicalPath, Length(Drive) + 2, Length(PhysicalPath));
ParsePath := Drive;
Result := Drive;
OLECheck( SHGetMalloc(Malloc) );
OleCheck( SHGetDesktopFolder(DesktopFolder) );
while Path <> '' do
begin
Name := CutFirstDirectory(Path);
Found := False;
pidl := nil;
Attributes := 0;
if Succeeded( DesktopFolder.ParseDisplayName(0, nil, PWideChar(ParsePath), Eaten, pidl, Attributes) ) then
begin
OleCheck( DesktopFolder.BindToObject(pidl, nil, IShellFolder, RootFolder) );
Malloc.Free(pidl);
OleCheck( RootFolder.EnumObjects(0, SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN, EnumIDL) );
Featched := 0;
while EnumIDL.Next(1, pidl, Featched) = NOERROR do
begin
ParseName := SHGetDisplayName(RootFolder, pidl, True);
DisplayName := SHGetDisplayName(RootFolder, pidl, False);
Malloc.Free(pidl);
if (AnsiCompareText(Name, ExtractFileName(ParseName)) = 0) or
(AnsiCompareText(Name, DisplayName) = 0) then
begin
Name := DisplayName;
ParsePath := ParseName;
Found := True;
Break;
end;
end;
EnumIDL := nil;
RootFolder := nil;
end;
Result := Result + DirDelimiter + Name;
if not Found then
ParsePath := ParsePath + DirDelimiter + Name;
end;
end;
{$ELSE ~MSWINDOWS}
function PathGetPhysicalPath(const LocalizedPath: string): string;
begin
Result := LocalizedPath;
end;
function PathGetLocalizedPath(const PhysicalPath: string): string;
begin
Result := PhysicalPath;
end;
{$ENDIF ~MSWINDOWS}
//=== Files and Directories ==================================================
{* Extended version of JclFileUtils.BuildFileList:
function parameter Path can include multiple FileMasks as:
c:\aaa\*.pas; pro*.dpr; *.d??
FileMask Seperator = ';'
*}
function BuildFileList(const Path: string; const Attr: Integer;
const List: TStrings): Boolean;
var
SearchRec: TSearchRec;
IndexMask: Integer;
MaskList: TStringList;
Masks, Directory: string;
begin
Assert(List <> nil);
MaskList := TStringList.Create;
try
{* extract the Directory *}
Directory := ExtractFileDir(Path);
{* files can be searched in the current directory *}
if Directory <> '' then
begin
Directory := PathAddSeparator(Directory);
{* extract the FileMasks portion out of Path *}
Masks := StrAfter(Directory, Path);
end
else
Masks := Path;
{* put the Masks into TStringlist *}
StrTokenToStrings(Masks, DirSeparator, MaskList);
{* search all files in the directory *}
Result := FindFirst(Directory + '*', faAnyFile, SearchRec) = 0;
List.BeginUpdate;
try
while Result do
begin
{* if the filename matches any mask then it is added to the list *}
for IndexMask := 0 to MaskList.Count - 1 do
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..')
and ((SearchRec.Attr and Attr) = (SearchRec.Attr and faAnyFile))
and IsFileNameMatch(SearchRec.Name, MaskList.Strings[IndexMask]) then
begin
List.Add(SearchRec.Name);
Break;
end;
case FindNext(SearchRec) of
0:
;
ERROR_NO_MORE_FILES:
Break;
else
Result := False;
end;
end;
finally
SysUtils.FindClose(SearchRec);
List.EndUpdate;
end;
finally
MaskList.Free;
end;
end;
{$IFDEF MSWINDOWS}
procedure CreateEmptyFile(const FileName: string);
var
Handle: THandle;
begin
Handle := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
if Handle <> INVALID_HANDLE_VALUE then
CloseHandle(Handle)
else
RaiseLastOSError;
end;
{$ENDIF MSWINDOWS}
{$IFDEF MSWINDOWS}
function CloseVolume(var Volume: THandle): Boolean;
begin
Result := False;
if Volume <> INVALID_HANDLE_VALUE then
begin
Result := CloseHandle(Volume);
if Result then
Volume := INVALID_HANDLE_VALUE;
end;
end;
{$IFNDEF FPC} // needs JclShell
function DeleteDirectory(const DirectoryName: string; MoveToRecycleBin: Boolean): Boolean;
begin
if MoveToRecycleBin then
Result := SHDeleteFolder(0, DirectoryName, [doSilent, doAllowUndo])
else
Result := DelTree(DirectoryName);
end;
function CopyDirectory(ExistingDirectoryName, NewDirectoryName: string): Boolean;
var
SH: SHFILEOPSTRUCT;
begin
ResetMemory(SH, SizeOf(SH));
with SH do
begin
Wnd := 0;
wFunc := FO_COPY;
pFrom := PChar(PathRemoveSeparator(ExistingDirectoryName) + #0);
pTo := PChar(PathRemoveSeparator(NewDirectoryName) + #0);
fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR or FOF_SILENT;
end;
Result := SHFileOperation(SH) = 0;
end;
function MoveDirectory(ExistingDirectoryName, NewDirectoryName: string): Boolean;
var
SH: SHFILEOPSTRUCT;
begin
ResetMemory(SH, SizeOf(SH));
with SH do
begin
Wnd := 0;
wFunc := FO_MOVE;
pFrom := PChar(PathRemoveSeparator(ExistingDirectoryName) + #0);
pTo := PChar(PathRemoveSeparator(NewDirectoryName) + #0);
fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR or FOF_SILENT;
end;
Result := SHFileOperation(SH) = 0;
end;
{$ENDIF ~FPC}
function DelTree(const Path: string): Boolean;
begin
Result := DelTreeEx(Path, False, nil);
end;
function DelTreeEx(const Path: string; AbortOnFailure: Boolean; Progress: TDelTreeProgress): Boolean;
var
Files: TStringList;
LPath: string; // writable copy of Path
FileName: string;
I: Integer;
PartialResult: Boolean;
Attr: DWORD;
begin
Assert(Path <> '', LoadResString(@RsDelTreePathIsEmpty));
{$IFNDEF ASSERTIONS_ON}
if Path = '' then
begin
Result := False;
Exit;
end;
{$ENDIF ~ASSERTIONS_ON}
Result := True;
Files := TStringList.Create;
try
LPath := PathRemoveSeparator(Path);
BuildFileList(LPath + '\*.*', faAnyFile, Files);
for I := 0 to Files.Count - 1 do
begin
FileName := LPath + DirDelimiter + Files[I];
PartialResult := True;
// If the current file is itself a directory then recursively delete it
Attr := GetFileAttributes(PChar(FileName));
if (Attr <> DWORD(-1)) and ((Attr and FILE_ATTRIBUTE_DIRECTORY) <> 0) then
PartialResult := DelTreeEx(FileName, AbortOnFailure, Progress)
else
begin
if Assigned(Progress) then
PartialResult := Progress(FileName, Attr);
if PartialResult then
begin
// Set attributes to normal in case it's a readonly file
PartialResult := SetFileAttributes(PChar(FileName), FILE_ATTRIBUTE_NORMAL);
if PartialResult then
PartialResult := DeleteFile(FileName);
end;
end;
if not PartialResult then
begin
Result := False;
if AbortOnFailure then
Break;
end;
end;
finally
FreeAndNil(Files);
end;
if Result then
begin
// Finally remove the directory itself
Result := SetFileAttributes(PChar(LPath), FILE_ATTRIBUTE_NORMAL);
if Result then
begin
{$IOCHECKS OFF}
RmDir(LPath);
{$IFDEF IOCHECKS_ON}
{$IOCHECKS ON}
{$ENDIF IOCHECKS_ON}
Result := IOResult = 0;
end;
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF MSWINDOWS}
function DirectoryExists(const Name: string): Boolean;
var
R: DWORD;
begin
R := GetFileAttributes(PChar(Name));
Result := (R <> DWORD(-1)) and ((R and FILE_ATTRIBUTE_DIRECTORY) <> 0);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function DirectoryExists(const Name: string; ResolveSymLinks: Boolean): Boolean;
begin
Result := IsDirectory(Name, ResolveSymLinks);
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function DiskInDrive(Drive: Char): Boolean;
var
ErrorMode: Cardinal;
begin
Result := False;
Assert(CharIsDriveLetter(Drive));
if CharIsDriveLetter(Drive) then
begin
Drive := CharUpper(Drive);
{ try to access the drive, it doesn't really matter how we access the drive and as such calling
DiskSize is more or less a random choice. The call to SetErrorMode supresses the system provided
error dialog if there is no disk in the drive and causes the to DiskSize to fail. }
ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
try
Result := DiskSize(Ord(Drive) - $40) <> -1;
finally
SetErrorMode(ErrorMode);
end;
end;
end;
{$ENDIF MSWINDOWS}
function FileCreateTemp(var Prefix: string): THandle;
{$IFDEF MSWINDOWS}
var
TempName: string;
begin
Result := INVALID_HANDLE_VALUE;
TempName := FileGetTempName(Prefix);
if TempName <> '' then
begin
Result := CreateFile(PChar(TempName), GENERIC_READ or GENERIC_WRITE, 0, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
// In certain situations it's possible that CreateFile fails yet the file is actually created,
// therefore explicitly delete it upon failure.
if Result = INVALID_HANDLE_VALUE then
DeleteFile(TempName);
Prefix := TempName;
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
var
Template: string;
begin
// The mkstemp function generates a unique file name just as mktemp does, but
// it also opens the file for you with open. If successful, it modifies
// template in place and returns a file descriptor for that file open for
// reading and writing. If mkstemp cannot create a uniquely-named file, it
// returns -1. If template does not end with `XXXXXX', mkstemp returns -1 and
// does not modify template.
// The file is opened using mode 0600. If the file is meant to be used by
// other users this mode must be changed explicitly.
// Unlike mktemp, mkstemp is actually guaranteed to create a unique file that
// cannot possibly clash with any other program trying to create a temporary
// file. This is because it works by calling open with the O_EXCL flag, which
// says you want to create a new file and get an error if the file already
// exists.
Template := Prefix + 'XXXXXX';
Result := mkstemp(PChar(Template));
Prefix := Template;
end;
{$ENDIF UNIX}
function FileBackup(const FileName: string; Move: Boolean = False): Boolean;
begin
if Move then
Result := FileMove(FileName, GetBackupFileName(FileName), True)
else
Result := FileCopy(FileName, GetBackupFileName(FileName), True);
end;
function FileCopy(const ExistingFileName, NewFileName: string; ReplaceExisting: Boolean = False): Boolean;
var
{$IFDEF UNIX}
SrcFile, DstFile: file;
Buf: array[0..511] of Byte;
BytesRead: Integer;
{$ENDIF UNIX}
DestFileName: string;
begin
if IsDirectory(NewFileName) then
DestFileName := PathAddSeparator(NewFileName) + ExtractFileName(ExistingFileName)
else
DestFileName := NewFileName;
{$IFDEF MSWINDOWS}
{ TODO : Use CopyFileEx where available? }
Result := CopyFile(PChar(ExistingFileName), PChar(DestFileName), not ReplaceExisting);
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
Result := False;
if not FileExists(DestFileName) or ReplaceExisting then
begin
AssignFile(SrcFile, ExistingFileName);
Reset(SrcFile, 1);
AssignFile(DstFile, DestFileName);
Rewrite(DstFile, 1);
while not Eof(SrcFile) do
begin
BlockRead(SrcFile, Buf, SizeOf(Buf), BytesRead);
BlockWrite(DstFile, Buf, BytesRead);
end;
CloseFile(DstFile);
CloseFile(SrcFile);
Result := True;
end;
{$ENDIF UNIX}
end;
function FileDelete(const FileName: string; MoveToRecycleBin: Boolean = False): Boolean;
{$IFDEF MSWINDOWS}
begin
if MoveToRecycleBin then
Result := SHDeleteFiles(0, FileName, [doSilent, doAllowUndo, doFilesOnly])
else
Result := Windows.DeleteFile(PChar(FileName));
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
{ TODO : implement MoveToRecycleBin for appropriate Desktops (e.g. KDE) }
begin
Result := remove(PChar(FileName)) <> -1;
end;
{$ENDIF UNIX}
function FileExists(const FileName: string): Boolean;
{$IFDEF MSWINDOWS}
var
Attr: Cardinal;
{$ENDIF MSWINDOWS}
begin
if FileName <> '' then
begin
{$IFDEF MSWINDOWS}
// FileGetSize is very slow, GetFileAttributes is much faster
Attr := GetFileAttributes(Pointer(Filename));
Result := (Attr <> $FFFFFFFF) and (Attr and FILE_ATTRIBUTE_DIRECTORY = 0);
{$ELSE ~MSWINDOWS}
// Attempt to access the file, doesn't matter how, using FileGetSize is as good as anything else.
Result := FileGetSize(FileName) <> -1;
{$ENDIF ~MSWINDOWS}
end
else
Result := False;
end;
function FileMove(const ExistingFileName, NewFileName: string; ReplaceExisting: Boolean = False): Boolean;
{$IFDEF MSWINDOWS}
const
Flag: array[Boolean] of Cardinal = (0, MOVEFILE_REPLACE_EXISTING);
{$ENDIF MSWINDOWS}
begin
{$IFDEF MSWINDOWS}
Result := MoveFileEx(PChar(ExistingFileName), PChar(NewFileName), Flag[ReplaceExisting]);
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
Result := __rename(PChar(ExistingFileName), PChar(NewFileName)) = 0;
{$ENDIF UNIX}
if not Result then
begin
Result := FileCopy(ExistingFileName, NewFileName, ReplaceExisting);
if Result then
FileDelete(ExistingFileName);
end;
end;
function FileRestore(const FileName: string): Boolean;
var
TempFileName: string;
begin
Result := False;
TempFileName := FileGetTempName('');
if FileMove(GetBackupFileName(FileName), TempFileName, True) then
if FileBackup(FileName, False) then
Result := FileMove(TempFileName, FileName, True);
end;
function GetBackupFileName(const FileName: string): string;
var
NewExt: string;
begin
NewExt := ExtractFileExt(FileName);
if Length(NewExt) > 0 then
begin
NewExt[1] := '~';
NewExt := '.' + NewExt
end
else
NewExt := '.~';
Result := ChangeFileExt(FileName, NewExt);
end;
function IsBackupFileName(const FileName: string): Boolean;
begin
Result := (pos('.~', ExtractFileExt(FileName)) = 1);
end;
function FileGetDisplayName(const FileName: string): string;
{$IFDEF MSWINDOWS}
var
FileInfo: TSHFileInfo;
begin
ResetMemory(FileInfo, SizeOf(FileInfo));
if SHGetFileInfo(PChar(FileName), 0, FileInfo, SizeOf(FileInfo), SHGFI_DISPLAYNAME) <> 0 then
Result := FileInfo.szDisplayName
else
Result := FileName;
end;
{$ELSE ~MSWINDOWS}
begin
{ TODO -cHelp : mention this reduced solution }
Result := FileName;
end;
{$ENDIF ~MSWINDOWS}
function FileGetGroupName(const FileName: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): string;
{$IFDEF MSWINDOWS}
var
DomainName: WideString;
TmpResult: WideString;
pSD: PSecurityDescriptor;
BufSize: DWORD;
begin
if IsWinNT then
begin
BufSize := 0;
GetFileSecurity(PChar(FileName), GROUP_SECURITY_INFORMATION, nil, 0, BufSize);
if BufSize > 0 then
begin
GetMem(pSD, BufSize);
GetFileSecurity(PChar(FileName), GROUP_SECURITY_INFORMATION,
pSD, BufSize, BufSize);
LookupAccountBySid(Pointer(TJclAddr(pSD) + TJclAddr(pSD^.Group)), TmpResult, DomainName);
FreeMem(pSD);
Result := Trim(TmpResult);
end;
end;
end;
{$ENDIF ~MSWINDOWS}
{$IFDEF UNIX}
var
Buf: TStatBuf64;
ResultBuf: TGroup;
ResultBufPtr: PGroup;
Buffer: array of Char;
begin
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
begin
SetLength(Buffer, 128);
while getgrgid_r(Buf.st_gid, ResultBuf, @Buffer[0], Length(Buffer), ResultBufPtr) = ERANGE do
SetLength(Buffer, Length(Buffer) * 2);
Result := ResultBuf.gr_name;
end;
end;
{$ENDIF ~UNIX}
function FileGetOwnerName(const FileName: string {$IFDEF UNIX}; ResolveSymLinks: Boolean = True {$ENDIF}): string;
{$IFDEF MSWINDOWS}
var
DomainName: WideString;
TmpResult: WideString;
pSD: PSecurityDescriptor;
BufSize: DWORD;
begin
if IsWinNT then
begin
BufSize := 0;
GetFileSecurity(PChar(FileName), OWNER_SECURITY_INFORMATION, nil, 0, BufSize);
if BufSize > 0 then
begin
GetMem(pSD, BufSize);
try
GetFileSecurity(PChar(FileName), OWNER_SECURITY_INFORMATION,
pSD, BufSize, BufSize);
LookupAccountBySid(Pointer(TJclAddr(pSD) + TJclAddr(pSD^.Owner)), TmpResult, DomainName);
finally
FreeMem(pSD);
end;
Result := Trim(TmpResult);
end;
end;
end;
{$ENDIF ~MSWINDOWS}
{$IFDEF UNIX}
var
Buf: TStatBuf64;
ResultBuf: TPasswordRecord;
ResultBufPtr: PPasswordRecord;
Buffer: array of Char;
begin
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
begin
SetLength(Buffer, 128);
while getpwuid_r(Buf.st_uid, ResultBuf, @Buffer[0], Length(Buffer), ResultBufPtr) = ERANGE do
SetLength(Buffer, Length(Buffer) * 2);
Result := ResultBuf.pw_name;
end;
end;
{$ENDIF ~UNIX}
function FileGetSize(const FileName: string): Int64;
{$IFDEF MSWINDOWS}
var
FileAttributesEx: WIN32_FILE_ATTRIBUTE_DATA;
OldMode: Cardinal;
Size: TJclULargeInteger;
begin
Result := -1;
OldMode := SetErrorMode(SEM_FAILCRITICALERRORS);
try
if GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @FileAttributesEx) then
begin
Size.LowPart := FileAttributesEx.nFileSizeLow;
Size.HighPart := FileAttributesEx.nFileSizeHigh;
Result := Size.QuadPart;
end;
finally
SetErrorMode(OldMode);
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
var
Buf: TStatBuf64;
begin
Result := -1;
if GetFileStatus(FileName, Buf, False) = 0 then
Result := Buf.st_size;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
{$IFDEF FPC}
{ TODO : Move this over to JclWin32 when JclWin32 gets overhauled. }
function GetTempFileName(lpPathName, lpPrefixString: PChar;
uUnique: UINT; lpTempFileName: PChar): UINT; stdcall;
external kernel32 name 'GetTempFileNameA';
{$ENDIF FPC}
{$ENDIF MSWINDOWS}
function FileGetTempName(const Prefix: string): string;
{$IFDEF MSWINDOWS}
var
TempPath, TempFile: string;
R: Cardinal;
begin
Result := '';
TempPath := PathGetTempPath;
if TempPath <> '' then
begin
SetLength(TempFile, MAX_PATH);
R := GetTempFileName(PChar(TempPath), PChar(Prefix), 0, PChar(TempFile));
if R <> 0 then
begin
StrResetLength(TempFile);
Result := TempFile;
end;
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
// Warning: Between the time the pathname is constructed and the file is created
// another process might have created a file with the same name using tmpnam,
// leading to a possible security hole. The implementation generates names which
// can hardly be predicted, but when opening the file you should use the O_EXCL
// flag. Using tmpfile or mkstemp is a safe way to avoid this problem.
var
P: PChar;
begin
P := tempnam(PChar(PathGetTempPath), PChar(Prefix));
Result := P;
Libc.free(P);
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function FileGetTypeName(const FileName: string): string;
var
FileInfo: TSHFileInfo;
RetVal: DWORD;
begin
ResetMemory(FileInfo, SizeOf(FileInfo));
RetVal := SHGetFileInfo(PChar(FileName), 0, FileInfo, SizeOf(FileInfo),
SHGFI_TYPENAME or SHGFI_USEFILEATTRIBUTES);
if RetVal <> 0 then
Result := FileInfo.szTypeName;
if (RetVal = 0) or (Trim(Result) = '') then
begin
// Lookup failed so mimic explorer behaviour by returning "XYZ File"
Result := ExtractFileExt(FileName);
Delete(Result, 1, 1);
Result := TrimLeft(UpperCase(Result) + LoadResString(@RsDefaultFileTypeName));
end;
end;
{$ENDIF MSWINDOWS}
function FindUnusedFileName(FileName: string; const FileExt: string; NumberPrefix: string = ''): string;
var
I: Integer;
begin
Result := PathAddExtension(FileName, FileExt);
if not FileExists(Result) then
Exit;
if SameText(Result, FileName) then
Delete(FileName, Length(FileName) - Length(FileExt) + 1, Length(FileExt));
I := 0;
repeat
Inc(I);
Result := PathAddExtension(FileName + NumberPrefix + IntToStr(I), FileExt);
until not FileExists(Result);
end;
// This routine is copied from FileCtrl.pas to avoid dependency on that unit.
// See the remark at the top of this section
function ForceDirectories(Name: string): Boolean;
var
ExtractPath: string;
begin
Result := True;
if Length(Name) = 0 then
raise EJclFileUtilsError.CreateRes(@RsCannotCreateDir);
Name := PathRemoveSeparator(Name);
{$IFDEF MSWINDOWS}
ExtractPath := ExtractFilePath(Name);
if ((Length(Name) = 2) and (Copy(Name, 2,1) = ':')) or DirectoryExists(Name) or (ExtractPath = Name) then
Exit;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
if (Length(Name) = 0) or DirectoryExists(Name) then
Exit;
ExtractPath := ExtractFilePath(Name);
{$ENDIF UNIX}
if ExtractPath = '' then
Result := CreateDir(Name)
else
Result := ForceDirectories(ExtractPath) and CreateDir(Name);
end;
function GetDirectorySize(const Path: string): Int64;
function RecurseFolder(const Path: string): Int64;
var
F: TSearchRec;
R: Integer;
{$IFDEF MSWINDOWS}
TempSize: TJclULargeInteger;
{$ENDIF MSWINDOWS}
begin
Result := 0;
R := SysUtils.FindFirst(Path + '*.*', faAnyFile, F);
if R = 0 then
try
while R = 0 do
begin
if (F.Name <> '.') and (F.Name <> '..') then
begin
if (F.Attr and faDirectory) = faDirectory then
Inc(Result, RecurseFolder(Path + F.Name + DirDelimiter))
else
{$IFDEF MSWINDOWS}
begin
TempSize.LowPart := F.FindData.nFileSizeLow;
TempSize.HighPart := F.FindData.nFileSizeHigh;
Inc(Result, TempSize.QuadPart);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
// SysUtils.Find* don't perceive files >= 2 GB anyway
Inc(Result, Int64(F.Size));
{$ENDIF UNIX}
end;
R := SysUtils.FindNext(F);
end;
if R <> ERROR_NO_MORE_FILES then
Abort;
finally
SysUtils.FindClose(F);
end;
end;
begin
if not DirectoryExists(PathRemoveSeparator(Path)) then
Result := -1
else
try
Result := RecurseFolder(PathAddSeparator(Path))
except
Result := -1;
end;
end;
{$IFDEF MSWINDOWS}
function GetDriveTypeStr(const Drive: Char): string;
var
DriveType: Integer;
DriveStr: string;
begin
if not CharIsDriveLetter(Drive) then
raise EJclPathError.CreateResFmt(@RsPathInvalidDrive, [Drive]);
DriveStr := Drive + ':\';
DriveType := GetDriveType(PChar(DriveStr));
case DriveType of
DRIVE_REMOVABLE:
Result := LoadResString(@RsRemovableDrive);
DRIVE_FIXED:
Result := LoadResString(@RsHardDisk);
DRIVE_REMOTE:
Result := LoadResString(@RsRemoteDrive);
DRIVE_CDROM:
Result := LoadResString(@RsCDRomDrive);
DRIVE_RAMDISK:
Result := LoadResString(@RsRamDisk);
else
Result := LoadResString(@RsUnknownDrive);
end;
end;
function GetFileAgeCoherence(const FileName: string): Boolean;
var
FileAttributesEx: WIN32_FILE_ATTRIBUTE_DATA;
begin
Result := False;
if GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @FileAttributesEx) then
{$IFDEF FPC}
Result := CompareFileTime(@FileAttributesEx.ftCreationTime, @FileAttributesEx.ftLastWriteTime) <= 0;
{$ELSE ~FPC}
Result := CompareFileTime(FileAttributesEx.ftCreationTime, FileAttributesEx.ftLastWriteTime) <= 0;
{$ENDIF ~FPC}
end;
{$ENDIF MSWINDOWS}
procedure GetFileAttributeList(const Items: TStrings; const Attr: Integer);
begin
{ TODO : clear list? }
Assert(Assigned(Items));
if not Assigned(Items) then
Exit;
Items.BeginUpdate;
try
{ TODO : differentiate Windows/UNIX idents }
if Attr and faDirectory = faDirectory then
Items.Add(LoadResString(@RsAttrDirectory));
if Attr and faReadOnly = faReadOnly then
Items.Add(LoadResString(@RsAttrReadOnly));
if Attr and faSysFile = faSysFile then
Items.Add(LoadResString(@RsAttrSystemFile));
if Attr and faArchive = faArchive then
Items.Add(LoadResString(@RsAttrArchive));
if Attr and faAnyFile = faAnyFile then
Items.Add(LoadResString(@RsAttrAnyFile));
if Attr and faHidden = faHidden then
Items.Add(LoadResString(@RsAttrHidden));
finally
Items.EndUpdate;
end;
end;
{$IFDEF MSWINDOWS}
{ TODO : GetFileAttributeListEx - Unix version }
procedure GetFileAttributeListEx(const Items: TStrings; const Attr: Integer);
begin
{ TODO : clear list? }
Assert(Assigned(Items));
if not Assigned(Items) then
Exit;
Items.BeginUpdate;
try
if Attr and FILE_ATTRIBUTE_READONLY = FILE_ATTRIBUTE_READONLY then
Items.Add(LoadResString(@RsAttrReadOnly));
if Attr and FILE_ATTRIBUTE_HIDDEN = FILE_ATTRIBUTE_HIDDEN then
Items.Add(LoadResString(@RsAttrHidden));
if Attr and FILE_ATTRIBUTE_SYSTEM = FILE_ATTRIBUTE_SYSTEM then
Items.Add(LoadResString(@RsAttrSystemFile));
if Attr and FILE_ATTRIBUTE_DIRECTORY = FILE_ATTRIBUTE_DIRECTORY then
Items.Add(LoadResString(@RsAttrDirectory));
if Attr and FILE_ATTRIBUTE_ARCHIVE = FILE_ATTRIBUTE_ARCHIVE then
Items.Add(LoadResString(@RsAttrArchive));
if Attr and FILE_ATTRIBUTE_NORMAL = FILE_ATTRIBUTE_NORMAL then
Items.Add(LoadResString(@RsAttrNormal));
if Attr and FILE_ATTRIBUTE_TEMPORARY = FILE_ATTRIBUTE_TEMPORARY then
Items.Add(LoadResString(@RsAttrTemporary));
if Attr and FILE_ATTRIBUTE_COMPRESSED = FILE_ATTRIBUTE_COMPRESSED then
Items.Add(LoadResString(@RsAttrCompressed));
if Attr and FILE_ATTRIBUTE_OFFLINE = FILE_ATTRIBUTE_OFFLINE then
Items.Add(LoadResString(@RsAttrOffline));
if Attr and FILE_ATTRIBUTE_ENCRYPTED = FILE_ATTRIBUTE_ENCRYPTED then
Items.Add(LoadResString(@RsAttrEncrypted));
if Attr and FILE_ATTRIBUTE_REPARSE_POINT = FILE_ATTRIBUTE_REPARSE_POINT then
Items.Add(LoadResString(@RsAttrReparsePoint));
if Attr and FILE_ATTRIBUTE_SPARSE_FILE = FILE_ATTRIBUTE_SPARSE_FILE then
Items.Add(LoadResString(@RsAttrSparseFile));
finally
Items.EndUpdate;
end;
end;
{$ENDIF MSWINDOWS}
function GetFileInformation(const FileName: string; out FileInfo: TSearchRec): Boolean;
begin
Result := FindFirst(FileName, faAnyFile, FileInfo) = 0;
if Result then
SysUtils.FindClose(FileInfo);
end;
function GetFileInformation(const FileName: string): TSearchRec;
begin
if not GetFileInformation(FileName, Result) then
RaiseLastOSError;
end;
{$IFDEF UNIX}
{ TODO -cHelp : Author: Robert Rossmair }
function GetFileStatus(const FileName: string; out StatBuf: TStatBuf64;
const ResolveSymLinks: Boolean): Integer;
begin
if ResolveSymLinks then
Result := stat64(PChar(FileName), StatBuf)
else
Result := lstat64(PChar(FileName), StatBuf);
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function GetFileLastWrite(const FileName: string): TFileTime;
begin
Result := GetFileInformation(FileName).FindData.ftLastWriteTime;
end;
function GetFileLastWrite(const FileName: string; out LocalTime: TDateTime): Boolean;
var
FileInfo: TSearchRec;
begin
Result := GetFileInformation(FileName, FileInfo);
if Result then
LocalTime := FileTimeToLocalDateTime(GetFileInformation(FileName).FindData.ftLastWriteTime);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function GetFileLastWrite(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
TimeStamp := Buf.st_mtime
end;
function GetFileLastWrite(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
LocalTime := FileDateToDateTime(Buf.st_mtime);
end;
function GetFileLastWrite(const FileName: string; ResolveSymLinks: Boolean): Integer;
var
Buf: TStatBuf64;
begin
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
Result := Buf.st_mtime
else
Result := -1;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function GetFileLastAccess(const FileName: string): TFileTime;
begin
Result := GetFileInformation(FileName).FindData.ftLastAccessTime;
end;
function GetFileLastAccess(const FileName: string; out LocalTime: TDateTime): Boolean;
var
FileInfo: TSearchRec;
begin
Result := GetFileInformation(FileName, FileInfo);
if Result then
LocalTime := FileTimeToLocalDateTime(GetFileInformation(FileName).FindData.ftLastAccessTime);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function GetFileLastAccess(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
TimeStamp := Buf.st_atime
end;
function GetFileLastAccess(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
LocalTime := FileDateToDateTime(Buf.st_atime);
end;
function GetFileLastAccess(const FileName: string; ResolveSymLinks: Boolean): Integer;
var
Buf: TStatBuf64;
begin
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
Result := Buf.st_atime
else
Result := -1;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function GetFileCreation(const FileName: string): TFileTime;
begin
Result := GetFileInformation(FileName).FindData.ftCreationTime;
end;
function GetFileCreation(const FileName: string; out LocalTime: TDateTime): Boolean;
var
FileInfo: TSearchRec;
begin
Result := GetFileInformation(FileName, FileInfo);
if Result then
LocalTime := FileTimeToLocalDateTime(GetFileInformation(FileName).FindData.ftCreationTime);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function GetFileLastAttrChange(const FileName: string; out TimeStamp: Integer; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
TimeStamp := Buf.st_ctime
end;
function GetFileLastAttrChange(const FileName: string; out LocalTime: TDateTime; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := GetFileStatus(FileName, Buf, ResolveSymLinks) = 0;
if Result then
LocalTime := FileDateToDateTime(Buf.st_ctime);
end;
function GetFileLastAttrChange(const FileName: string; ResolveSymLinks: Boolean): Integer;
var
Buf: TStatBuf64;
begin
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
Result := Buf.st_ctime
else
Result := -1;
end;
{$ENDIF UNIX}
function GetModulePath(const Module: HMODULE): string;
var
L: Integer;
begin
L := MAX_PATH + 1;
SetLength(Result, L);
{$IFDEF MSWINDOWS}
L := Windows.GetModuleFileName(Module, Pointer(Result), L);
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
{$IFDEF FPC}
L := 0; // FIXME
{$ELSE ~FPC}
L := GetModuleFileName(Module, Pointer(Result), L);
{$ENDIF ~FPC}
{$ENDIF UNIX}
SetLength(Result, L);
end;
function GetSizeOfFile(const FileName: string): Int64;
{$IFDEF MSWINDOWS}
var
FileAttributesEx: WIN32_FILE_ATTRIBUTE_DATA;
Size: TJclULargeInteger;
begin
Result := 0;
if GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @FileAttributesEx) then
begin
Size.LowPart := FileAttributesEx.nFileSizeLow;
Size.HighPart := FileAttributesEx.nFileSizeHigh;
Result := Size.QuadPart;
end
else
RaiseLastOSError;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
var
Buf: TStatBuf64;
begin
if GetFileStatus(FileName, Buf, False) <> 0 then
RaiseLastOSError;
Result := Buf.st_size;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function GetSizeOfFile(Handle: THandle): Int64; overload;
var
Size: TJclULargeInteger;
begin
Size.LowPart := GetFileSize(Handle, @Size.HighPart);
Result := Size.QuadPart;
end;
{$ENDIF MSWINDOWS}
function GetSizeOfFile(const FileInfo: TSearchRec): Int64;
{$IFDEF MSWINDOWS}
begin
with Int64Rec(Result) do
begin
Lo := FileInfo.FindData.nFileSizeLow;
Hi := FileInfo.FindData.nFileSizeHigh;
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
var
Buf: TStatBuf64;
begin
// rr: Note that SysUtils.FindFirst/Next ignore files >= 2 GB under Linux,
// thus the following code is rather pointless at the moment of this writing.
// We apparently need to write our own set of Findxxx functions to overcome this limitation.
if GetFileStatus(FileInfo.PathOnly + FileInfo.Name, Buf, True) <> 0 then
Result := -1
else
Result := Buf.st_size
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
{$IFDEF FPC}
{ TODO : Move this over to JclWin32 when JclWin32 gets overhauled. }
function GetFileAttributesEx(lpFileName: PChar;
fInfoLevelId: TGetFileExInfoLevels; lpFileInformation: Pointer): BOOL; stdcall;
external kernel32 name 'GetFileAttributesExA';
{$ENDIF FPC}
function GetStandardFileInfo(const FileName: string): TWin32FileAttributeData;
var
Handle: THandle;
FileInfo: TByHandleFileInformation;
begin
Assert(FileName <> '');
{ TODO : Use RTDL-Version of GetFileAttributesEx }
if IsWin95 or IsWin95OSR2 or IsWinNT3 then
begin
Handle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
if Handle <> INVALID_HANDLE_VALUE then
try
FileInfo.dwFileAttributes := 0;
if not GetFileInformationByHandle(Handle, FileInfo) then
raise EJclFileUtilsError.CreateResFmt(@RsFileUtilsAttrUnavailable, [FileName]);
Result.dwFileAttributes := FileInfo.dwFileAttributes;
Result.ftCreationTime := FileInfo.ftCreationTime;
Result.ftLastAccessTime := FileInfo.ftLastAccessTime;
Result.ftLastWriteTime := FileInfo.ftLastWriteTime;
Result.nFileSizeHigh := FileInfo.nFileSizeHigh;
Result.nFileSizeLow := FileInfo.nFileSizeLow;
finally
CloseHandle(Handle);
end
else
raise EJclFileUtilsError.CreateResFmt(@RsFileUtilsAttrUnavailable, [FileName]);
end
else
begin
if not GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @Result) then
raise EJclFileUtilsError.CreateResFmt(@RsFileUtilsAttrUnavailable, [FileName]);
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF MSWINDOWS}
function IsDirectory(const FileName: string): Boolean;
var
R: DWORD;
begin
R := GetFileAttributes(PChar(FileName));
Result := (R <> DWORD(-1)) and ((R and FILE_ATTRIBUTE_DIRECTORY) <> 0);
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function IsDirectory(const FileName: string; ResolveSymLinks: Boolean): Boolean;
var
Buf: TStatBuf64;
begin
Result := False;
if GetFileStatus(FileName, Buf, ResolveSymLinks) = 0 then
Result := S_ISDIR(Buf.st_mode);
end;
{$ENDIF UNIX}
function IsRootDirectory(const CanonicFileName: string): Boolean;
{$IFDEF MSWINDOWS}
var
I: Integer;
begin
I := Pos(':\', CanonicFileName);
Result := (I > 0) and (I + 1 = Length(CanonicFileName));
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
begin
Result := CanonicFileName = DirDelimiter;
end;
{$ENDIF UNIX}
{$IFDEF MSWINDOWS}
function LockVolume(const Volume: string; var Handle: THandle): Boolean;
var
BytesReturned: DWORD;
begin
Result := False;
Handle := CreateFile(PChar('\\.\' + Volume), GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING, 0);
if Handle <> INVALID_HANDLE_VALUE then
begin
BytesReturned := 0;
Result := DeviceIoControl(Handle, FSCTL_LOCK_VOLUME, nil, 0, nil, 0,
BytesReturned, nil);
if not Result then
begin
CloseHandle(Handle);
Handle := INVALID_HANDLE_VALUE;
end;
end;
end;
function OpenVolume(const Drive: Char): THandle;
var
VolumeName: array [0..6] of Char;
begin
VolumeName := '\\.\A:';
VolumeName[4] := Drive;
Result := CreateFile(VolumeName, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, 0, 0);
end;
{$ENDIF MSWINDOWS}
type
// indicates the file time to set, used by SetFileTimesHelper and SetDirTimesHelper
TFileTimes = (ftLastAccess, ftLastWrite {$IFDEF MSWINDOWS}, ftCreation {$ENDIF});
{$IFDEF MSWINDOWS}
function SetFileTimesHelper(const FileName: string; const DateTime: TDateTime; Times: TFileTimes): Boolean;
var
Handle: THandle;
FileTime: TFileTime;
SystemTime: TSystemTime;
begin
Result := False;
Handle := CreateFile(PChar(FileName), GENERIC_WRITE, FILE_SHARE_READ, nil,
OPEN_EXISTING, 0, 0);
if Handle <> INVALID_HANDLE_VALUE then
try
//SysUtils.DateTimeToSystemTime(DateTimeToLocalDateTime(DateTime), SystemTime);
SysUtils.DateTimeToSystemTime(DateTime, SystemTime);
FileTime.dwLowDateTime := 0;
FileTime.dwHighDateTime := 0;
if Windows.SystemTimeToFileTime(SystemTime, FileTime) then
begin
case Times of
ftLastAccess:
Result := SetFileTime(Handle, nil, @FileTime, nil);
ftLastWrite:
Result := SetFileTime(Handle, nil, nil, @FileTime);
ftCreation:
Result := SetFileTime(Handle, @FileTime, nil, nil);
end;
end;
finally
CloseHandle(Handle);
end;
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function SetFileTimesHelper(const FileName: string; const DateTime: TDateTime; Times: TFileTimes): Boolean;
var
FileTime: Integer;
StatBuf: TStatBuf64;
TimeBuf: utimbuf;
begin
Result := False;
FileTime := DateTimeToFileDate(DateTime);
if GetFileStatus(FileName, StatBuf, False) = 0 then
begin
TimeBuf.actime := StatBuf.st_atime;
TimeBuf.modtime := StatBuf.st_mtime;
case Times of
ftLastAccess:
TimeBuf.actime := FileTime;
ftLastWrite:
TimeBuf.modtime := FileTime;
end;
Result := utime(PChar(FileName), @TimeBuf) = 0;
end;
end;
{$ENDIF UNIX}
function SetFileLastAccess(const FileName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetFileTimesHelper(FileName, DateTime, ftLastAccess);
end;
function SetFileLastWrite(const FileName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetFileTimesHelper(FileName, DateTime, ftLastWrite);
end;
{$IFDEF MSWINDOWS}
function SetFileCreation(const FileName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetFileTimesHelper(FileName, DateTime, ftCreation);
end;
// utility function for SetDirTimesHelper
function BackupPrivilegesEnabled: Boolean;
begin
Result := IsPrivilegeEnabled(SE_BACKUP_NAME) and IsPrivilegeEnabled(SE_RESTORE_NAME);
end;
function SetDirTimesHelper(const DirName: string; const DateTime: TDateTime;
Times: TFileTimes): Boolean;
var
Handle: THandle;
FileTime: TFileTime;
SystemTime: TSystemTime;
begin
Result := False;
if IsDirectory(DirName) and BackupPrivilegesEnabled then
begin
Handle := CreateFile(PChar(DirName), GENERIC_WRITE, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle <> INVALID_HANDLE_VALUE then
try
SysUtils.DateTimeToSystemTime(DateTime, SystemTime);
FileTime.dwLowDateTime := 0;
FileTime.dwHighDateTime := 0;
Windows.SystemTimeToFileTime(SystemTime, FileTime);
case Times of
ftLastAccess:
Result := SetFileTime(Handle, nil, @FileTime, nil);
ftLastWrite:
Result := SetFileTime(Handle, nil, nil, @FileTime);
ftCreation:
Result := SetFileTime(Handle, @FileTime, nil, nil);
end;
finally
CloseHandle(Handle);
end;
end;
end;
function SetDirLastWrite(const DirName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetDirTimesHelper(DirName, DateTime, ftLastWrite);
end;
function SetDirLastAccess(const DirName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetDirTimesHelper(DirName, DateTime, ftLastAccess);
end;
function SetDirCreation(const DirName: string; const DateTime: TDateTime): Boolean;
begin
Result := SetDirTimesHelper(DirName, DateTime, ftCreation);
end;
procedure FillByteArray(var Bytes: array of Byte; Count: Cardinal; B: Byte);
begin
FillMemory(@Bytes[0], Count, B);
end;
procedure ShredFile(const FileName: string; Times: Integer);
const
BUFSIZE = 4096;
ODD_FILL = $C1;
EVEN_FILL = $3E;
var
Fs: TFileStream;
Size: Integer;
N: Integer;
ContentPtr: array of Byte;
begin
Size := FileGetSize(FileName);
if Size > 0 then
begin
if Times < 0 then
Times := 2
else
Times := Times * 2;
ContentPtr := nil;
Fs := TFileStream.Create(FileName, fmOpenReadWrite);
try
SetLength(ContentPtr, BUFSIZE);
while Times > 0 do
begin
if Times mod 2 = 0 then
FillByteArray(ContentPtr, BUFSIZE, EVEN_FILL)
else
FillByteArray(ContentPtr, BUFSIZE, ODD_FILL);
Fs.Seek(0, soBeginning);
N := Size div BUFSIZE;
while N > 0 do
begin
Fs.Write(ContentPtr[0], BUFSIZE);
Dec(N);
end;
N := Size mod BUFSIZE;
if N > 0 then
Fs.Write(ContentPtr[0], N);
FlushFileBuffers(Fs.Handle);
Dec(Times);
end;
finally
ContentPtr := nil;
Fs.Free;
DeleteFile(FileName);
end;
end
else
DeleteFile(FileName);
end;
function UnlockVolume(var Handle: THandle): Boolean;
var
BytesReturned: DWORD;
begin
Result := False;
if Handle <> INVALID_HANDLE_VALUE then
begin
BytesReturned := 0;
Result := DeviceIoControl(Handle, FSCTL_UNLOCK_VOLUME, nil, 0, nil, 0,
BytesReturned, nil);
if Result then
begin
CloseHandle(Handle);
Handle := INVALID_HANDLE_VALUE;
end;
end;
end;
{$IFDEF KEEP_DEPRECATED}
function Win32DeleteFile(const FileName: string; MoveToRecycleBin: Boolean): Boolean;
begin
Result := FileDelete(FileName, MoveToRecycleBin);
end;
function Win32MoveFileReplaceExisting(const SrcFileName, DstFileName: string): Boolean;
begin
Result := FileMove(SrcFilename, DstFilename, True);
end;
function Win32BackupFile(const FileName: string; Move: Boolean): Boolean;
begin
Result := FileBackup(FileName, Move);
end;
function Win32RestoreFile(const FileName: string): Boolean;
begin
Result := FileRestore(FileName);
end;
{$ENDIF KEEP_DEPRECATED}
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
function CreateSymbolicLink(const Name, Target: string): Boolean;
begin
Result := symlink(PChar(Target), PChar(Name)) = 0;
end;
function SymbolicLinkTarget(const Name: string): string;
var
N, BufLen: Integer;
begin
BufLen := 128;
repeat
Inc(BufLen, BufLen);
SetLength(Result, BufLen);
N := readlink(PChar(Name), PChar(Result), BufLen);
if N < 0 then // Error
begin
Result := '';
Exit;
end;
until N < BufLen;
SetLength(Result, N);
end;
{$ENDIF UNIX}
//=== File Version info routines =============================================
{$IFDEF MSWINDOWS}
const
VerKeyNames: array [1..12] of string =
('Comments',
'CompanyName',
'FileDescription',
'FileVersion',
'InternalName',
'LegalCopyright',
'LegalTradeMarks',
'OriginalFilename',
'ProductName',
'ProductVersion',
'SpecialBuild',
'PrivateBuild');
function OSIdentToString(const OSIdent: DWORD): string;
begin
case OSIdent of
VOS_UNKNOWN:
Result := LoadResString(@RsVosUnknown);
VOS_DOS:
Result := LoadResString(@RsVosDos);
VOS_OS216:
Result := LoadResString(@RsVosOS216);
VOS_OS232:
Result := LoadResString(@RsVosOS232);
VOS_NT:
Result := LoadResString(@RsVosNT);
VOS__WINDOWS16:
Result := LoadResString(@RsVosWindows16);
VOS__PM16:
Result := LoadResString(@RsVosPM16);
VOS__PM32:
Result := LoadResString(@RsVosPM32);
VOS__WINDOWS32:
Result := LoadResString(@RsVosWindows32);
VOS_DOS_WINDOWS16:
Result := LoadResString(@RsVosDosWindows16);
VOS_DOS_WINDOWS32:
Result := LoadResString(@RsVosDosWindows32);
VOS_OS216_PM16:
Result := LoadResString(@RsVosOS216PM16);
VOS_OS232_PM32:
Result := LoadResString(@RsVosOS232PM32);
VOS_NT_WINDOWS32:
Result := LoadResString(@RsVosNTWindows32);
else
Result := '';
end;
if Result = '' then
Result := LoadResString(@RsVosUnknown)
else
Result := Format(LoadResString(@RsVosDesignedFor), [Result]);
end;
function OSFileTypeToString(const OSFileType: DWORD; const OSFileSubType: DWORD): string;
begin
case OSFileType of
VFT_UNKNOWN:
Result := LoadResString(@RsVftUnknown);
VFT_APP:
Result := LoadResString(@RsVftApp);
VFT_DLL:
Result := LoadResString(@RsVftDll);
VFT_DRV:
begin
case OSFileSubType of
VFT2_DRV_PRINTER:
Result := LoadResString(@RsVft2DrvPRINTER);
VFT2_DRV_KEYBOARD:
Result := LoadResString(@RsVft2DrvKEYBOARD);
VFT2_DRV_LANGUAGE:
Result := LoadResString(@RsVft2DrvLANGUAGE);
VFT2_DRV_DISPLAY:
Result := LoadResString(@RsVft2DrvDISPLAY);
VFT2_DRV_MOUSE:
Result := LoadResString(@RsVft2DrvMOUSE);
VFT2_DRV_NETWORK:
Result := LoadResString(@RsVft2DrvNETWORK);
VFT2_DRV_SYSTEM:
Result := LoadResString(@RsVft2DrvSYSTEM);
VFT2_DRV_INSTALLABLE:
Result := LoadResString(@RsVft2DrvINSTALLABLE);
VFT2_DRV_SOUND:
Result := LoadResString(@RsVft2DrvSOUND);
VFT2_DRV_COMM:
Result := LoadResString(@RsVft2DrvCOMM);
else
Result := '';
end;
Result := Result + ' ' + LoadResString(@RsVftDrv);
end;
VFT_FONT:
begin
case OSFileSubType of
VFT2_FONT_RASTER:
Result := LoadResString(@RsVft2FontRASTER);
VFT2_FONT_VECTOR:
Result := LoadResString(@RsVft2FontVECTOR);
VFT2_FONT_TRUETYPE:
Result := LoadResString(@RsVft2FontTRUETYPE);
else
Result := '';
end;
Result := Result + ' ' + LoadResString(@RsVftFont);
end;
VFT_VXD:
Result := LoadResString(@RsVftVxd);
VFT_STATIC_LIB:
Result := LoadResString(@RsVftStaticLib);
else
Result := '';
end;
Result := TrimLeft(Result);
end;
function VersionResourceAvailable(const FileName: string): Boolean;
var
Size: DWORD;
Handle: DWORD;
Buffer: string;
begin
Result := False;
Handle := 0;
Size := GetFileVersionInfoSize(PChar(FileName), Handle);
if Size > 0 then
begin
SetLength(Buffer, Size);
Result := GetFileVersionInfo(PChar(FileName), Handle, Size, PChar(Buffer));
end;
end;
{$ENDIF MSWINDOWS}
// Version Info formatting
function FormatVersionString(const HiV, LoV: Word): string;
begin
Result := Format('%u.%.2u', [HiV, LoV]);
end;
function FormatVersionString(const Major, Minor, Build, Revision: Word): string;
begin
Result := Format('%u.%u.%u.%u', [Major, Minor, Build, Revision]);
end;
{$IFDEF MSWINDOWS}
function FormatVersionString(const FixedInfo: TVSFixedFileInfo; VersionFormat: TFileVersionFormat): string;
begin
with FixedInfo do
case VersionFormat of
vfMajorMinor:
Result := Format('%u.%u', [HiWord(dwFileVersionMS), LoWord(dwFileVersionMS)]);
vfFull:
Result := Format('%u.%u.%u.%u', [HiWord(dwFileVersionMS), LoWord(dwFileVersionMS),
HiWord(dwFileVersionLS), LoWord(dwFileVersionLS)]);
end;
end;
// Version Info extracting
procedure VersionExtractFileInfo(const FixedInfo: TVSFixedFileInfo; var Major, Minor, Build, Revision: Word);
begin
Major := HiWord(FixedInfo.dwFileVersionMS);
Minor := LoWord(FixedInfo.dwFileVersionMS);
Build := HiWord(FixedInfo.dwFileVersionLS);
Revision := LoWord(FixedInfo.dwFileVersionLS);
end;
procedure VersionExtractProductInfo(const FixedInfo: TVSFixedFileInfo; var Major, Minor, Build, Revision: Word);
begin
Major := HiWord(FixedInfo.dwProductVersionMS);
Minor := LoWord(FixedInfo.dwProductVersionMS);
Build := HiWord(FixedInfo.dwProductVersionLS);
Revision := LoWord(FixedInfo.dwProductVersionLS);
end;
// Fixed Version Info routines
function VersionFixedFileInfo(const FileName: string; var FixedInfo: TVSFixedFileInfo): Boolean;
var
Size, FixInfoLen: DWORD;
Handle: DWORD;
Buffer: string;
FixInfoBuf: PVSFixedFileInfo;
begin
Result := False;
Handle := 0;
Size := GetFileVersionInfoSize(PChar(FileName), Handle);
if Size > 0 then
begin
SetLength(Buffer, Size);
FixInfoLen := 0;
FixInfoBuf := nil;
if GetFileVersionInfo(PChar(FileName), Handle, Size, Pointer(Buffer)) and
VerQueryValue(Pointer(Buffer), DirDelimiter, Pointer(FixInfoBuf), FixInfoLen) and
(FixInfoLen = SizeOf(TVSFixedFileInfo)) then
begin
Result := True;
FixedInfo := FixInfoBuf^;
end;
end;
end;
function VersionFixedFileInfoString(const FileName: string; VersionFormat: TFileVersionFormat;
const NotAvailableText: string): string;
var
FixedInfo: TVSFixedFileInfo;
begin
FixedInfo.dwSignature := 0;
if VersionFixedFileInfo(FileName, FixedInfo) then
Result := FormatVersionString(FixedInfo, VersionFormat)
else
Result := NotAvailableText;
end;
//=== { TJclFileVersionInfo } ================================================
constructor TJclFileVersionInfo.Attach(VersionInfoData: Pointer; Size: Integer);
begin
SetLength(FBuffer, Size);
CopyMemory(PAnsiChar(FBuffer), VersionInfoData, Size);
ExtractData;
end;
constructor TJclFileVersionInfo.Create(const FileName: string);
var
Handle: DWORD;
Size: DWORD;
begin
Handle := 0;
Size := GetFileVersionInfoSize(PChar(FileName), Handle);
if Size = 0 then
raise EJclFileVersionInfoError.CreateRes(@RsFileUtilsNoVersionInfo);
SetLength(FBuffer, Size);
Win32Check(GetFileVersionInfo(PChar(FileName), Handle, Size, PAnsiChar(FBuffer)));
ExtractData;
end;
{$IFDEF MSWINDOWS}
{$IFDEF FPC}
constructor TJclFileVersionInfo.Create(const Window: HWND; Dummy: Pointer = nil);
{$ELSE}
constructor TJclFileVersionInfo.Create(const Window: HWND);
{$ENDIF}
type
{$IFDEF SUPPORTS_UNICODE}
TGetModuleFileNameEx =function(hProcess: THandle; hModule: HMODULE; FileName: PWideChar; nSize: DWORD): DWORD; stdcall;
{$ELSE ~SUPPORTS_UNICODE}
TGetModuleFileNameEx =function(hProcess: THandle; hModule: HMODULE; FileName: PAnsiChar; nSize: DWORD): DWORD; stdcall;
{$ENDIF ~SUPPORTS_UNICODE}
var
FileName: array[0..300] of Char;
DllHinst: HMODULE;
ProcessID: DWORD;
HProcess: THandle;
GetModuleFileNameExAddress: TGetModuleFileNameEx;
begin
if Window <>0 then
begin
Windows.GetWindowThreadProcessId(Window, @ProcessID);
hProcess := Windows.OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, ProcessID);
if hProcess <> 0 then
begin
if GetWindowsVersion() < WVWin2000 then
raise EJclWin32Error.CreateRes(@RsEWindowsVersionNotSupported)
else
begin
DllHinst := LoadLibrary('Psapi.dll');
if DllHinst < HINSTANCE_ERROR then
begin
try
{$IFDEF SUPPORTS_UNICODE}
GetModuleFileNameExAddress := GetProcAddress(DllHinst, 'GetModuleFileNameExW');
{$ELSE ~SUPPORTS_UNICODE}
GetModuleFileNameExAddress := GetProcAddress(DllHinst, 'GetModuleFileNameExA');
{$ENDIF ~SUPPORTS_UNICODE}
if Assigned(GetModuleFileNameExAddress) then
begin
GetModuleFileNameExAddress(hProcess, 0, FileName, sizeof(FileName));
Create(FileName);
end
else
begin
raise EJclError.CreateResFmt(@RsEFunctionNotFound, ['Psapi.dll', 'GetModuleFileNameEx']);
end
finally
FreeLibrary(DllHinst);
end;
end
else
raise EJclError.CreateResFmt(@RsELibraryNotFound, ['Psapi.dll']);
end;
end
else
raise EJclError.CreateResFmt(@RsEProcessNotValid, [ProcessID]);
end
else
raise EJclError.CreateResFmt(@RsEWindowNotValid, [Window]);
end;
constructor TJclFileVersionInfo.Create(const Module: HMODULE);
begin
if Module <> 0 then
Create(GetModulePath(Module))
else
raise EJclError.CreateResFmt(@RsEModuleNotValid, [Module]);
end;
{$ENDIF MSWINDOWS}
destructor TJclFileVersionInfo.Destroy;
begin
FreeAndNil(FItemList);
FreeAndNil(FItems);
inherited Destroy;
end;
procedure TJclFileVersionInfo.CheckLanguageIndex(Value: Integer);
begin
if (Value < 0) or (Value >= LanguageCount) then
raise EJclFileVersionInfoError.CreateRes(@RsFileUtilsLanguageIndex);
end;
procedure TJclFileVersionInfo.CreateItemsForLanguage;
var
I: Integer;
begin
Items.Clear;
for I := 0 to FItemList.Count - 1 do
if Integer(FItemList.Objects[I]) = FLanguageIndex then
Items.AddObject(FItemList[I], Pointer(FLanguages[FLanguageIndex].Pair));
end;
procedure TJclFileVersionInfo.ExtractData;
var
Data, EndOfData: PAnsiChar;
Len, ValueLen, DataType: Word;
HeaderSize: Integer;
Key: string;
Error, IsUnicode: Boolean;
procedure Padding(var DataPtr: PAnsiChar);
begin
while TJclAddr(DataPtr) and 3 <> 0 do
Inc(DataPtr);
end;
procedure GetHeader;
var
P: PAnsiChar;
TempKey: PWideChar;
begin
P := Data;
Len := PWord(P)^;
if Len = 0 then
begin
Error := True;
Exit;
end;
Inc(P, SizeOf(Word));
ValueLen := PWord(P)^;
Inc(P, SizeOf(Word));
if IsUnicode then
begin
DataType := PWord(P)^;
Inc(P, SizeOf(Word));
TempKey := PWideChar(P);
Inc(P, (lstrlenW(TempKey) + 1) * SizeOf(WideChar)); // length + #0#0
Key := TempKey;
end
else
begin
DataType := 1;
Key := string(PAnsiChar(P));
Inc(P, lstrlenA(PAnsiChar(P)) + 1);
end;
Padding(P);
HeaderSize := P - Data;
Data := P;
end;
procedure FixKeyValue;
const
HexNumberCPrefix = '0x';
var
I: Integer;
begin // GAPI32.DLL version 5.5.2803.1 contanins '04050x04E2' value
repeat
I := Pos(HexNumberCPrefix, Key);
if I > 0 then
Delete(Key, I, Length(HexNumberCPrefix));
until I = 0;
I := 1;
while I <= Length(Key) do
if CharIsHexDigit(Key[I]) then
Inc(I)
else
Delete(Key, I, 1);
end;
procedure ProcessStringInfo(Size: Integer);
var
EndPtr, EndStringPtr: PAnsiChar;
LangIndex: Integer;
LangIdRec: TLangIdRec;
Value: string;
begin
EndPtr := Data + Size;
LangIndex := 0;
while not Error and (Data < EndPtr) do
begin
GetHeader; // StringTable
FixKeyValue;
if (ValueLen <> 0) or (Length(Key) <> 8) then
begin
Error := True;
Break;
end;
Padding(Data);
LangIdRec.LangId := StrToIntDef('$' + Copy(Key, 1, 4), 0);
LangIdRec.CodePage := StrToIntDef('$' + Copy(Key, 5, 4), 0);
SetLength(FLanguages, LangIndex + 1);
FLanguages[LangIndex] := LangIdRec;
EndStringPtr := Data + Len - HeaderSize;
while not Error and (Data < EndStringPtr) do
begin
GetHeader; // string
case DataType of
0:
if ValueLen in [1..4] then
Value := Format('$%.*x', [ValueLen * 2, PInteger(Data)^])
else
begin
if (ValueLen > 0) and IsUnicode then
Value:=PWideChar(Data)
else
Value := '';
end;
1:
if ValueLen = 0 then
Value := ''
else
if IsUnicode then
begin
Value := WideCharLenToString(PWideChar(Data), ValueLen);
StrResetLength(Value);
end
else
Value := string(PAnsiChar(Data));
else
Error := True;
Break;
end;
Inc(Data, Len - HeaderSize);
Padding(Data); // String.Padding
FItemList.AddObject(Format('%s=%s', [Key, Value]), Pointer(LangIndex));
end;
Inc(LangIndex);
end;
end;
procedure ProcessVarInfo;
var
TranslationIndex: Integer;
begin
GetHeader; // Var
if SameText(Key, 'Translation') then
begin
SetLength(FTranslations, ValueLen div SizeOf(TLangIdRec));
for TranslationIndex := 0 to Length(FTranslations) - 1 do
begin
FTranslations[TranslationIndex] := PLangIdRec(Data)^;
Inc(Data, SizeOf(TLangIdRec));
end;
end;
end;
begin
FItemList := TStringList.Create;
FItems := TStringList.Create;
Data := Pointer(FBuffer);
Assert(TJclAddr(Data) mod 4 = 0);
IsUnicode := (PWord(Data + 4)^ in [0, 1]);
Error := True;
GetHeader;
EndOfData := Data + Len - HeaderSize;
if SameText(Key, 'VS_VERSION_INFO') and (ValueLen = SizeOf(TVSFixedFileInfo)) then
begin
FFixedInfo := PVSFixedFileInfo(Data);
Error := FFixedInfo.dwSignature <> $FEEF04BD;
Inc(Data, ValueLen); // VS_FIXEDFILEINFO
Padding(Data); // VS_VERSIONINFO.Padding2
while not Error and (Data < EndOfData) do
begin
GetHeader;
Inc(Data, ValueLen); // some files (VREDIR.VXD 4.00.1111) has non zero value of ValueLen
Dec(Len, HeaderSize + ValueLen);
if SameText(Key, 'StringFileInfo') then
ProcessStringInfo(Len)
else
if SameText(Key, 'VarFileInfo') then
ProcessVarInfo
else
Break;
end;
ExtractFlags;
CreateItemsForLanguage;
end;
if Error then
raise EJclFileVersionInfoError.CreateRes(@RsFileUtilsNoVersionInfo);
end;
procedure TJclFileVersionInfo.ExtractFlags;
var
Masked: DWORD;
begin
FFileFlags := [];
Masked := FFixedInfo^.dwFileFlags and FFixedInfo^.dwFileFlagsMask;
if (Masked and VS_FF_DEBUG) <> 0 then
Include(FFileFlags, ffDebug);
if (Masked and VS_FF_INFOINFERRED) <> 0 then
Include(FFileFlags, ffInfoInferred);
if (Masked and VS_FF_PATCHED) <> 0 then
Include(FFileFlags, ffPatched);
if (Masked and VS_FF_PRERELEASE) <> 0 then
Include(FFileFlags, ffPreRelease);
if (Masked and VS_FF_PRIVATEBUILD) <> 0 then
Include(FFileFlags, ffPrivateBuild);
if (Masked and VS_FF_SPECIALBUILD) <> 0 then
Include(FFileFlags, ffSpecialBuild);
end;
function TJclFileVersionInfo.GetBinFileVersion: string;
begin
with FFixedInfo^ do
Result := Format('%u.%u.%u.%u', [HiWord(dwFileVersionMS),
LoWord(dwFileVersionMS), HiWord(dwFileVersionLS), LoWord(dwFileVersionLS)]);
end;
function TJclFileVersionInfo.GetBinProductVersion: string;
begin
with FFixedInfo^ do
Result := Format('%u.%u.%u.%u', [HiWord(dwProductVersionMS),
LoWord(dwProductVersionMS), HiWord(dwProductVersionLS),
LoWord(dwProductVersionLS)]);
end;
function TJclFileVersionInfo.GetCustomFieldValue(const FieldName: string): string;
var
ItemIndex: Integer;
begin
if FieldName <> '' then
begin
ItemIndex := FItems.IndexOfName(FieldName);
if ItemIndex <> -1 then
//Return the required value, the value the user passed in was found.
Result := FItems.Values[FieldName]
else
raise EJclFileVersionInfoError.CreateResFmt(@RsFileUtilsValueNotFound, [FieldName]);
end
else
raise EJclFileVersionInfoError.CreateRes(@RsFileUtilsEmptyValue);
end;
function TJclFileVersionInfo.GetFileOS: DWORD;
begin
Result := FFixedInfo^.dwFileOS;
end;
function TJclFileVersionInfo.GetFileSubType: DWORD;
begin
Result := FFixedInfo^.dwFileSubtype;
end;
function TJclFileVersionInfo.GetFileType: DWORD;
begin
Result := FFixedInfo^.dwFileType;
end;
function TJclFileVersionInfo.GetFileVersionBuild: string;
var
Left: Integer;
begin
Result := FileVersion;
StrReplaceChar(Result, ',', '.');
Left := CharLastPos(Result, '.') + 1;
Result := StrMid(Result, Left, Length(Result) - Left + 1);
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetFileVersionMajor: string;
begin
Result := FileVersion;
StrReplaceChar(Result, ',', '.');
Result := StrBefore('.', Result);
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetFileVersionMinor: string;
var
Left, Right: integer;
begin
Result := FileVersion;
StrReplaceChar(Result, ',', '.');
Left := CharPos(Result, '.') + 1; // skip major
Right := CharPos(Result, '.', Left) {-1};
Result := StrMid(Result, Left, Right - Left {+1});
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetFileVersionRelease: string;
var
Left, Right: Integer;
begin
Result := FileVersion;
StrReplaceChar(Result, ',', '.');
Left := CharPos(Result, '.') + 1; // skip major
Left := CharPos(Result, '.', Left) + 1; // skip minor
Right := CharPos(Result, '.', Left) {-1};
Result := StrMid(Result, Left, Right - Left {+1});
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetFixedInfo: TVSFixedFileInfo;
begin
Result := FFixedInfo^;
end;
function TJclFileVersionInfo.GetItems: TStrings;
begin
Result := FItems;
end;
function TJclFileVersionInfo.GetLanguageCount: Integer;
begin
Result := Length(FLanguages);
end;
function TJclFileVersionInfo.GetLanguageIds(Index: Integer): string;
begin
CheckLanguageIndex(Index);
Result := VersionLanguageId(FLanguages[Index]);
end;
function TJclFileVersionInfo.GetLanguages(Index: Integer): TLangIdRec;
begin
CheckLanguageIndex(Index);
Result := FLanguages[Index];
end;
function TJclFileVersionInfo.GetLanguageNames(Index: Integer): string;
begin
CheckLanguageIndex(Index);
Result := VersionLanguageName(FLanguages[Index].LangId);
end;
function TJclFileVersionInfo.GetTranslationCount: Integer;
begin
Result := Length(FTranslations);
end;
function TJclFileVersionInfo.GetTranslations(Index: Integer): TLangIdRec;
begin
Result := FTranslations[Index];
end;
function TJclFileVersionInfo.GetProductVersionBuild: string;
var
Left: Integer;
begin
Result := ProductVersion;
StrReplaceChar(Result, ',', '.');
Left := CharLastPos(Result, '.') + 1;
Result := StrMid(Result, Left, Length(Result) - Left + 1);
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetProductVersionMajor: string;
begin
Result := ProductVersion;
StrReplaceChar(Result, ',', '.');
Result := StrBefore('.', Result);
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetProductVersionMinor: string;
var
Left, Right: integer;
begin
Result := ProductVersion;
StrReplaceChar(Result, ',', '.');
Left := CharPos(Result, '.') + 1; // skip major
Right := CharPos(Result, '.', Left) {-1};
Result := StrMid(Result, Left, Right - Left {+1});
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetProductVersionRelease: string;
var
Left, Right: Integer;
begin
Result := ProductVersion;
StrReplaceChar(Result, ',', '.');
Left := CharPos(Result, '.') + 1; // skip major
Left := CharPos(Result, '.', Left) + 1; // skip minor
Right := CharPos(Result, '.', Left) {-1};
Result := StrMid(Result, Left, Right - Left {+1});
Result := Trim(Result);
end;
function TJclFileVersionInfo.GetVersionKeyValue(Index: Integer): string;
begin
Result := Items.Values[VerKeyNames[Index]];
end;
procedure TJclFileVersionInfo.SetLanguageIndex(const Value: Integer);
begin
CheckLanguageIndex(Value);
if FLanguageIndex <> Value then
begin
FLanguageIndex := Value;
CreateItemsForLanguage;
end;
end;
function TJclFileVersionInfo.TranslationMatchesLanguages(Exact: Boolean): Boolean;
var
TransIndex, LangIndex: Integer;
TranslationPair: DWORD;
begin
Result := (LanguageCount = TranslationCount) or (not Exact and (TranslationCount > 0));
if Result then
for TransIndex := 0 to TranslationCount - 1 do
begin
TranslationPair := FTranslations[TransIndex].Pair;
LangIndex := LanguageCount - 1;
while (LangIndex >= 0) and (TranslationPair <> FLanguages[LangIndex].Pair) do
Dec(LangIndex);
if LangIndex < 0 then
begin
Result := False;
Break;
end;
end;
end;
class function TJclFileVersionInfo.VersionLanguageId(const LangIdRec: TLangIdRec): string;
begin
with LangIdRec do
Result := Format('%.4x%.4x', [LangId, CodePage]);
end;
class function TJclFileVersionInfo.VersionLanguageName(const LangId: Word): string;
var
R: DWORD;
begin
SetLength(Result, MAX_PATH);
R := VerLanguageName(LangId, PChar(Result), MAX_PATH);
SetLength(Result, R);
end;
{$ENDIF MSWINDOWS}
//=== { TJclFileMaskComparator } =============================================
constructor TJclFileMaskComparator.Create;
begin
inherited Create;
FSeparator := DirSeparator;
end;
function TJclFileMaskComparator.Compare(const NameExt: string): Boolean;
var
I: Integer;
NamePart, ExtPart: string;
NameWild, ExtWild: Boolean;
begin
Result := False;
I := StrLastPos('.', NameExt);
if I = 0 then
begin
NamePart := NameExt;
ExtPart := '';
end
else
begin
NamePart := Copy(NameExt, 1, I - 1);
ExtPart := Copy(NameExt, I + 1, Length(NameExt));
end;
for I := 0 to Length(FNames) - 1 do
begin
NameWild := FWildChars[I] and 1 = 1;
ExtWild := FWildChars[I] and 2 = 2;
if ((not NameWild and StrSame(FNames[I], NamePart)) or
(NameWild and (StrMatches(FNames[I], NamePart, 1)))) and
((not ExtWild and StrSame(FExts[I], ExtPart)) or
(ExtWild and (StrMatches(FExts[I], ExtPart, 1)))) then
begin
Result := True;
Break;
end;
end;
end;
procedure TJclFileMaskComparator.CreateMultiMasks;
var
List: TStringList;
I, N: Integer;
NS, ES: string;
begin
FExts := nil;
FNames := nil;
FWildChars := nil;
List := TStringList.Create;
try
StrToStrings(FFileMask, FSeparator, List);
SetLength(FExts, List.Count);
SetLength(FNames, List.Count);
SetLength(FWildChars, List.Count);
for I := 0 to List.Count - 1 do
begin
N := StrLastPos('.', List[I]);
if N = 0 then
begin
NS := List[I];
ES := '';
end
else
begin
NS := Copy(List[I], 1, N - 1);
ES := Copy(List[I], N + 1, 255);
end;
FNames[I] := NS;
FExts[I] := ES;
N := 0;
if StrContainsChars(NS, CharIsWildcard, False) then
N := N or 1;
if StrContainsChars(ES, CharIsWildcard, False) then
N := N or 2;
FWildChars[I] := N;
end;
finally
List.Free;
end;
end;
function TJclFileMaskComparator.GetCount: Integer;
begin
Result := Length(FWildChars);
end;
function TJclFileMaskComparator.GetExts(Index: Integer): string;
begin
Result := FExts[Index];
end;
function TJclFileMaskComparator.GetMasks(Index: Integer): string;
begin
Result := FNames[Index] + '.' + FExts[Index];
end;
function TJclFileMaskComparator.GetNames(Index: Integer): string;
begin
Result := FNames[Index];
end;
procedure TJclFileMaskComparator.SetFileMask(const Value: string);
begin
FFileMask := Value;
CreateMultiMasks;
end;
procedure TJclFileMaskComparator.SetSeparator(const Value: Char);
begin
if FSeparator <> Value then
begin
FSeparator := Value;
CreateMultiMasks;
end;
end;
function AdvBuildFileList(const Path: string; const Attr: Integer; const Files: TStrings;
const AttributeMatch: TJclAttributeMatch; const Options: TFileListOptions;
const SubfoldersMask: string; const FileMatchFunc: TFileMatchFunc): Boolean;
var
FileMask: string;
RootDir: string;
Folders: TStringList;
CurrentItem: Integer;
Counter: Integer;
FindAttr: Integer;
procedure BuildFolderList;
var
FindInfo: TSearchRec;
Rslt: Integer;
begin
Counter := Folders.Count - 1;
CurrentItem := 0;
while CurrentItem <= Counter do
begin
// searching for subfolders (including hidden ones)
Rslt := FindFirst(Folders[CurrentItem] + '*.*', faAnyFile, FindInfo);
try
while Rslt = 0 do
begin
if (FindInfo.Name <> '.') and (FindInfo.Name <> '..') and
(FindInfo.Attr and faDirectory = faDirectory) then
Folders.Add(Folders[CurrentItem] + FindInfo.Name + DirDelimiter);
Rslt := FindNext(FindInfo);
end;
finally
FindClose(FindInfo);
end;
Counter := Folders.Count - 1;
Inc(CurrentItem);
end;
end;
procedure FillFileList(CurrentCounter: Integer);
var
FindInfo: TSearchRec;
Rslt: Integer;
CurrentFolder: string;
Matches: Boolean;
begin
CurrentFolder := Folders[CurrentCounter];
Rslt := FindFirst(CurrentFolder + FileMask, FindAttr, FindInfo);
try
while Rslt = 0 do
begin
Matches := False;
case AttributeMatch of
amAny:
Matches := True;
amExact:
Matches := Attr = FindInfo.Attr;
amSubSetOf:
Matches := (Attr and FindInfo.Attr) = Attr;
amSuperSetOf:
Matches := (Attr and FindInfo.Attr) = FindInfo.Attr;
amCustom:
if Assigned(FileMatchFunc) then
Matches := FileMatchFunc(Attr, FindInfo);
end;
if Matches then
if flFullNames in Options then
Files.Add(CurrentFolder + FindInfo.Name)
else
Files.Add(FindInfo.Name);
Rslt := FindNext(FindInfo);
end;
finally
FindClose(FindInfo);
end;
end;
begin
Assert(Assigned(Files));
FileMask := ExtractFileName(Path);
RootDir := ExtractFilePath(Path);
Folders := TStringList.Create;
Files.BeginUpdate;
try
Folders.Add(RootDir);
case AttributeMatch of
amExact, amSuperSetOf:
FindAttr := Attr;
else
FindAttr := faAnyFile;
end;
// here's the recursive search for nested folders
if flRecursive in Options then
BuildFolderList;
for Counter := 0 to Folders.Count - 1 do
begin
if (((flMaskedSubfolders in Options) and (StrMatches(SubfoldersMask,
Folders[Counter], 1))) or (not (flMaskedSubfolders in Options))) then
FillFileList(Counter);
end;
finally
Folders.Free;
Files.EndUpdate;
end;
Result := True;
end;
function VerifyFileAttributeMask(var RejectedAttributes, RequiredAttributes: Integer): Boolean;
begin
if RequiredAttributes and faNormalFile <> 0 then
RejectedAttributes := not faNormalFile or RejectedAttributes;
Result := RequiredAttributes and RejectedAttributes = 0;
end;
function AttributeMatch(FileAttributes, RejectedAttr, RequiredAttr: Integer): Boolean;
begin
if FileAttributes = 0 then
FileAttributes := faNormalFile;
{$IFDEF MSWINDOWS}
RequiredAttr := RequiredAttr and not faUnixSpecific;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
RequiredAttr := RequiredAttr and not faWindowsSpecific;
{$ENDIF UNIX}
Result := (FileAttributes and RejectedAttr = 0)
and (FileAttributes and RequiredAttr = RequiredAttr);
end;
function IsFileAttributeMatch(FileAttributes, RejectedAttributes,
RequiredAttributes: Integer): Boolean;
begin
VerifyFileAttributeMask(RejectedAttributes, RequiredAttributes);
Result := AttributeMatch(FileAttributes, RejectedAttributes, RequiredAttributes);
end;
function FileAttributesStr(const FileInfo: TSearchRec): string;
{$IFDEF MSWINDOWS}
const
SAllAttrSet = 'rahs'; // readonly, archive, hidden, system
Attributes: array [1..4] of Integer =
(faReadOnly, faArchive, faHidden, faSysFile);
var
I: Integer;
begin
Result := SAllAttrSet;
for I := Low(Attributes) to High(Attributes) do
if (FileInfo.Attr and Attributes[I]) = 0 then
Result[I] := '-';
end;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
const
SAllAttrSet = 'drwxrwxrwx';
var
I: Integer;
Flag: Cardinal;
begin
Result := SAllAttrSet;
if FileInfo.Attr and faDirectory = 0 then
Result[1] := '-'; // no directory
Flag := 1 shl 8;
for I := 2 to 10 do
begin
if FileInfo.Mode and Flag = 0 then
Result[I] := '-';
Flag := Flag shr 1;
end;
end;
{$ENDIF UNIX}
function IsFileNameMatch(FileName: string; const Mask: string;
const CaseSensitive: Boolean): Boolean;
begin
Result := True;
{$IFDEF MSWINDOWS}
if (Mask = '') or (Mask = '*') or (Mask = '*.*') then
Exit;
if Pos('.', FileName) = 0 then
FileName := FileName + '.'; // file names w/o extension match '*.'
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
if (Mask = '') or (Mask = '*') then
Exit;
{$ENDIF UNIX}
if CaseSensitive then
Result := StrMatches(Mask, FileName)
else
Result := StrMatches(AnsiUpperCase(Mask), AnsiUpperCase(FileName));
end;
// author: Robert Rossmair
function CanonicalizedSearchPath(const Directory: string): string;
begin
Result := PathCanonicalize(Directory);
{$IFDEF MSWINDOWS}
// avoid changing "X:" (current directory on drive X:) into "X:\" (root dir.)
if Result[Length(Result)] <> ':' then
{$ENDIF MSWINDOWS}
Result := PathAddSeparator(Result);
// strip leading "./" resp. ".\"
if Pos('.' + DirDelimiter, Result) = 1 then
Result := Copy(Result, 3, Length(Result) - 2);
end;
procedure EnumFiles(const Path: string; HandleFile: TFileHandlerEx;
RejectedAttributes: Integer; RequiredAttributes: Integer; Abort: PBoolean);
var
Directory: string;
FileInfo: TSearchRec;
Attr: Integer;
Found: Boolean;
begin
Assert(Assigned(HandleFile));
Assert(VerifyFileAttributeMask(RejectedAttributes, RequiredAttributes),
LoadResString(@RsFileSearchAttrInconsistency));
Directory := ExtractFilePath(Path);
Attr := faAnyFile and not RejectedAttributes;
Found := SysUtils.FindFirst(Path, Attr, FileInfo) = 0;
try
while Found do
begin
if (Abort <> nil) and LongBool(Abort^) then
Exit;
if AttributeMatch(FileInfo.Attr, RejectedAttributes, RequiredAttributes) then
if ((FileInfo.Attr and faDirectory = 0)
or ((FileInfo.Name <> '.') and (FileInfo.Name <> '..'))) then
HandleFile(Directory, FileInfo);
Found := FindNext(FileInfo) = 0;
end;
finally
FindClose(FileInfo);
end;
end;
procedure EnumFiles(const Path: string; HandleFile: TFileInfoHandlerEx;
RejectedAttributes: Integer; RequiredAttributes: Integer; Abort: PBoolean);
var
FileInfo: TSearchRec;
Attr: Integer;
Found: Boolean;
begin
Assert(Assigned(HandleFile));
Assert(VerifyFileAttributeMask(RejectedAttributes, RequiredAttributes),
LoadResString(@RsFileSearchAttrInconsistency));
Attr := faAnyFile and not RejectedAttributes;
Found := SysUtils.FindFirst(Path, Attr, FileInfo) = 0;
try
while Found do
begin
if (Abort <> nil) and LongBool(Abort^) then
Exit;
if AttributeMatch(FileInfo.Attr, RejectedAttributes, RequiredAttributes) then
if ((FileInfo.Attr and faDirectory = 0)
or ((FileInfo.Name <> '.') and (FileInfo.Name <> '..'))) then
HandleFile(FileInfo);
Found := FindNext(FileInfo) = 0;
end;
finally
FindClose(FileInfo);
end;
end;
procedure EnumDirectories(const Root: string; const HandleDirectory: TFileHandler;
const IncludeHiddenDirectories: Boolean; const SubDirectoriesMask: string;
Abort: PBoolean {$IFDEF UNIX}; ResolveSymLinks: Boolean {$ENDIF});
var
RootDir: string;
Attr: Integer;
procedure Process(const Directory: string);
var
DirInfo: TSearchRec;
SubDir: string;
Found: Boolean;
begin
HandleDirectory(Directory);
Found := SysUtils.FindFirst(Directory + '*', Attr, DirInfo) = 0;
try
while Found do
begin
if (Abort <> nil) and LongBool(Abort^) then
Exit;
if (DirInfo.Name <> '.') and (DirInfo.Name <> '..') and
{$IFDEF UNIX}
(IncludeHiddenDirectories or (Pos('.', DirInfo.Name) <> 1)) and
((DirInfo.Attr and faSymLink = 0) or ResolveSymLinks) and
{$ENDIF UNIX}
(DirInfo.Attr and faDirectory <> 0) then
begin
SubDir := Directory + DirInfo.Name + DirDelimiter;
if (SubDirectoriesMask = '') or StrMatches(SubDirectoriesMask, SubDir, Length(RootDir)) then
Process(SubDir);
end;
Found := FindNext(DirInfo) = 0;
end;
finally
FindClose(DirInfo);
end;
end;
begin
Assert(Assigned(HandleDirectory));
RootDir := CanonicalizedSearchPath(Root);
if IncludeHiddenDirectories then
Attr := faDirectory + faHidden // no effect on Linux
else
Attr := faDirectory;
Process(RootDir);
end;
//=== { TJclCustomFileAttributeMask } ==============================================
constructor TJclCustomFileAttrMask.Create;
begin
inherited Create;
FRejectedAttr := faRejectedByDefault;
end;
procedure TJclCustomFileAttrMask.Assign(Source: TPersistent);
begin
if Source is TJclCustomFileAttrMask then
begin
Required := TJclCustomFileAttrMask(Source).Required;
Rejected := TJclCustomFileAttrMask(Source).Rejected;
end
else
inherited Assign(Source);
end;
procedure TJclCustomFileAttrMask.Clear;
begin
Rejected := 0;
Required := 0;
end;
procedure TJclCustomFileAttrMask.DefineProperties(Filer: TFiler);
var
Ancestor: TJclCustomFileAttrMask;
Attr: Integer;
begin
Attr := 0;
Ancestor := TJclCustomFileAttrMask(Filer.Ancestor);
if Assigned(Ancestor) then
Attr := Ancestor.FRequiredAttr;
Filer.DefineProperty('Required', ReadRequiredAttributes, WriteRequiredAttributes,
Attr <> FRequiredAttr);
if Assigned(Ancestor) then
Attr := Ancestor.FRejectedAttr;
Filer.DefineProperty('Rejected', ReadRejectedAttributes, WriteRejectedAttributes,
Attr <> FRejectedAttr);
end;
function TJclCustomFileAttrMask.Match(FileAttributes: Integer): Boolean;
begin
Result := AttributeMatch(FileAttributes, Rejected, Required);
end;
function TJclCustomFileAttrMask.Match(const FileInfo: TSearchRec): Boolean;
begin
Result := Match(FileInfo.Attr);
end;
function TJclCustomFileAttrMask.GetAttr(Index: Integer): TAttributeInterest;
begin
if ((FRequiredAttr and Index) <> 0) or (Index = faNormalFile) and
(FRejectedAttr = not faNormalFile) then
Result := aiRequired
else
if (FRejectedAttr and Index) <> 0 then
Result := aiRejected
else
Result := aiIgnored;
end;
procedure TJclCustomFileAttrMask.ReadRejectedAttributes(Reader: TReader);
begin
FRejectedAttr := Reader.ReadInteger;
end;
procedure TJclCustomFileAttrMask.ReadRequiredAttributes(Reader: TReader);
begin
FRequiredAttr := Reader.ReadInteger;
end;
procedure TJclCustomFileAttrMask.SetAttr(Index: Integer; const Value: TAttributeInterest);
begin
case Value of
aiIgnored:
begin
FRequiredAttr := FRequiredAttr and not Index;
FRejectedAttr := FRejectedAttr and not Index;
end;
aiRejected:
begin
FRequiredAttr := FRequiredAttr and not Index;
FRejectedAttr := FRejectedAttr or Index;
end;
aiRequired:
begin
if Index = faNormalFile then
begin
FRequiredAttr := faNormalFile;
FRejectedAttr := not faNormalFile;
end
else
begin
FRequiredAttr := FRequiredAttr or Index;
FRejectedAttr := FRejectedAttr and not Index;
end;
end;
end;
end;
procedure TJclCustomFileAttrMask.WriteRejectedAttributes(Writer: TWriter);
begin
Writer.WriteInteger(FRejectedAttr);
end;
procedure TJclCustomFileAttrMask.WriteRequiredAttributes(Writer: TWriter);
begin
Writer.WriteInteger(FRequiredAttr);
end;
//=== { TJclFileAttributeMask } ==============================================
procedure TJclFileAttributeMask.ReadVolumeID(Reader: TReader);
begin
// Nothing, we are not interested in the value of the VolumeID property,
// this procedure and the associated DefineProperty call are here only
// to allow reading legacy DFMs that have this property defined.
end;
procedure TJclFileAttributeMask.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('VolumeID', ReadVolumeID, nil, False);
end;
//=== { TEnumFileThread } ====================================================
type
TEnumFileThread = class(TThread)
private
FID: TFileSearchTaskID;
FFileMasks: TStringList;
FDirectories: TStrings;
FCurrentDirectory: string;
FSubDirectoryMask: string;
FOnEnterDirectory: TFileHandler;
FFileHandlerEx: TFileHandlerEx;
FFileHandler: TFileHandler;
FInternalDirHandler: TFileHandler;
FInternalFileInfoHandler: TFileInfoHandlerEx;
FFileInfo: TSearchRec;
FRejectedAttr: Integer;
FRequiredAttr: Integer;
FFileSizeMin: Int64;
FFileSizeMax: Int64;
FFileTimeMin: Integer;
FFileTimeMax: Integer;
FSynchronizationMode: TFileEnumeratorSyncMode;
FIncludeSubDirectories: Boolean;
FIncludeHiddenSubDirectories: Boolean;
FNotifyOnTermination: Boolean;
FCaseSensitiveSearch: Boolean;
FAllNamesMatch: Boolean;
procedure EnterDirectory;
procedure AsyncProcessDirectory(const Directory: string);
procedure SyncProcessDirectory(const Directory: string);
procedure AsyncProcessFile(const FileInfo: TSearchRec);
procedure SyncProcessFile(const FileInfo: TSearchRec);
function GetDirectories: TStrings;
function GetFileMasks: TStrings;
procedure SetDirectories(const Value: TStrings);
procedure SetFileMasks(const Value: TStrings);
protected
procedure DoTerminate; override;
procedure Execute; override;
function FileMatch: Boolean;
function FileNameMatchesMask: Boolean;
procedure ProcessDirectory;
procedure ProcessDirFiles;
procedure ProcessFile;
property AllNamesMatch: Boolean read FAllNamesMatch;
property CaseSensitiveSearch: Boolean read FCaseSensitiveSearch write FCaseSensitiveSearch;
property FileMasks: TStrings read GetFileMasks write SetFileMasks;
property FileSizeMin: Int64 read FFileSizeMin write FFileSizeMin;
property FileSizeMax: Int64 read FFileSizeMax write FFileSizeMax;
property Directories: TStrings read GetDirectories write SetDirectories;
property IncludeSubDirectories: Boolean
read FIncludeSubDirectories write FIncludeSubDirectories;
property IncludeHiddenSubDirectories: Boolean
read FIncludeHiddenSubDirectories write FIncludeHiddenSubDirectories;
property RejectedAttr: Integer read FRejectedAttr write FRejectedAttr;
property RequiredAttr: Integer read FRequiredAttr write FRequiredAttr;
property SynchronizationMode: TFileEnumeratorSyncMode
read FSynchronizationMode write FSynchronizationMode;
public
constructor Create;
destructor Destroy; override;
property ID: TFileSearchTaskID read FID;
{$IFDEF FPC} // protected property
property Terminated;
{$ENDIF FPC}
end;
constructor TEnumFileThread.Create;
begin
inherited Create(True);
FDirectories := TStringList.Create;
FFileMasks := TStringList.Create;
FFileTimeMin := Low(FFileInfo.Time);
FFileTimeMax := High(FFileInfo.Time);
FFileSizeMax := High(FFileSizeMax);
{$IFDEF MSWINDOWS}
Priority := tpIdle;
{$ENDIF MSWINDOWS}
{$IFDEF UNIX}
{$IFDEF FPC}
Priority := tpIdle;
{$ELSE ~FPC}
Priority := 0;
{$ENDIF ~FPC}
{$ENDIF UNIX}
FreeOnTerminate := True;
FNotifyOnTermination := True;
end;
destructor TEnumFileThread.Destroy;
begin
FFileMasks.Free;
FDirectories.Free;
inherited Destroy;
end;
procedure TEnumFileThread.Execute;
var
Index: Integer;
begin
if SynchronizationMode = smPerDirectory then
begin
FInternalDirHandler := SyncProcessDirectory;
FInternalFileInfoHandler := AsyncProcessFile;
end
else // SynchronizationMode = smPerFile
begin
FInternalDirHandler := AsyncProcessDirectory;
FInternalFileInfoHandler := SyncProcessFile;
end;
if FIncludeSubDirectories then
begin
for Index := 0 to FDirectories.Count - 1 do
EnumDirectories(FDirectories.Strings[Index], FInternalDirHandler, FIncludeHiddenSubDirectories,
FSubDirectoryMask, @Terminated)
end
else
begin
for Index := 0 to FDirectories.Count - 1 do
FInternalDirHandler(CanonicalizedSearchPath(FDirectories.Strings[Index]));
end;
end;
procedure TEnumFileThread.DoTerminate;
begin
if FNotifyOnTermination then
inherited DoTerminate;
end;
procedure TEnumFileThread.EnterDirectory;
begin
FOnEnterDirectory(FCurrentDirectory);
end;
procedure TEnumFileThread.ProcessDirectory;
begin
if Assigned(FOnEnterDirectory) then
EnterDirectory;
ProcessDirFiles;
end;
procedure TEnumFileThread.AsyncProcessDirectory(const Directory: string);
begin
FCurrentDirectory := Directory;
if Assigned(FOnEnterDirectory) then
Synchronize(EnterDirectory);
ProcessDirFiles;
end;
procedure TEnumFileThread.SyncProcessDirectory(const Directory: string);
begin
FCurrentDirectory := Directory;
Synchronize(ProcessDirectory);
end;
procedure TEnumFileThread.ProcessDirFiles;
begin
EnumFiles(FCurrentDirectory + '*', FInternalFileInfoHandler, FRejectedAttr, FRequiredAttr, @Terminated);
end;
function TEnumFileThread.FileMatch: Boolean;
var
FileSize: Int64;
begin
Result := FileNameMatchesMask and (FFileInfo.Time >= FFileTimeMin) and (FFileInfo.Time <= FFileTimeMax);
if Result then
begin
FileSize := GetSizeOfFile(FFileInfo);
Result := (FileSize >= FFileSizeMin) and (FileSize <= FFileSizeMax);
end;
end;
function TEnumFileThread.FileNameMatchesMask: Boolean;
var
I: Integer;
begin
Result := AllNamesMatch;
if not Result then
for I := 0 to FileMasks.Count - 1 do
if IsFileNameMatch(FFileInfo.Name, FileMasks[I], CaseSensitiveSearch) then
begin
Result := True;
Break;
end;
end;
procedure TEnumFileThread.ProcessFile;
begin
if Assigned(FFileHandlerEx) then
FFileHandlerEx(FCurrentDirectory, FFileInfo)
else
FFileHandler(FCurrentDirectory + FFileInfo.Name);
end;
procedure TEnumFileThread.AsyncProcessFile(const FileInfo: TSearchRec);
begin
FFileInfo := FileInfo;
if FileMatch then
ProcessFile;
end;
procedure TEnumFileThread.SyncProcessFile(const FileInfo: TSearchRec);
begin
FFileInfo := FileInfo;
if FileMatch then
Synchronize(ProcessFile);
end;
function TEnumFileThread.GetDirectories: TStrings;
begin
Result := FDirectories;
end;
function TEnumFileThread.GetFileMasks: TStrings;
begin
Result := FFileMasks;
end;
procedure TEnumFileThread.SetDirectories(const Value: TStrings);
begin
FDirectories.Assign(Value);
end;
procedure TEnumFileThread.SetFileMasks(const Value: TStrings);
var
I: Integer;
begin
FAllNamesMatch := Value.Count = 0;
for I := 0 to Value.Count - 1 do
if (Value[I] = '*') {$IFDEF MSWINDOWS} or (Value[I] = '*.*') {$ENDIF} then
begin
FAllNamesMatch := True;
Break;
end;
if FAllNamesMatch then
FileMasks.Clear
else
FileMasks.Assign(Value);
end;
//=== { TJclFileEnumerator } =================================================
constructor TJclFileEnumerator.Create;
begin
inherited Create;
FTasks := TList.Create;
FAttributeMask := TJclFileAttributeMask.Create;
FRootDirectories := TStringList.Create;
FRootDirectories.Add('.');
FFileMasks := TStringList.Create;
FFileMasks.Add('*');
FSubDirectoryMask := '*';
FOptions := [fsIncludeSubDirectories];
FLastChangeAfter := MinDateTime;
FLastChangeBefore := MaxDateTime;
{$IFDEF UNIX}
FCaseSensitiveSearch := True;
{$ENDIF UNIX}
if GetOwner <> nil then
GetOwner.GetInterface(IInterface, FOwnerInterface);
end;
destructor TJclFileEnumerator.Destroy;
begin
StopAllTasks(True);
FTasks.Free;
FAttributeMask.Free;
FFileMasks.Free;
FRootDirectories.Free;
inherited Destroy;
end;
procedure TJclFileEnumerator.AfterConstruction;
begin
inherited AfterConstruction;
if GetOwner <> nil then
GetOwner.GetInterface(IInterface, FOwnerInterface);
end;
function TJclFileEnumerator.QueryInterface(const IID: TGUID; out Obj): HRESULT;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TJclFileEnumerator._AddRef: Integer;
begin
if FOwnerInterface <> nil then
Result := FOwnerInterface._AddRef
else
Result := InterlockedIncrement(FRefCount);
end;
function TJclFileEnumerator._Release: Integer;
begin
if FOwnerInterface <> nil then
Result := FOwnerInterface._Release
else
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
end;
procedure TJclFileEnumerator.Assign(Source: TPersistent);
var
Src: TJclFileEnumerator;
begin
if Source is TJclFileEnumerator then
begin
Src := TJclFileEnumerator(Source);
FCaseSensitiveSearch := Src.FCaseSensitiveSearch;
FileMasks.Assign(Src.FileMasks);
RootDirectory := Src.RootDirectory;
SubDirectoryMask := Src.SubDirectoryMask;
AttributeMask := Src.AttributeMask;
Options := Src.Options;
FileSizeMin := Src.FileSizeMin;
FileSizeMax := Src.FileSizeMax;
LastChangeAfter := Src.LastChangeAfter;
LastChangeBefore := Src.LastChangeBefore;
SynchronizationMode := Src.SynchronizationMode;
OnEnterDirectory := Src.OnEnterDirectory;
OnTerminateTask := Src.OnTerminateTask;
end
else
inherited Assign(Source);
end;
function TJclFileEnumerator.CreateTask: TThread;
var
Task: TEnumFileThread;
begin
Task := TEnumFileThread.Create;
Task.FID := NextTaskID;
Task.CaseSensitiveSearch := FCaseSensitiveSearch;
Task.FileMasks := FileMasks;
Task.Directories := RootDirectories;
Task.RejectedAttr := AttributeMask.Rejected;
Task.RequiredAttr := AttributeMask.Required;
Task.IncludeSubDirectories := IncludeSubDirectories;
Task.IncludeHiddenSubDirectories := IncludeHiddenSubDirectories;
if fsMinSize in Options then
Task.FileSizeMin := FileSizeMin;
if fsMaxSize in Options then
Task.FileSizeMax := FileSizeMax;
if fsLastChangeAfter in Options then
Task.FFileTimeMin := DateTimeToFileDate(LastChangeAfter);
if fsLastChangeBefore in Options then
Task.FFileTimeMax := DateTimeToFileDate(LastChangeBefore);
Task.SynchronizationMode := SynchronizationMode;
Task.FOnEnterDirectory := OnEnterDirectory;
Task.OnTerminate := TaskTerminated;
FTasks.Add(Task);
if FRefCount > 0 then
_AddRef;
Result := Task;
end;
function TJclFileEnumerator.FillList(List: TStrings): TFileSearchTaskID;
begin
List.BeginUpdate;
try
Result := ForEach(List.Append);
finally
List.EndUpdate;
end;
end;
function TJclFileEnumerator.ForEach(Handler: TFileHandlerEx): TFileSearchTaskID;
var
Task: TEnumFileThread;
begin
Task := TEnumFileThread(CreateTask);
Task.FFileHandlerEx := Handler;
Result := Task.ID;
{$IFDEF RTL210_UP}
Task.Suspended := False;
{$ELSE ~RTL210_UP}
Task.Resume;
{$ENDIF ~RTL210_UP}
end;
function TJclFileEnumerator.ForEach(Handler: TFileHandler): TFileSearchTaskID;
var
Task: TEnumFileThread;
begin
Task := TEnumFileThread(CreateTask);
Task.FFileHandler := Handler;
Result := Task.ID;
{$IFDEF RTL210_UP}
Task.Suspended := False;
{$ELSE ~RTL210_UP}
Task.Resume;
{$ENDIF ~RTL210_UP}
end;
function TJclFileEnumerator.GetRunningTasks: Integer;
begin
Result := FTasks.Count;
end;
procedure TJclFileEnumerator.StopTask(ID: TFileSearchTaskID);
var
Task: TEnumFileThread;
I: Integer;
begin
for I := 0 to FTasks.Count - 1 do
begin
Task := TEnumFileThread(FTasks[I]);
if Task.ID = ID then
begin
Task.Terminate;
Break;
end;
end;
end;
procedure TJclFileEnumerator.StopAllTasks(Silently: Boolean = False);
var
I: Integer;
begin
for I := 0 to FTasks.Count - 1 do
with TEnumFileThread(FTasks[I]) do
begin
FNotifyOnTermination := not Silently;
Terminate;
end;
end;
procedure TJclFileEnumerator.TaskTerminated(Sender: TObject);
begin
FTasks.Remove(Sender);
try
if Assigned(FOnTerminateTask) then
with TEnumFileThread(Sender) do
FOnTerminateTask(ID, Terminated);
finally
if FRefCount > 0 then
_Release;
end;
end;
function TJclFileEnumerator.GetNextTaskID: TFileSearchTaskID;
begin
Result := FNextTaskID;
Inc(FNextTaskID);
end;
procedure TJclFileEnumerator.SetAttributeMask(const Value: TJclFileAttributeMask);
begin
FAttributeMask.Assign(Value);
end;
function TJclFileEnumerator.GetLastChangeAfterStr: string;
begin
Result := DateTimeToStr(LastChangeAfter);
end;
function TJclFileEnumerator.GetLastChangeBeforeStr: string;
begin
Result := DateTimeToStr(LastChangeBefore);
end;
procedure TJclFileEnumerator.SetLastChangeAfterStr(const Value: string);
begin
if Value = '' then
LastChangeAfter := MinDateTime
else
LastChangeAfter := StrToDateTime(Value);
end;
procedure TJclFileEnumerator.SetLastChangeBeforeStr(const Value: string);
begin
if Value = '' then
LastChangeBefore := MaxDateTime
else
LastChangeBefore := StrToDateTime(Value);
end;
function TJclFileEnumerator.GetAttributeMask: TJclFileAttributeMask;
begin
Result := FAttributeMask;
end;
function TJclFileEnumerator.GetCaseSensitiveSearch: Boolean;
begin
Result := FCaseSensitiveSearch;
end;
function TJclFileEnumerator.GetRootDirectories: TStrings;
begin
Result := FRootDirectories;
end;
function TJclFileEnumerator.GetRootDirectory: string;
begin
if FRootDirectories.Count = 1 then
Result := FRootDirectories.Strings[0]
else
Result := '';
end;
function TJclFileEnumerator.GetFileMask: string;
begin
Result := StringsToStr(FileMasks, DirSeparator, False);
end;
function TJclFileEnumerator.GetFileMasks: TStrings;
begin
Result := FFileMasks;
end;
function TJclFileEnumerator.GetFileSizeMax: Int64;
begin
Result := FFileSizeMax;
end;
function TJclFileEnumerator.GetFileSizeMin: Int64;
begin
Result := FFileSizeMin;
end;
function TJclFileEnumerator.GetIncludeHiddenSubDirectories: Boolean;
begin
Result := fsIncludeHiddenSubDirectories in Options;
end;
function TJclFileEnumerator.GetIncludeSubDirectories: Boolean;
begin
Result := fsIncludeSubDirectories in Options;
end;
function TJclFileEnumerator.GetLastChangeAfter: TDateTime;
begin
Result := FLastChangeAfter;
end;
function TJclFileEnumerator.GetLastChangeBefore: TDateTime;
begin
Result := FLastChangeBefore;
end;
function TJclFileEnumerator.GetOnEnterDirectory: TFileHandler;
begin
Result := FOnEnterDirectory;
end;
function TJclFileEnumerator.GetOnTerminateTask: TFileSearchTerminationEvent;
begin
Result := FOnTerminateTask;
end;
function TJclFileEnumerator.GetOption(const Option: TFileSearchOption): Boolean;
begin
Result := Option in FOptions;
end;
function TJclFileEnumerator.GetOptions: TFileSearchOptions;
begin
Result := FOptions;
end;
function TJclFileEnumerator.GetSubDirectoryMask: string;
begin
Result := FSubDirectoryMask;
end;
function TJclFileEnumerator.GetSynchronizationMode: TFileEnumeratorSyncMode;
begin
Result := FSynchronizationMode;
end;
function TJclFileEnumerator.IsLastChangeAfterStored: Boolean;
begin
Result := FLastChangeAfter <> MinDateTime;
end;
function TJclFileEnumerator.IsLastChangeBeforeStored: Boolean;
begin
Result := FLastChangeBefore <> MaxDateTime;
end;
procedure TJclFileEnumerator.SetCaseSensitiveSearch(const Value: Boolean);
begin
FCaseSensitiveSearch := Value;
end;
procedure TJclFileEnumerator.SetRootDirectories(const Value: TStrings);
begin
FRootDirectories.Assign(Value);
end;
procedure TJclFileEnumerator.SetRootDirectory(const Value: string);
begin
FRootDirectories.Clear;
FRootDirectories.Add(Value);
end;
procedure TJclFileEnumerator.SetFileMask(const Value: string);
begin
{ TODO : UNIX : ? }
StrToStrings(Value, DirSeparator, FFileMasks, False);
end;
procedure TJclFileEnumerator.SetFileMasks(const Value: TStrings);
begin
FileMasks.Assign(Value);
end;
procedure TJclFileEnumerator.SetFileSizeMax(const Value: Int64);
begin
FFileSizeMax := Value;
end;
procedure TJclFileEnumerator.SetFileSizeMin(const Value: Int64);
begin
FFileSizeMin := Value;
end;
procedure TJclFileEnumerator.SetIncludeHiddenSubDirectories(
const Value: Boolean);
begin
SetOption(fsIncludeHiddenSubDirectories, Value);
end;
procedure TJclFileEnumerator.SetIncludeSubDirectories(
const Value: Boolean);
begin
SetOption(fsIncludeSubDirectories, Value);
end;
procedure TJclFileEnumerator.SetLastChangeAfter(const Value: TDateTime);
begin
FLastChangeAfter := Value;
end;
procedure TJclFileEnumerator.SetLastChangeBefore(const Value: TDateTime);
begin
FLastChangeBefore := Value;
end;
procedure TJclFileEnumerator.SetOnEnterDirectory(
const Value: TFileHandler);
begin
FOnEnterDirectory := Value;
end;
procedure TJclFileEnumerator.SetOnTerminateTask(
const Value: TFileSearchTerminationEvent);
begin
FOnTerminateTask := Value;
end;
procedure TJclFileEnumerator.SetOption(const Option: TFileSearchOption; const Value: Boolean);
begin
if Value then
Include(FOptions, Option)
else
Exclude(FOptions, Option);
end;
procedure TJclFileEnumerator.SetOptions(const Value: TFileSearchOptions);
begin
FOptions := Value;
end;
procedure TJclFileEnumerator.SetSubDirectoryMask(const Value: string);
begin
FSubDirectoryMask := Value;
end;
procedure TJclFileEnumerator.SetSynchronizationMode(
const Value: TFileEnumeratorSyncMode);
begin
FSynchronizationMode := Value;
end;
function FileSearch: IJclFileEnumerator;
begin
Result := TJclFileEnumerator.Create;
end;
function SamePath(const Path1, Path2: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := AnsiSameText(PathGetLongName(Path1), PathGetLongName(Path2));
{$ELSE ~MSWINDOWS}
Result := Path1 = Path2;
{$ENDIF ~MSWINDOWS}
end;
// add items at the end
procedure PathListAddItems(var List: string; const Items: string);
begin
ListAddItems(List, DirSeparator, Items);
end;
// add items at the end if they are not present
procedure PathListIncludeItems(var List: string; const Items: string);
var
StrList, NewItems: TStringList;
IndexNew, IndexList: Integer;
Item: string;
Duplicate: Boolean;
begin
StrList := TStringList.Create;
try
StrToStrings(List, DirSeparator, StrList);
NewItems := TStringList.Create;
try
StrToStrings(Items, DirSeparator, NewItems);
for IndexNew := 0 to NewItems.Count - 1 do
begin
Item := NewItems.Strings[IndexNew];
Duplicate := False;
for IndexList := 0 to StrList.Count - 1 do
if SamePath(Item, StrList.Strings[IndexList]) then
begin
Duplicate := True;
Break;
end;
if not Duplicate then
StrList.Add(Item);
end;
List := StringsToStr(StrList, DirSeparator);
finally
NewItems.Free;
end;
finally
StrList.Free;
end;
end;
// delete multiple items
procedure PathListDelItems(var List: string; const Items: string);
var
StrList, RemItems: TStringList;
IndexRem, IndexList: Integer;
Item: string;
begin
StrList := TStringList.Create;
try
StrToStrings(List, DirSeparator, StrList);
RemItems := TStringList.Create;
try
StrToStrings(Items, DirSeparator, RemItems);
for IndexRem := 0 to RemItems.Count - 1 do
begin
Item := RemItems.Strings[IndexRem];
for IndexList := StrList.Count - 1 downto 0 do
if SamePath(Item, StrList.Strings[IndexList]) then
StrList.Delete(IndexList);
end;
List := StringsToStr(StrList, DirSeparator);
finally
RemItems.Free;
end;
finally
StrList.Free;
end;
end;
// delete one item
procedure PathListDelItem(var List: string; const Index: Integer);
begin
ListDelItem(List, DirSeparator, Index);
end;
// return the number of item
function PathListItemCount(const List: string): Integer;
begin
Result := ListItemCount(List, DirSeparator);
end;
// return the Nth item
function PathListGetItem(const List: string; const Index: Integer): string;
begin
Result := ListGetItem(List, DirSeparator, Index);
end;
// set the Nth item
procedure PathListSetItem(var List: string; const Index: Integer; const Value: string);
begin
ListSetItem(List, DirSeparator, Index, Value);
end;
// return the index of an item
function PathListItemIndex(const List, Item: string): Integer;
var
StrList: TStringList;
IndexList: Integer;
begin
StrList := TStringList.Create;
try
StrToStrings(List, DirSeparator, StrList);
Result := -1;
for IndexList := 0 to StrList.Count - 1 do
if SamePath(StrList.Strings[IndexList], Item) then
begin
Result := IndexList;
Break;
end;
finally
StrList.Free;
end;
end;
// additional functions to access the commandline parameters of an application
// returns the name of the command line parameter at position index, which is
// separated by the given separator, if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamName (Index : Integer; const Separator : string = '=';
const AllowedPrefixCharacters : string = '-/'; TrimName : Boolean = true) : string;
var s: string;
p: Integer;
begin
if (index > 0) and (index <= ParamCount) then
begin
s := ParamStr(index);
if Pos(Copy(s, 1, 1), AllowedPrefixCharacters) > 0 then
s := Copy (s, 2, Length(s)-1);
p := Pos(Separator, s);
if p > 0 then
s := Copy (s, 1, p-1);
if TrimName then
s := Trim(s);
Result := s;
end
else
Result := '';
end;
// returns the value of the command line parameter at position index, which is
// separated by the given separator
function ParamValue (Index : Integer; const Separator : string = '='; TrimValue : Boolean = true) : string;
var s: string;
p: Integer;
begin
if (index > 0) and (index <= ParamCount) then
begin
s := ParamStr(index);
p := Pos(Separator, s);
if p > 0 then
s := Copy (s, p+1, Length(s)-p);
if TrimValue then
s := Trim(s);
Result := s;
end
else
Result := '';
end;
// seaches a command line parameter where the namepart is the searchname
// and returns the value which is which by the given separator.
// CaseSensitive defines the search type. if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamValue (const SearchName : string; const Separator : string = '=';
CaseSensitive : Boolean = False;
const AllowedPrefixCharacters : string = '-/'; TrimValue : Boolean = true) : string;
var pName : string;
i : Integer;
begin
Result := '';
for i := 1 to ParamCount do
begin
pName := ParamName(i, Separator, AllowedPrefixCharacters, True);
if (CaseSensitive and (pName = Trim(SearchName))) or
(UpperCase(pName) = Trim(UpperCase(SearchName))) then
begin
Result := ParamValue (i, Separator, TrimValue);
exit;
end;
end;
end;
// seaches a command line parameter where the namepart is the searchname
// and returns the position index. if no separator is defined, the full paramstr is compared.
// CaseSensitive defines the search type. if the first character of the name part
// is one of the AllowedPrefixCharacters, this character will be deleted.
function ParamPos (const SearchName : string; const Separator : string = '=';
CaseSensitive : Boolean = False;
const AllowedPrefixCharacters : string = '-/'): Integer;
var pName : string;
i : Integer;
begin
Result := -1;
for i := 1 to ParamCount do
begin
pName := ParamName(i, Separator, AllowedPrefixCharacters, True);
if (CaseSensitive and (pName = SearchName)) or
(UpperCase(pName) = UpperCase(SearchName)) then
begin
Result := i;
Exit;
end;
end;
end;
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
end.