mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-26 21:43:58 +02:00
1610 lines
46 KiB
ObjectPascal
1610 lines
46 KiB
ObjectPascal
{
|
|
**********************************************************************
|
|
This file is part of LazUtils.
|
|
All functions are thread safe unless explicitely stated
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
**********************************************************************
|
|
}
|
|
unit LazFileUtils;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$i lazutils_defines.inc}
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, SysConst, LazUTF8, LazUtilsStrConsts;
|
|
|
|
{$IF defined(Windows) or defined(darwin) or defined(HASAMIGA)}
|
|
{$define CaseInsensitiveFilenames}
|
|
{$IFDEF Windows}
|
|
{$define HasUNCPaths}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IF defined(CaseInsensitiveFilenames)}
|
|
{$define NotLiteralFilenames} // e.g. HFS+ normalizes file names
|
|
{$ENDIF}
|
|
|
|
function CompareFilenames(const Filename1, Filename2: string): integer; overload;
|
|
function CompareFilenamesIgnoreCase(const Filename1, Filename2: string): integer;
|
|
function CompareFilenameStarts(const Filename1, Filename2: string): integer;
|
|
function CompareFilenames(Filename1: PChar; Len1: integer;
|
|
Filename2: PChar; Len2: integer): integer; overload;
|
|
function CompareFilenamesP(Filename1, Filename2: PChar; IgnoreCase: boolean=false): integer;
|
|
function CompareFileExt(const Filename: string; Ext: string; CaseSensitive: boolean): integer;
|
|
function CompareFileExt(const Filename, Ext: string): integer;
|
|
function FilenameExtIs(const Filename,Ext: string; CaseSensitive: boolean=false): boolean;
|
|
function FilenameExtIn(const Filename: string; Exts: array of string;
|
|
CaseSensitive: boolean=false): boolean;
|
|
|
|
function DirPathExists(DirectoryName: string): boolean;
|
|
function DirectoryIsWritable(const DirectoryName: string): boolean;
|
|
function ExtractFileNameOnly(const AFilename: string): string;
|
|
function ExtractFileNameWithoutExt(const AFilename: string): string;
|
|
function FilenameIsAbsolute(const TheFilename: string):boolean;
|
|
function FilenameIsWinAbsolute(const TheFilename: string):boolean;
|
|
function FilenameIsUnixAbsolute(const TheFilename: string):boolean;
|
|
function ForceDirectory(DirectoryName: string): boolean;
|
|
procedure CheckIfFileIsExecutable(const AFilename: string);
|
|
procedure CheckIfFileIsSymlink(const AFilename: string);
|
|
function FileIsExecutable(const AFilename: string): boolean;
|
|
function FileIsSymlink(const AFilename: string): boolean;
|
|
function FileIsHardLink(const AFilename: string): boolean;
|
|
function FileIsReadable(const AFilename: string): boolean;
|
|
function FileIsWritable(const AFilename: string): boolean;
|
|
function FileIsText(const AFilename: string): boolean;
|
|
function FileIsText(const AFilename: string; out FileReadable: boolean): boolean;
|
|
function FilenameIsTrimmed(const TheFilename: string): boolean;
|
|
function FilenameIsTrimmed(StartPos: PChar; NameLen: integer): boolean;
|
|
function TrimFilename(const AFilename: string): string;
|
|
function ResolveDots(const AFilename: string): string;
|
|
function CleanAndExpandFilename(const Filename: string): string; // empty string returns current directory
|
|
function CleanAndExpandDirectory(const Filename: string): string; // empty string returns current directory
|
|
function TrimAndExpandFilename(const Filename: string; const BaseDir: string = ''): string; // empty string returns empty string
|
|
function TrimAndExpandDirectory(const Filename: string; const BaseDir: string = ''): string; // empty string returns empty string
|
|
function CreateAbsolutePath(const Filename, BaseDirectory: string): string;
|
|
function TryCreateRelativePath(const Dest, Source: String; UsePointDirectory: boolean;
|
|
AlwaysRequireSharedBaseFolder: Boolean; out RelPath: String): Boolean;
|
|
function CreateRelativePath(const Filename, BaseDirectory: string;
|
|
UsePointDirectory: boolean = false; AlwaysRequireSharedBaseFolder: Boolean = True): string;
|
|
function FileIsInPath(const Filename, Path: string): boolean;
|
|
function PathIsInPath(const Path, Directory: string): boolean;
|
|
// Storten a file name for display.
|
|
function ShortDisplayFilename(const aFileName: string; aLimit: Integer = 80): string;
|
|
|
|
type
|
|
TPathDelimSwitch = (
|
|
pdsNone, // no change
|
|
pdsSystem, // switch to current PathDelim
|
|
pdsUnix, // switch to slash /
|
|
pdsWindows // switch to backslash \
|
|
);
|
|
const
|
|
PathDelimSwitchToDelim: array[TPathDelimSwitch] of char = (
|
|
PathDelim, // pdsNone
|
|
PathDelim, // pdsSystem
|
|
'/', // pdsUnix
|
|
'\' // pdsWindows
|
|
);
|
|
|
|
// Path delimiters
|
|
procedure ForcePathDelims(Var FileName: string);
|
|
function GetForcedPathDelims(const FileName: string): string;
|
|
function AppendPathDelim(const Path: string): string;
|
|
function ChompPathDelim(const Path: string): string;
|
|
function SwitchPathDelims(const Filename: string; Switch: TPathDelimSwitch): string;
|
|
function SwitchPathDelims(const Filename: string; Switch: boolean): string;
|
|
function CheckPathDelim(const OldPathDelim: string; out Changed: boolean): TPathDelimSwitch;
|
|
function IsCurrentPathDelim(Switch: TPathDelimSwitch): boolean;
|
|
|
|
// search paths
|
|
function CreateAbsoluteSearchPath(const SearchPath, BaseDirectory: string): string;
|
|
function CreateRelativeSearchPath(const SearchPath, BaseDirectory: string): string;
|
|
function MinimizeSearchPath(const SearchPath: string): string;
|
|
function FindPathInSearchPath(APath: PChar; APathLen: integer;
|
|
SearchPath: PChar; SearchPathLen: integer): PChar; overload;
|
|
function FindPathInSearchPath(const APath, SearchPath: string): integer; overload;
|
|
|
|
// file operations
|
|
function FileExistsUTF8(const Filename: string): boolean;
|
|
function FileAgeUTF8(const FileName: string): Longint; // -1 if not exists
|
|
function DirectoryExistsUTF8(const Directory: string): Boolean;
|
|
function ExpandFileNameUTF8(const FileName: string; {const} BaseDir: string = ''): string;
|
|
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
|
|
function FindNextUTF8(var Rslt: TSearchRec): Longint;
|
|
procedure FindCloseUTF8(var F: TSearchrec); inline;
|
|
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
|
|
function FileGetAttrUTF8(const FileName: String): Longint;
|
|
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
|
|
function DeleteFileUTF8(const FileName: String): Boolean;
|
|
function RenameFileUTF8(const OldName, NewName: String): Boolean;
|
|
function FileSearchUTF8(const Name, DirList : String; ImplicitCurrentDir : Boolean = True): String;
|
|
function FileIsReadOnlyUTF8(const FileName: String): Boolean;
|
|
function GetCurrentDirUTF8: String;
|
|
function SetCurrentDirUTF8(const NewDir: String): Boolean;
|
|
function CreateDirUTF8(const NewDir: String): Boolean;
|
|
function RemoveDirUTF8(const Dir: String): Boolean;
|
|
function ForceDirectoriesUTF8(const Dir: string): Boolean;
|
|
|
|
function FileOpenUTF8(Const FileName : string; Mode : Integer) : THandle;
|
|
function FileCreateUTF8(Const FileName : string) : THandle; overload;
|
|
function FileCreateUTF8(Const FileName : string; Rights: Cardinal) : THandle; overload;
|
|
Function FileCreateUtf8(Const FileName : String; ShareMode : Integer; Rights : Cardinal) : THandle; overload;
|
|
|
|
function FileSizeUtf8(const Filename: string): int64;
|
|
function GetFileDescription(const AFilename: string): string;
|
|
function ReadAllLinks(const Filename: string;
|
|
{%H-}ExceptionOnError: boolean): string; // if a link is broken returns ''
|
|
function TryReadAllLinks(const Filename: string): string; // if a link is broken returns Filename
|
|
function GetShellLinkTarget(const FileName: string): string;
|
|
|
|
// for debugging
|
|
function DbgSFileAttr(Attr: LongInt): String;
|
|
|
|
|
|
type
|
|
TPhysicalFilenameOnError = (pfeException,pfeEmpty,pfeOriginal);
|
|
function GetPhysicalFilename(const Filename: string;
|
|
OnError: TPhysicalFilenameOnError): string;
|
|
{$IFDEF Unix}
|
|
function GetUnixPhysicalFilename(const Filename: string;
|
|
ExceptionOnError: boolean): string; // if a link is broken returns ''
|
|
{$ENDIF}
|
|
|
|
function GetAppConfigDirUTF8(Global: Boolean; Create: boolean = false): string;
|
|
function GetAppConfigFileUTF8(Global: Boolean; SubDir: boolean = false;
|
|
CreateDir: boolean = false): string;
|
|
function GetTempFileNameUTF8(const Dir, Prefix: String): String;
|
|
|
|
// UNC paths
|
|
function IsUNCPath(const {%H-}Path: String): Boolean;
|
|
function ExtractUNCVolume(const {%H-}Path: String): String;
|
|
function ExtractFileRoot(FileName: String): String;
|
|
|
|
// darwin paths
|
|
{$IFDEF darwin}
|
|
function GetDarwinSystemFilename(Filename: string): string;
|
|
function GetDarwinNormalizedFilename(Filename: string; nForm:Integer=2): string;
|
|
{$ENDIF}
|
|
|
|
// windows paths
|
|
{$IFDEF windows}
|
|
function SHGetFolderPathUTF8(ID : Integer) : String;
|
|
{$ENDIF}
|
|
|
|
// Command line
|
|
procedure SplitCmdLineParams(const Params: string; ParamList: TStrings;
|
|
ReadBackslash: boolean = false);
|
|
function StrToCmdLineParam(const Param: string): string;
|
|
function MergeCmdLineParams(ParamList: TStrings): string;
|
|
// ToDo: Study if they are needed or if the above functions could be used instead.
|
|
procedure SplitCmdLine(const CmdLine: string;
|
|
out ProgramFilename, Params: string);
|
|
function PrepareCmdLineOption(const Option: string): string;
|
|
|
|
|
|
type
|
|
TInvalidateFileStateCacheEvent = procedure(const Filename: string);
|
|
var
|
|
OnInvalidateFileStateCache: TInvalidateFileStateCacheEvent = nil;
|
|
procedure InvalidateFileStateCache(const Filename: string = ''); inline;
|
|
|
|
implementation
|
|
|
|
// to get more detailed error messages consider the os
|
|
uses
|
|
{$IFDEF Windows}
|
|
Windows {$IFnDEF WinCE}, ShlObj, ActiveX, WinDirs{$ENDIF};
|
|
{$ELSE}
|
|
{$IFDEF HASAMIGA}
|
|
exec, amigados;
|
|
{$ELSE}
|
|
{$IFDEF darwin}
|
|
MacOSAll,
|
|
{$ENDIF}
|
|
Unix, BaseUnix;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
{$I lazfileutils.inc}
|
|
{$IFDEF windows}
|
|
{$I winlazfileutils.inc}
|
|
{$ELSE}
|
|
{$IFDEF HASAMIGA}
|
|
{$I amigalazfileutils.inc}
|
|
{$ELSE}
|
|
{$I unixlazfileutils.inc}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
function CompareFilenames(const Filename1, Filename2: string): integer;
|
|
{$IFDEF darwin}
|
|
var
|
|
F1: CFStringRef;
|
|
F2: CFStringRef;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF darwin}
|
|
if Filename1=Filename2 then exit(0);
|
|
if (Filename1='') or (Filename2='') then
|
|
exit(length(Filename2)-length(Filename1));
|
|
F1:=CFStringCreateWithCString(nil,Pointer(Filename1),kCFStringEncodingUTF8);
|
|
F2:=CFStringCreateWithCString(nil,Pointer(Filename2),kCFStringEncodingUTF8);
|
|
Result:=CFStringCompare(F1,F2,kCFCompareNonliteral
|
|
{$IFDEF CaseInsensitiveFilenames}+kCFCompareCaseInsensitive{$ENDIF});
|
|
CFRelease(F1);
|
|
CFRelease(F2);
|
|
{$ELSE}
|
|
{$IFDEF CaseInsensitiveFilenames}
|
|
Result:=UTF8CompareText(Filename1, Filename2);
|
|
{$ELSE}
|
|
Result:=CompareStr(Filename1, Filename2);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function CompareFilenamesIgnoreCase(const Filename1, Filename2: string): integer;
|
|
{$IFDEF darwin}
|
|
var
|
|
F1: CFStringRef;
|
|
F2: CFStringRef;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF darwin}
|
|
if Filename1=Filename2 then exit(0);
|
|
F1:=CFStringCreateWithCString(nil,Pointer(Filename1),kCFStringEncodingUTF8);
|
|
F2:=CFStringCreateWithCString(nil,Pointer(Filename2),kCFStringEncodingUTF8);
|
|
Result:=CFStringCompare(F1,F2,kCFCompareNonliteral+kCFCompareCaseInsensitive);
|
|
CFRelease(F1);
|
|
CFRelease(F2);
|
|
{$ELSE}
|
|
// AnsiCompareText uses UTF8CompareText on Windows, elsewhere system string manager.
|
|
Result:=AnsiCompareText(Filename1, Filename2);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function CompareFilenameStarts(const Filename1, Filename2: string): integer;
|
|
var
|
|
len1: Integer;
|
|
len2: Integer;
|
|
begin
|
|
len1:=length(Filename1);
|
|
len2:=length(Filename2);
|
|
if len1=len2 then begin
|
|
Result:=CompareFilenames(Filename1,Filename2);
|
|
exit;
|
|
end else if len1>len2 then
|
|
Result:=CompareFilenames(copy(Filename1,1,len2),Filename2)
|
|
else
|
|
Result:=CompareFilenames(Filename1,copy(Filename2,1,len1));
|
|
if Result<>0 then exit;
|
|
if len1<len2 then
|
|
Result:=-1
|
|
else
|
|
Result:=1;
|
|
end;
|
|
|
|
function CompareFilenames(Filename1: PChar; Len1: integer; Filename2: PChar;
|
|
Len2: integer): integer;
|
|
var
|
|
{$IFDEF NotLiteralFilenames}
|
|
File1: string;
|
|
File2: string;
|
|
{$ELSE}
|
|
i: Integer;
|
|
{$ENDIF}
|
|
begin
|
|
if (Len1=0) or (Len2=0) then begin
|
|
Result:=Len1-Len2;
|
|
exit;
|
|
end;
|
|
{$IFDEF NotLiteralFilenames}
|
|
SetLength(File1{%H-},Len1);
|
|
System.Move(Filename1^,File1[1],Len1);
|
|
SetLength(File2{%H-},Len2);
|
|
System.Move(Filename2^,File2[1],Len2);
|
|
Result:=CompareFilenames(File1,File2);
|
|
{$ELSE}
|
|
Result:=0;
|
|
i:=0;
|
|
while (Result=0) and ((i<Len1) and (i<Len2)) do begin
|
|
Result:=Ord(Filename1[i])
|
|
-Ord(Filename2[i]);
|
|
Inc(i);
|
|
end;
|
|
if Result=0 Then
|
|
Result:=Len1-Len2;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function CompareFilenamesP(Filename1, Filename2: PChar; IgnoreCase: boolean): integer;
|
|
{$IFDEF darwin}
|
|
var
|
|
F1: CFStringRef;
|
|
F2: CFStringRef;
|
|
Flags: CFStringCompareFlags;
|
|
{$ENDIF}
|
|
begin
|
|
if (Filename1=nil) or (Filename1^=#0) then begin
|
|
if (Filename2=nil) or (Filename2^=#0) then begin
|
|
// both empty
|
|
exit(0);
|
|
end else begin
|
|
// filename1 empty, filename2 not empty
|
|
exit(-1);
|
|
end;
|
|
end else if (Filename2=nil) or (Filename2^=#0) then begin
|
|
// filename1 not empty, filename2 empty
|
|
exit(1);
|
|
end;
|
|
|
|
{$IFDEF CaseInsensitiveFilenames}
|
|
// this platform is by default case insensitive
|
|
IgnoreCase:=true;
|
|
{$ENDIF}
|
|
{$IFDEF darwin}
|
|
F1:=CFStringCreateWithCString(nil,Pointer(Filename1),kCFStringEncodingUTF8);
|
|
F2:=CFStringCreateWithCString(nil,Pointer(Filename2),kCFStringEncodingUTF8);
|
|
Flags:=kCFCompareNonliteral;
|
|
if IgnoreCase then Flags+=kCFCompareCaseInsensitive;
|
|
Result:=CFStringCompare(F1,F2,Flags);
|
|
CFRelease(F1);
|
|
CFRelease(F2);
|
|
{$ELSE}
|
|
if IgnoreCase then // compare case insensitive
|
|
Result:=UTF8CompareTextP(Filename1, Filename2)
|
|
else begin
|
|
// compare literally
|
|
while (Filename1^=Filename2^) and (Filename1^<>#0) do begin
|
|
Inc(Filename1);
|
|
Inc(Filename2);
|
|
end;
|
|
Result:=ord(Filename1^)-ord(Filename2^);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function CompareFileExt(const Filename: string; Ext: string; CaseSensitive: boolean): integer;
|
|
// Ext can contain a point or not
|
|
var
|
|
FnExt: String;
|
|
FnPos: integer;
|
|
begin
|
|
// Filename
|
|
FnPos := length(Filename);
|
|
while (FnPos>=1) and (Filename[FnPos]<>'.') do dec(FnPos);
|
|
if FnPos < 1 then
|
|
exit(1); // no extension in filename
|
|
FnExt := Copy(Filename, FnPos+1, length(FileName)); // FnPos+1 skips point
|
|
// Ext
|
|
if (length(Ext) > 1) and (Ext[1] = '.') then
|
|
Delete(Ext, 1, 1);
|
|
// compare extensions
|
|
if CaseSensitive then
|
|
Result := CompareStr(FnExt, Ext)
|
|
else
|
|
Result := UTF8CompareLatinTextFast(FnExt, Ext);
|
|
if Result < 0 then
|
|
Result := -1
|
|
else if Result > 0 then
|
|
Result := 1;
|
|
end;
|
|
|
|
function CompareFileExt(const Filename, Ext: string): integer;
|
|
begin
|
|
Result := CompareFileExt(Filename, Ext,
|
|
{$IFDEF CaseInsensitiveFilenames} False {$ELSE} True {$ENDIF} );
|
|
end;
|
|
|
|
function FilenameExtIs(const Filename, Ext: string; CaseSensitive: boolean): boolean;
|
|
// Return True if Filename has an extension Ext.
|
|
// Ext can contain a point or not. Case-insensitive comparison supports only ASCII.
|
|
var
|
|
FnExtLen, ExtLen: integer;
|
|
FnStart, FnEnd, FnP, ExtP: PChar;
|
|
begin
|
|
// Filename
|
|
FnStart := PChar(Filename);
|
|
FnEnd := FnStart + Length(Filename) - 1;
|
|
FnP := FnEnd;
|
|
while (FnP >= FnStart) and (FnP^ <> '.') do
|
|
Dec(FnP);
|
|
if FnP < FnStart then
|
|
exit(False); // no extension in filename
|
|
Inc(FnP); // Skip '.' in Filename
|
|
FnExtLen := 1 + FnEnd - FnP;
|
|
// Ext
|
|
ExtLen := Length(Ext);
|
|
ExtP := PChar(Ext);
|
|
if ExtP^ = '.' then
|
|
begin
|
|
Inc(ExtP); // Skip '.' in Ext
|
|
Dec(ExtLen);
|
|
end;
|
|
if ExtLen <> FnExtLen then
|
|
exit(False); // Ext has different length than Filename's extension
|
|
// compare extensions
|
|
if CaseSensitive then
|
|
Result := StrLComp(ExtP, FnP, ExtLen) = 0
|
|
else
|
|
Result := StrLIComp(ExtP, FnP, ExtLen) = 0
|
|
end;
|
|
|
|
function FilenameExtIn(const Filename: string; Exts: array of string;
|
|
CaseSensitive: boolean): boolean;
|
|
// Return True if Filename's extension is one of Exts.
|
|
// Ext can contain a point or not. Case-insensitive comparison supports only ASCII.
|
|
var
|
|
FnExtLen, ExtLen, i: integer;
|
|
FnStart, FnEnd, FnP, ExtP: PChar;
|
|
begin
|
|
// Filename
|
|
FnStart := PChar(Filename);
|
|
FnEnd := FnStart + Length(Filename) - 1;
|
|
FnP := FnEnd;
|
|
while (FnP >= FnStart) and (FnP^ <> '.') do
|
|
Dec(FnP);
|
|
if FnP < FnStart then
|
|
exit(False); // no extension in filename
|
|
Inc(FnP); // Skip '.' in Filename
|
|
FnExtLen := 1 + FnEnd - FnP;
|
|
// Extensions
|
|
for i := low(Exts) to high(Exts) do
|
|
begin
|
|
ExtLen := Length(Exts[i]);
|
|
ExtP := PChar(Exts[i]);
|
|
if ExtP^ = '.' then
|
|
begin
|
|
Inc(ExtP); // Skip '.' in Ext
|
|
Dec(ExtLen);
|
|
end;
|
|
if ExtLen <> FnExtLen then
|
|
continue; // Ext has different length than Filename's extension
|
|
// compare extensions
|
|
if CaseSensitive then
|
|
Result := StrLComp(ExtP, FnP, ExtLen) = 0
|
|
else
|
|
Result := StrLIComp(ExtP, FnP, ExtLen) = 0;
|
|
if Result then exit;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
{$IFDEF darwin}
|
|
function GetDarwinSystemFilename(Filename: string): string;
|
|
var
|
|
s: CFStringRef;
|
|
l: CFIndex;
|
|
begin
|
|
if Filename='' then exit('');
|
|
s:=CFStringCreateWithCString(nil,Pointer(Filename),kCFStringEncodingUTF8);
|
|
l:=CFStringGetMaximumSizeOfFileSystemRepresentation(s);
|
|
SetLength(Result,l);
|
|
if Result<>'' then begin
|
|
CFStringGetFileSystemRepresentation(s,@Result[1],length(Result));
|
|
SetLength(Result,StrLen(PChar(Result)));
|
|
end;
|
|
CFRelease(s);
|
|
end;
|
|
|
|
// borrowed from CarbonProcs
|
|
function CFStringToStr(AString: CFStringRef; Encoding: CFStringEncoding): String;
|
|
var
|
|
Str: Pointer;
|
|
StrSize: CFIndex;
|
|
StrRange: CFRange;
|
|
begin
|
|
if AString = nil then
|
|
begin
|
|
Result := '';
|
|
Exit;
|
|
end;
|
|
|
|
// Try the quick way first
|
|
Str := CFStringGetCStringPtr(AString, Encoding);
|
|
if Str <> nil then
|
|
Result := PChar(Str)
|
|
else
|
|
begin
|
|
// if that doesn't work this will
|
|
StrRange.location := 0;
|
|
StrRange.length := CFStringGetLength(AString);
|
|
|
|
CFStringGetBytes(AString, StrRange, Encoding,
|
|
Ord('?'), False, nil, 0, StrSize{%H-});
|
|
SetLength(Result, StrSize);
|
|
|
|
if StrSize > 0 then
|
|
CFStringGetBytes(AString, StrRange, Encoding,
|
|
Ord('?'), False, @Result[1], StrSize, StrSize);
|
|
end;
|
|
end;
|
|
|
|
//NForm can be one of
|
|
//kCFStringNormalizationFormD = 0; // Canonical Decomposition
|
|
//kCFStringNormalizationFormKD = 1; // Compatibility Decomposition
|
|
//kCFStringNormalizationFormC = 2; // Canonical Decomposition followed by Canonical Composition
|
|
//kCFStringNormalizationFormKC = 3; // Compatibility Decomposition followed by Canonical Composition
|
|
function GetDarwinNormalizedFilename(Filename: string; nForm:Integer=2): string;
|
|
var
|
|
theString: CFStringRef;
|
|
Mutable: CFMutableStringRef;
|
|
begin
|
|
theString:=CFStringCreateWithCString(nil, Pointer(FileName), kCFStringEncodingUTF8);
|
|
Mutable := CFStringCreateMutableCopy(nil, 0, theString);
|
|
if (NForm<0) or (NForm>3) then NForm := kCFStringNormalizationFormC;
|
|
CFStringNormalize(Mutable, NForm);
|
|
Result := CFStringToStr(Mutable, kCFStringEncodingUTF8);
|
|
CFRelease(Mutable);
|
|
CFRelease(theString);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
function ExtractFileNameOnly(const AFilename: string): string;
|
|
var
|
|
StartPos: Integer;
|
|
ExtPos: Integer;
|
|
begin
|
|
StartPos:=length(AFilename)+1;
|
|
while (StartPos>1)
|
|
and not (AFilename[StartPos-1] in AllowDirectorySeparators)
|
|
{$IF defined(Windows) or defined(HASAMIGA)}and (AFilename[StartPos-1]<>':'){$ENDIF}
|
|
do
|
|
dec(StartPos);
|
|
ExtPos:=length(AFilename);
|
|
while (ExtPos>=StartPos) and (AFilename[ExtPos]<>'.') do
|
|
dec(ExtPos);
|
|
if (ExtPos<StartPos) then ExtPos:=length(AFilename)+1;
|
|
Result:=copy(AFilename,StartPos,ExtPos-StartPos);
|
|
end;
|
|
|
|
function ExtractFileNameWithoutExt(const AFilename: string): string;
|
|
var
|
|
p: Integer;
|
|
begin
|
|
Result:=AFilename;
|
|
p:=length(Result);
|
|
while (p>0) do begin
|
|
case Result[p] of
|
|
PathDelim: exit;
|
|
{$ifdef windows}
|
|
'/': if ('/' in AllowDirectorySeparators) then exit;
|
|
{$endif}
|
|
'.': exit(copy(Result,1, p-1));
|
|
end;
|
|
dec(p);
|
|
end;
|
|
end;
|
|
|
|
function DirPathExists(DirectoryName: string): boolean;
|
|
begin
|
|
Result:=DirectoryExistsUTF8(ChompPathDelim(DirectoryName));
|
|
end;
|
|
|
|
function DirectoryIsWritable(const DirectoryName: string): boolean;
|
|
var
|
|
TempFilename: String;
|
|
s: String;
|
|
fHandle: THANDLE;
|
|
begin
|
|
Result:=false;
|
|
if not DirPathExists(DirectoryName) then exit;
|
|
TempFilename:=SysUtils.GetTempFilename(AppendPathDelim(DirectoryName),'tstperm');
|
|
fHandle := FileCreateUtf8(TempFileName, fmCreate, 438);
|
|
if (THandle(fHandle) <> feInvalidHandle) then
|
|
begin
|
|
s:='WriteTest';
|
|
if FileWrite(fHandle,S[1],Length(S)) > 0 then Result := True;
|
|
FileClose(fHandle);
|
|
if not DeleteFileUTF8(TempFilename) then
|
|
InvalidateFileStateCache(TempFilename);
|
|
end;
|
|
end;
|
|
|
|
function ForceDirectory(DirectoryName: string): boolean;
|
|
var
|
|
i: integer;
|
|
Dir: string;
|
|
begin
|
|
DirectoryName:=AppendPathDelim(DirectoryName);
|
|
i:=1;
|
|
while i<=length(DirectoryName) do begin
|
|
if DirectoryName[i] in AllowDirectorySeparators then begin
|
|
// optimize paths like \foo\\bar\\foobar
|
|
while (i<length(DirectoryName)) and (DirectoryName[i+1] in AllowDirectorySeparators) do
|
|
Delete(DirectoryName,i+1,1);
|
|
Dir:=copy(DirectoryName,1,i-1);
|
|
if (Dir<>'') and not DirPathExists(Dir) then begin
|
|
Result:=CreateDirUTF8(Dir);
|
|
if not Result then exit;
|
|
end;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function FileIsText(const AFilename: string): boolean;
|
|
var
|
|
FileReadable: Boolean;
|
|
begin
|
|
Result:=FileIsText(AFilename,FileReadable);
|
|
if FileReadable then ;
|
|
end;
|
|
|
|
function FileIsText(const AFilename: string; out FileReadable: boolean): boolean;
|
|
var
|
|
Buf: string;
|
|
Len: integer;
|
|
p: PChar;
|
|
ZeroAllowed: Boolean;
|
|
fHandle: THandle;
|
|
const
|
|
BufSize = 2048;
|
|
begin
|
|
Result:=false;
|
|
FileReadable:=true;
|
|
fHandle := FileOpenUtf8(AFileName, fmOpenRead or fmShareDenyNone);
|
|
if (THandle(fHandle) <> feInvalidHandle) then
|
|
begin
|
|
try
|
|
Len:=BufSize;
|
|
SetLength(Buf{%H-},Len+1);
|
|
Len := FileRead(fHandle,Buf[1],Len);
|
|
|
|
if Len>0 then begin
|
|
Buf[Len+1]:=#0;
|
|
p:=PChar(Buf);
|
|
ZeroAllowed:=false;
|
|
if (p[0]=#$EF) and (p[1]=#$BB) and (p[2]=#$BF) then begin
|
|
// UTF-8 BOM (Byte Order Mark)
|
|
inc(p,3);
|
|
end else if (p[0]=#$FF) and (p[1]=#$FE) then begin
|
|
// ucs-2le BOM FF FE
|
|
inc(p,2);
|
|
ZeroAllowed:=true;
|
|
end else if (p[0]=#$FE) and (p[1]=#$FF) then begin
|
|
// ucs-2be BOM FE FF
|
|
inc(p,2);
|
|
ZeroAllowed:=true;
|
|
end;
|
|
while true do begin
|
|
case p^ of
|
|
#0:
|
|
if p-PChar(Buf)>=Len then
|
|
break
|
|
else if not ZeroAllowed then
|
|
exit;
|
|
// #10,#13: new line
|
|
// #12: form feed
|
|
// #26: end of file
|
|
#1..#8,#11,#14..#25,#27..#31: exit;
|
|
end;
|
|
inc(p);
|
|
end;
|
|
Result:=true;
|
|
end else
|
|
Result:=true;
|
|
finally
|
|
FileClose(fHandle);
|
|
end
|
|
end
|
|
else
|
|
FileReadable := False;
|
|
end;
|
|
|
|
function FilenameIsTrimmed(const TheFilename: string): boolean;
|
|
begin
|
|
Result:=FilenameIsTrimmed(PChar(Pointer(TheFilename)),// pointer type cast avoids #0 check
|
|
length(TheFilename));
|
|
end;
|
|
|
|
function FilenameIsTrimmed(StartPos: PChar; NameLen: integer): boolean;
|
|
var
|
|
i: Integer;
|
|
c: Char;
|
|
begin
|
|
Result:=false;
|
|
if NameLen<=0 then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
// check heading spaces
|
|
if StartPos[0]=' ' then exit;
|
|
// check trailing spaces
|
|
if StartPos[NameLen-1]=' ' then exit;
|
|
// check ./ at start
|
|
if (StartPos[0]='.') and (StartPos[1] in AllowDirectorySeparators) then exit;
|
|
i:=0;
|
|
while i<NameLen do begin
|
|
c:=StartPos[i];
|
|
if not (c in AllowDirectorySeparators) then
|
|
inc(i)
|
|
else begin
|
|
if c<>PathDelim then exit;
|
|
inc(i);
|
|
if i=NameLen then break;
|
|
|
|
// check for double path delimiter
|
|
if (StartPos[i] in AllowDirectorySeparators) then exit;
|
|
|
|
if (StartPos[i]='.') and (i>0) then begin
|
|
inc(i);
|
|
// check /./ or /. at end
|
|
if (StartPos[i] in AllowDirectorySeparators) or (i=NameLen) then exit;
|
|
if StartPos[i]='.' then begin
|
|
inc(i);
|
|
// check /../ or /.. at end
|
|
if (StartPos[i] in AllowDirectorySeparators) or (i=NameLen) then exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TrimFilename(const AFilename: string): string;
|
|
//Trim leading and trailing spaces
|
|
//then call ResolveDots to trim double path delims and expand special dirs like .. and .
|
|
var
|
|
Len, Start: Integer;
|
|
begin
|
|
Result := AFileName;
|
|
Len := Length(AFileName);
|
|
if (Len = 0) or FilenameIsTrimmed(Result) then exit;
|
|
if AFilename[1] = #32 then
|
|
begin
|
|
Start := 1;
|
|
while (Start <= Len) and (AFilename[Start] = #32) do Inc(Start);
|
|
System.Delete(Result,1,Start-1);
|
|
Len := Length(Result);
|
|
end;
|
|
while (Len > 0) and (Result[Len] = #32) do Dec(Len);
|
|
SetLength(Result, Len);
|
|
Result := ResolveDots(Result);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function CleanAndExpandFilename(const Filename: string): string;
|
|
------------------------------------------------------------------------------}
|
|
function CleanAndExpandFilename(const Filename: string): string;
|
|
begin
|
|
Result:=ExpandFileNameUTF8(TrimFileName(Filename));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function CleanAndExpandDirectory(const Filename: string): string;
|
|
------------------------------------------------------------------------------}
|
|
function CleanAndExpandDirectory(const Filename: string): string;
|
|
begin
|
|
Result:=AppendPathDelim(CleanAndExpandFilename(Filename));
|
|
end;
|
|
|
|
function TrimAndExpandFilename(const Filename: string; const BaseDir: string): string;
|
|
begin
|
|
Result:=ChompPathDelim(TrimFilename(Filename));
|
|
if Result='' then exit;
|
|
Result:=TrimFilename(ExpandFileNameUTF8(Result,BaseDir));
|
|
end;
|
|
|
|
function TrimAndExpandDirectory(const Filename: string; const BaseDir: string): string;
|
|
begin
|
|
Result:=TrimFilename(Filename);
|
|
if Result='' then exit;
|
|
Result:=TrimFilename(AppendPathDelim(ExpandFileNameUTF8(Result,BaseDir)));
|
|
end;
|
|
|
|
function FileIsInPath(const Filename, Path: string): boolean;
|
|
var
|
|
ExpFile: String;
|
|
ExpPath: String;
|
|
l: integer;
|
|
begin
|
|
if Path='' then exit(false);
|
|
ExpFile:=ResolveDots(Filename);
|
|
ExpPath:=AppendPathDelim(ResolveDots(Path));
|
|
l:=length(ExpPath);
|
|
Result:=(l>0) and (length(ExpFile)>l) and (ExpFile[l]=PathDelim)
|
|
and (CompareFilenames(ExpPath,LeftStr(ExpFile,l))=0);
|
|
end;
|
|
|
|
function PathIsInPath(const Path, Directory: string): boolean;
|
|
// Note: Under Windows this treats C: as C:\
|
|
var
|
|
ExpPath: String;
|
|
ExpDir: String;
|
|
l: integer;
|
|
begin
|
|
if Path='' then exit(false);
|
|
ExpPath:=AppendPathDelim(ResolveDots(Path));
|
|
ExpDir:=AppendPathDelim(ResolveDots(Directory));
|
|
l:=length(ExpDir);
|
|
Result:=(l>0) and (length(ExpPath)>=l) and (ExpPath[l]=PathDelim)
|
|
and (CompareFilenames(ExpDir,LeftStr(ExpPath,l))=0);
|
|
end;
|
|
|
|
function ShortDisplayFilename(const aFileName: string; aLimit: Integer): string;
|
|
// Shorten a long filename for display.
|
|
// Add '...' after the 2. path delimiter, then the end part of filename.
|
|
var
|
|
StartLen, EndLen, SepCnt: Integer;
|
|
begin
|
|
if Length(aFileName) > aLimit then
|
|
begin
|
|
StartLen := 1;
|
|
SepCnt := 0;
|
|
while StartLen < Length(aFileName) - (aLimit div 2) do
|
|
begin
|
|
if aFileName[StartLen] in AllowDirectorySeparators then
|
|
begin
|
|
Inc(SepCnt);
|
|
if SepCnt = 2 then Break;
|
|
end;
|
|
Inc(StartLen);
|
|
end;
|
|
EndLen := aLimit - StartLen - 3;
|
|
Result := Copy(aFileName, 1, StartLen) + '...'
|
|
+ Copy(aFileName, Length(aFileName)-EndLen+1, EndLen);
|
|
end
|
|
else
|
|
Result := aFileName;
|
|
end;
|
|
|
|
|
|
// Path delimiters
|
|
|
|
procedure ForcePathDelims(var FileName: string);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=1 to length(FileName) do
|
|
{$IFDEF Windows}
|
|
if Filename[i]='/' then
|
|
Filename[i]:='\';
|
|
{$ELSE}
|
|
if Filename[i]='\' then
|
|
Filename[i]:='/';
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function GetForcedPathDelims(const FileName: string): string;
|
|
begin
|
|
Result:=FileName;
|
|
ForcePathDelims(Result);
|
|
end;
|
|
|
|
function AppendPathDelim(const Path: string): string;
|
|
begin
|
|
if (Path<>'') and not (Path[length(Path)] in AllowDirectorySeparators) then
|
|
Result:=Path+PathDelim
|
|
else
|
|
Result:=Path;
|
|
end;
|
|
|
|
function ChompPathDelim(const Path: string): string;
|
|
var
|
|
Len, MinLen: Integer;
|
|
begin
|
|
Result:=Path;
|
|
if Path = '' then
|
|
exit;
|
|
Len:=length(Result);
|
|
if (Result[1] in AllowDirectorySeparators) then begin
|
|
MinLen := 1;
|
|
{$IFDEF HasUNCPaths}
|
|
if (Len >= 2) and (Result[2] in AllowDirectorySeparators) then
|
|
MinLen := 2; // keep UNC '\\', chomp 'a\' to 'a'
|
|
{$ENDIF}
|
|
end
|
|
else begin
|
|
MinLen := 0;
|
|
{$IFdef MSWindows}
|
|
if (Len >= 3) and (Result[1] in ['a'..'z', 'A'..'Z']) and
|
|
(Result[2] = ':') and (Result[3] in AllowDirectorySeparators)
|
|
then
|
|
MinLen := 3;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
while (Len > MinLen) and (Result[Len] in AllowDirectorySeparators) do dec(Len);
|
|
if Len<length(Result) then
|
|
SetLength(Result,Len);
|
|
end;
|
|
|
|
function SwitchPathDelims(const Filename: string; Switch: TPathDelimSwitch): string;
|
|
var
|
|
i: Integer;
|
|
p: Char;
|
|
begin
|
|
Result:=Filename;
|
|
case Switch of
|
|
pdsSystem: p:=PathDelim;
|
|
pdsUnix: p:='/';
|
|
pdsWindows: p:='\';
|
|
else exit;
|
|
end;
|
|
for i:=1 to length(Result) do
|
|
if Result[i] in ['/','\'] then
|
|
Result[i]:=p;
|
|
end;
|
|
|
|
function SwitchPathDelims(const Filename: string; Switch: boolean): string;
|
|
begin
|
|
if Switch then
|
|
Result:=SwitchPathDelims(Filename,pdsSystem)
|
|
else
|
|
Result:=Filename;
|
|
end;
|
|
|
|
function CheckPathDelim(const OldPathDelim: string; out Changed: boolean): TPathDelimSwitch;
|
|
begin
|
|
Changed:=OldPathDelim<>PathDelim;
|
|
if Changed then begin
|
|
if OldPathDelim='/' then
|
|
Result:=pdsUnix
|
|
else if OldPathDelim='\' then
|
|
Result:=pdsWindows
|
|
else
|
|
Result:=pdsSystem;
|
|
end else begin
|
|
Result:=pdsNone;
|
|
end;
|
|
end;
|
|
|
|
function IsCurrentPathDelim(Switch: TPathDelimSwitch): boolean;
|
|
begin
|
|
Result:=(Switch in [pdsNone,pdsSystem])
|
|
or ((Switch=pdsUnix) and (PathDelim='/'))
|
|
or ((Switch=pdsWindows) and (PathDelim='\'));
|
|
end;
|
|
|
|
|
|
function CreateAbsoluteSearchPath(const SearchPath, BaseDirectory: string): string;
|
|
var
|
|
PathLen: Integer;
|
|
EndPos: Integer;
|
|
StartPos: Integer;
|
|
CurDir: String;
|
|
NewCurDir: String;
|
|
DiffLen: Integer;
|
|
BaseDir: String;
|
|
begin
|
|
Result:=SearchPath;
|
|
if (SearchPath='') or (BaseDirectory='') then exit;
|
|
BaseDir:=AppendPathDelim(BaseDirectory);
|
|
|
|
PathLen:=length(Result);
|
|
EndPos:=1;
|
|
while EndPos<=PathLen do begin
|
|
StartPos:=EndPos;
|
|
while (Result[StartPos]=';') do begin
|
|
inc(StartPos);
|
|
if StartPos>PathLen then exit;
|
|
end;
|
|
EndPos:=StartPos;
|
|
while (EndPos<=PathLen) and (Result[EndPos]<>';') do inc(EndPos);
|
|
CurDir:=copy(Result,StartPos,EndPos-StartPos);
|
|
if not FilenameIsAbsolute(CurDir) then begin
|
|
NewCurDir:=BaseDir+CurDir;
|
|
if NewCurDir<>CurDir then begin
|
|
DiffLen:=length(NewCurDir)-length(CurDir);
|
|
Result:=copy(Result,1,StartPos-1)+NewCurDir
|
|
+copy(Result,EndPos,PathLen-EndPos+1);
|
|
inc(EndPos,DiffLen);
|
|
inc(PathLen,DiffLen);
|
|
end;
|
|
end;
|
|
StartPos:=EndPos;
|
|
end;
|
|
end;
|
|
|
|
function CreateRelativeSearchPath(const SearchPath, BaseDirectory: string
|
|
): string;
|
|
var
|
|
PathLen: Integer;
|
|
EndPos: Integer;
|
|
StartPos: Integer;
|
|
CurDir: String;
|
|
NewCurDir: String;
|
|
DiffLen: Integer;
|
|
begin
|
|
Result:=SearchPath;
|
|
if (SearchPath='') or (BaseDirectory='') then exit;
|
|
|
|
PathLen:=length(Result);
|
|
EndPos:=1;
|
|
while EndPos<=PathLen do begin
|
|
StartPos:=EndPos;
|
|
while (Result[StartPos]=';') do begin
|
|
inc(StartPos);
|
|
if StartPos>PathLen then exit;
|
|
end;
|
|
EndPos:=StartPos;
|
|
while (EndPos<=PathLen) and (Result[EndPos]<>';') do inc(EndPos);
|
|
CurDir:=copy(Result,StartPos,EndPos-StartPos);
|
|
if FilenameIsAbsolute(CurDir) then begin
|
|
NewCurDir:=CreateRelativePath(CurDir,BaseDirectory);
|
|
if (NewCurDir<>CurDir) and (NewCurDir='') then
|
|
NewCurDir:='.';
|
|
if NewCurDir<>CurDir then begin
|
|
DiffLen:=length(NewCurDir)-length(CurDir);
|
|
Result:=copy(Result,1,StartPos-1)+NewCurDir
|
|
+copy(Result,EndPos,PathLen-EndPos+1);
|
|
inc(EndPos,DiffLen);
|
|
inc(PathLen,DiffLen);
|
|
end;
|
|
end;
|
|
StartPos:=EndPos;
|
|
end;
|
|
end;
|
|
|
|
function MinimizeSearchPath(const SearchPath: string): string;
|
|
// trim the paths, remove doubles and empty paths
|
|
var
|
|
StartPos: Integer;
|
|
EndPos: LongInt;
|
|
NewPath: String;
|
|
begin
|
|
Result:=SearchPath;
|
|
StartPos:=1;
|
|
while StartPos<=length(Result) do begin
|
|
EndPos:=StartPos;
|
|
while (EndPos<=length(Result)) and (Result[EndPos]<>';') do
|
|
inc(EndPos);
|
|
if StartPos<EndPos then begin
|
|
// trim path and chomp PathDelim
|
|
if (Result[EndPos-1] in AllowDirectorySeparators)
|
|
or (not FilenameIsTrimmed(@Result[StartPos],EndPos-StartPos)) then begin
|
|
NewPath:=ChompPathDelim(
|
|
TrimFilename(copy(Result,StartPos,EndPos-StartPos)));
|
|
Result:=copy(Result,1,StartPos-1)+NewPath+copy(Result,EndPos,length(Result));
|
|
EndPos:=StartPos+length(NewPath);
|
|
end;
|
|
// check if path already exists
|
|
if (Length(Result) > 0) and
|
|
(FindPathInSearchPath(@Result[StartPos],EndPos-StartPos, @Result[1],StartPos-1) <> nil)
|
|
then begin
|
|
// remove path
|
|
System.Delete(Result,StartPos,EndPos-StartPos+1);
|
|
end else begin
|
|
StartPos:=EndPos+1;
|
|
end;
|
|
end else begin
|
|
// remove empty path
|
|
System.Delete(Result,StartPos,1);
|
|
end;
|
|
end;
|
|
if (Result<>'') and (Result[length(Result)]=';') then
|
|
SetLength(Result,length(Result)-1);
|
|
end;
|
|
|
|
function FindPathInSearchPath(APath: PChar; APathLen: integer;
|
|
SearchPath: PChar; SearchPathLen: integer): PChar;
|
|
var
|
|
StartPos: Integer;
|
|
EndPos: LongInt;
|
|
NextStartPos: LongInt;
|
|
CmpPos: LongInt;
|
|
UseQuickCompare: Boolean;
|
|
PathStr: String;
|
|
CurFilename: String;
|
|
begin
|
|
Result:=nil;
|
|
if SearchPath=nil then exit;
|
|
if (APath=nil) or (APathLen=0) then exit;
|
|
// ignore trailing PathDelim at end
|
|
while (APathLen>1) and (APath[APathLen-1] in AllowDirectorySeparators) do dec(APathLen);
|
|
|
|
{$IFDEF CaseInsensitiveFilenames}
|
|
UseQuickCompare:=false;
|
|
{$ELSE}
|
|
{$IFDEF NotLiteralFilenames}
|
|
CmpPos:=0;
|
|
while (CmpPos<APathLen) and (ord(APath[CmpPos]<128)) do inc(CmpPos);
|
|
UseQuickCompare:=CmpPos=APathLen;
|
|
{$ELSE}
|
|
UseQuickCompare:=true;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
if not UseQuickCompare then begin
|
|
SetLength(PathStr{%H-},APathLen);
|
|
System.Move(APath^,PathStr[1],APathLen);
|
|
end;
|
|
|
|
StartPos:=0;
|
|
while StartPos<SearchPathLen do begin
|
|
// find current path bounds
|
|
NextStartPos:=StartPos;
|
|
while (SearchPath[NextStartPos]<>';') and (NextStartPos<SearchPathLen) do
|
|
inc(NextStartPos);
|
|
EndPos:=NextStartPos;
|
|
// ignore trailing PathDelim at end
|
|
while (EndPos>StartPos+1) and (SearchPath[EndPos-1] in AllowDirectorySeparators) do
|
|
dec(EndPos);
|
|
// compare current path
|
|
if UseQuickCompare then begin
|
|
if EndPos-StartPos=APathLen then begin
|
|
CmpPos:=0;
|
|
while CmpPos<APathLen do begin
|
|
if APath[CmpPos]<>SearchPath[StartPos+CmpPos] then
|
|
break;
|
|
inc(CmpPos);
|
|
end;
|
|
if CmpPos=APathLen then begin
|
|
Result:=@SearchPath[StartPos];
|
|
exit;
|
|
end;
|
|
end;
|
|
end else if EndPos>StartPos then begin
|
|
// use CompareFilenames
|
|
CurFilename:='';
|
|
SetLength(CurFilename,EndPos-StartPos);
|
|
System.Move(SearchPath[StartPos],CurFilename[1],EndPos-StartPos);
|
|
if CompareFilenames(PathStr,CurFilename)=0 then begin
|
|
Result:=@SearchPath[StartPos];
|
|
exit;
|
|
end;
|
|
end;
|
|
StartPos:=NextStartPos+1;
|
|
end;
|
|
end;
|
|
|
|
function FindPathInSearchPath(const APath, SearchPath: string): integer;
|
|
var
|
|
p: PChar;
|
|
SearchP: PChar;
|
|
begin
|
|
SearchP:=PChar(SearchPath);
|
|
p:=FindPathInSearchPath(PChar(APath),length(APath),SearchP,length(SearchPath));
|
|
if p=nil then
|
|
Result:=-1
|
|
else
|
|
Result:=p-SearchP+1;
|
|
end;
|
|
|
|
function FileSearchUTF8(const Name, DirList: String; ImplicitCurrentDir : Boolean = True): String;
|
|
Var
|
|
I : longint;
|
|
Temp : String;
|
|
|
|
begin
|
|
Result:=Name;
|
|
temp:=SetDirSeparators(DirList);
|
|
// Start with checking the file in the current directory
|
|
If ImplicitCurrentDir and (Result <> '') and FileExistsUTF8(Result) Then
|
|
exit;
|
|
while True do begin
|
|
If Temp = '' then
|
|
Break; // No more directories to search - fail
|
|
I:=pos(PathSeparator,Temp);
|
|
If I<>0 then
|
|
begin
|
|
Result:=Copy (Temp,1,i-1);
|
|
system.Delete(Temp,1,I);
|
|
end
|
|
else
|
|
begin
|
|
Result:=Temp;
|
|
Temp:='';
|
|
end;
|
|
If Result<>'' then
|
|
Result:=AppendPathDelim(Result)+Name;
|
|
If (Result <> '') and FileExistsUTF8(Result) Then
|
|
exit;
|
|
end;
|
|
Result:='';
|
|
end;
|
|
|
|
function FileIsReadOnlyUTF8(const FileName: String): Boolean;
|
|
begin
|
|
Result:=FileGetAttrUTF8(FileName) and faReadOnly > 0;
|
|
end;
|
|
|
|
|
|
|
|
function GetTempFileNameUTF8(const Dir, Prefix: String): String;
|
|
var
|
|
I: Integer;
|
|
Start: String;
|
|
begin
|
|
if Assigned(OnGetTempFile) then
|
|
Result:=OnGetTempFile(Dir,Prefix)
|
|
else
|
|
begin
|
|
if (Dir='') then
|
|
Start:=GetTempDir
|
|
else
|
|
Start:=IncludeTrailingPathDelimiter(Dir);
|
|
if (Prefix='') then
|
|
Start:=Start+'TMP'
|
|
else
|
|
Start:=Start+Prefix;
|
|
I:=0;
|
|
repeat
|
|
Result:=SysUtils.Format('%s%.5d.tmp',[Start,I]);
|
|
Inc(I);
|
|
until not FileExistsUTF8(Result);
|
|
end;
|
|
end;
|
|
|
|
function ForceDirectoriesUTF8(const Dir: string): Boolean;
|
|
var
|
|
E: EInOutError;
|
|
ADrv : String;
|
|
|
|
function DoForceDirectories(Const Dir: string): Boolean;
|
|
var
|
|
ADir : String;
|
|
APath: String;
|
|
begin
|
|
Result:=True;
|
|
ADir:=ExcludeTrailingPathDelimiter(Dir);
|
|
if (ADir='') then Exit;
|
|
if Not DirectoryExistsUTF8(ADir) then
|
|
begin
|
|
APath := ExtractFilePath(ADir);
|
|
//this can happen on Windows if user specifies Dir like \user\name/test/
|
|
//and would, if not checked for, cause an infinite recusrsion and a stack overflow
|
|
if (APath = ADir) then
|
|
Result := False
|
|
else
|
|
Result:=DoForceDirectories(APath);
|
|
if Result then
|
|
Result := CreateDirUTF8(ADir);
|
|
end;
|
|
end;
|
|
|
|
function IsUncDrive(const Drv: String): Boolean;
|
|
begin
|
|
Result := (Length(Drv) > 2) and (Drv[1] in AllowDirectorySeparators) and (Drv[2] in AllowDirectorySeparators);
|
|
end;
|
|
|
|
begin
|
|
Result := False;
|
|
ADrv := ExtractFileDrive(Dir);
|
|
if (ADrv<>'') and (not DirectoryExistsUTF8(ADrv))
|
|
{$IFNDEF FORCEDIR_NO_UNC_SUPPORT} and (not IsUncDrive(ADrv)){$ENDIF} then Exit;
|
|
if Dir='' then
|
|
begin
|
|
E:=EInOutError.Create(SCannotCreateEmptyDir);
|
|
E.ErrorCode:=3;
|
|
Raise E;
|
|
end;
|
|
Result := DoForceDirectories(GetForcedPathDelims(Dir));
|
|
end;
|
|
|
|
function TryReadAllLinks(const Filename: string): string;
|
|
begin
|
|
Result:=ReadAllLinks(Filename,false);
|
|
if Result='' then
|
|
Result:=Filename;
|
|
end;
|
|
|
|
procedure InvalidateFileStateCache(const Filename: string);
|
|
begin
|
|
if Assigned(OnInvalidateFileStateCache) then
|
|
OnInvalidateFileStateCache(Filename);
|
|
end;
|
|
|
|
procedure SplitCmdLineParams(const Params: string; ParamList: TStrings;
|
|
ReadBackslash: boolean = false);
|
|
// split spaces, quotes are parsed as single parameter
|
|
// if ReadBackslash=true then \" is replaced to " and not treated as quote
|
|
// #0 is always end
|
|
type
|
|
TMode = (mNormal,mApostrophe,mQuote);
|
|
var
|
|
p: Integer;
|
|
Mode: TMode;
|
|
Param: String;
|
|
begin
|
|
p:=1;
|
|
while p<=length(Params) do
|
|
begin
|
|
// skip whitespace
|
|
while (p<=length(Params)) and (Params[p] in [' ',#9,#10,#13]) do inc(p);
|
|
if (p>length(Params)) or (Params[p]=#0) then
|
|
break;
|
|
//writeln('SplitCmdLineParams After Space p=',p,'=[',Params[p],']');
|
|
// read param
|
|
Param:='';
|
|
Mode:=mNormal;
|
|
while p<=length(Params) do
|
|
begin
|
|
case Params[p] of
|
|
#0:
|
|
break;
|
|
'\':
|
|
begin
|
|
inc(p);
|
|
if ReadBackslash then
|
|
begin
|
|
// treat next character as normal character
|
|
if (p>length(Params)) or (Params[p]=#0) then
|
|
break;
|
|
if ord(Params[p])<128 then
|
|
begin
|
|
Param+=Params[p];
|
|
inc(p);
|
|
end else begin
|
|
// next character is already a normal character
|
|
end;
|
|
end else begin
|
|
// treat backslash as normal character
|
|
Param+='\';
|
|
end;
|
|
end;
|
|
'''':
|
|
begin
|
|
inc(p);
|
|
case Mode of
|
|
mNormal:
|
|
Mode:=mApostrophe;
|
|
mApostrophe:
|
|
Mode:=mNormal;
|
|
mQuote:
|
|
Param+='''';
|
|
end;
|
|
end;
|
|
'"':
|
|
begin
|
|
inc(p);
|
|
case Mode of
|
|
mNormal:
|
|
Mode:=mQuote;
|
|
mApostrophe:
|
|
Param+='"';
|
|
mQuote:
|
|
Mode:=mNormal;
|
|
end;
|
|
end;
|
|
' ',#9,#10,#13:
|
|
begin
|
|
if Mode=mNormal then break;
|
|
Param+=Params[p];
|
|
inc(p);
|
|
end;
|
|
else
|
|
Param+=Params[p];
|
|
inc(p);
|
|
end;
|
|
end;
|
|
//writeln('SplitCmdLineParams Param=#'+Param+'#');
|
|
ParamList.Add(Param);
|
|
end;
|
|
end;
|
|
|
|
function StrToCmdLineParam(const Param: string): string;
|
|
{ <empty> -> ''
|
|
word -> word
|
|
word1 word2 -> 'word1 word2'
|
|
word's -> "word's"
|
|
a" -> 'a"'
|
|
"a" -> '"a"'
|
|
'a' -> "'a'"
|
|
#0 character -> cut the rest
|
|
}
|
|
const
|
|
NoQuot = ' ';
|
|
AnyQuot = '*';
|
|
SysQuot = {$IFDEF Windows}'"'{$ELSE}''''{$ENDIF};
|
|
var
|
|
Quot: Char;
|
|
p: PChar;
|
|
i: Integer;
|
|
begin
|
|
Result:=Param;
|
|
if Result='' then
|
|
Result:=''''''
|
|
else begin
|
|
p:=PChar(Result);
|
|
Quot:=NoQuot;
|
|
repeat
|
|
case p^ of
|
|
#0:
|
|
begin
|
|
i:=p-PChar(Result);
|
|
if i<length(Result) then
|
|
Delete(Result,i+1,length(Result));
|
|
case Quot of
|
|
AnyQuot: Result:=SysQuot+Result+SysQuot;
|
|
'''': Result+='''';
|
|
'"': Result+='"';
|
|
end;
|
|
break;
|
|
end;
|
|
' ',#9,#10,#13:
|
|
begin
|
|
if Quot=NoQuot then
|
|
Quot:=AnyQuot;
|
|
inc(p);
|
|
end;
|
|
'''':
|
|
begin
|
|
case Quot of
|
|
NoQuot,AnyQuot:
|
|
begin
|
|
// need "
|
|
Quot:='"';
|
|
i:=p-PChar(Result);
|
|
System.Insert('"',Result,1);
|
|
p:=PChar(Result)+i+1;
|
|
end;
|
|
'"':
|
|
inc(p);
|
|
'''':
|
|
begin
|
|
// ' within a '
|
|
// => end ', start "
|
|
i:=p-PChar(Result)+1;
|
|
System.Insert('''"',Result,i);
|
|
p:=PChar(Result)+i+1;
|
|
Quot:='"';
|
|
end;
|
|
end;
|
|
end;
|
|
'"':
|
|
begin
|
|
case Quot of
|
|
NoQuot,AnyQuot:
|
|
begin
|
|
// need '
|
|
Quot:='''';
|
|
i:=p-PChar(Result);
|
|
System.Insert('''',Result,1);
|
|
p:=PChar(Result)+i+1;
|
|
end;
|
|
'''':
|
|
inc(p);
|
|
'"':
|
|
begin
|
|
// " within a "
|
|
// => end ", start '
|
|
i:=p-PChar(Result)+1;
|
|
System.Insert('"''',Result,i);
|
|
p:=PChar(Result)+i+1;
|
|
Quot:='''';
|
|
end;
|
|
end;
|
|
end;
|
|
else
|
|
inc(p);
|
|
end;
|
|
until false;
|
|
end;
|
|
end;
|
|
|
|
function MergeCmdLineParams(ParamList: TStrings): string;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:='';
|
|
if ParamList=nil then exit;
|
|
for i:=0 to ParamList.Count-1 do
|
|
begin
|
|
if i>0 then Result+=' ';
|
|
Result+=StrToCmdLineParam(ParamList[i]);
|
|
end;
|
|
end;
|
|
|
|
procedure SplitCmdLine(const CmdLine: string;
|
|
out ProgramFilename, Params: string);
|
|
var
|
|
p: integer;
|
|
|
|
procedure SkipChar; inline;
|
|
begin
|
|
{$IFDEF Unix}
|
|
if (CmdLine[p]='\') and (p<length(CmdLine)) then
|
|
// skip escaped char
|
|
inc(p,2)
|
|
else
|
|
{$ENDIF}
|
|
inc(p);
|
|
end;
|
|
|
|
var s, l: integer;
|
|
quote: char;
|
|
begin
|
|
ProgramFilename:='';
|
|
Params:='';
|
|
if CmdLine='' then exit;
|
|
p:=1;
|
|
s:=1;
|
|
if (CmdLine[p] in ['"','''']) then
|
|
begin
|
|
// skip quoted string
|
|
quote:=CmdLine[p];
|
|
inc(s);
|
|
inc(p);
|
|
while (p<=length(CmdLine)) and (CmdLine[p]<>quote) do
|
|
SkipChar;
|
|
// go past last character or quoted string
|
|
l:=p-s;
|
|
inc(p);
|
|
end else begin
|
|
while (p<=length(CmdLine)) and (CmdLine[p]>' ') do
|
|
SkipChar;
|
|
l:=p-s;
|
|
end;
|
|
ProgramFilename:=Copy(CmdLine,s,l);
|
|
while (p<=length(CmdLine)) and (CmdLine[p]<=' ') do inc(p);
|
|
Params:=copy(CmdLine,p,length(CmdLine));
|
|
end;
|
|
|
|
function PrepareCmdLineOption(const Option: string): string;
|
|
// If there is a space in the option add " " around the whole option
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result:=Option;
|
|
if (Result='') or (Result[1] in ['"','''']) then exit;
|
|
for i:=1 to length(Result) do
|
|
case Result[i] of
|
|
' ','''': exit(AnsiQuotedStr(Result,'"'));
|
|
'"': exit(AnsiQuotedStr(Result,''''));
|
|
end;
|
|
end;
|
|
{
|
|
function AddCmdLineParameter(const CmdLine, AddParameter: string): string;
|
|
begin
|
|
Result:=CmdLine;
|
|
if (Result<>'') and (Result[length(Result)]<>' ') then
|
|
Result:=Result+' ';
|
|
Result:=Result+AddParameter;
|
|
end;
|
|
}
|
|
{
|
|
Returns
|
|
- DriveLetter + : + PathDelim on Windows (if present) or
|
|
- UNC Share on Windows if present or
|
|
- PathDelim if FileName starts with PathDelim on Unix or Wince or
|
|
- Empty string of non eof the above applies
|
|
}
|
|
function ExtractFileRoot(FileName: String): String;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Result := '';
|
|
Len := Length(FileName);
|
|
if (Len > 0) then
|
|
begin
|
|
if IsUncPath(FileName) then
|
|
begin
|
|
Result := ExtractUNCVolume(FileName);
|
|
// is it like \\?\C:\Directory? then also include the "C:\" part
|
|
if (Result = '\\?\') and (Length(FileName) > 6) and
|
|
(FileName[5] in ['a'..'z','A'..'Z']) and (FileName[6] = ':') and (FileName[7] in AllowDirectorySeparators)
|
|
then
|
|
Result := Copy(FileName, 1, 7);
|
|
end
|
|
else
|
|
begin
|
|
{$if defined(unix) or defined(wince)}
|
|
if (FileName[1] = PathDelim) then Result := PathDelim;
|
|
{$else}
|
|
{$ifdef HASAMIGA}
|
|
if Pos(':', FileName) > 1 then
|
|
Result := Copy(FileName, 1, Pos(':', FileName));
|
|
{$else}
|
|
if (Len > 2) and (FileName[1] in ['a'..'z','A'..'Z']) and (FileName[2] = ':') and (FileName[3] in AllowDirectorySeparators) then
|
|
Result := UpperCase(Copy(FileName,1,3));
|
|
{$endif}
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
InitLazFileUtils;
|
|
|
|
finalization
|
|
FinalizeLazFileUtils;
|
|
|
|
end.
|
|
|
|
end.
|
|
|