lazarus/components/lazutils/lazfileutils.pas
bart 0c8df133dd LazUtils:
- An attempt to unify the defines for the different scenario's in the use of (not) codepage aware ansistrings
  and the use of the "Utf8 in RTL" feature.
  It makes for better separation of code and thus better readability and ease of maintainance (and in a later
  stadium it makes it easier to remove code that deals with non codepage aware ansistrings (fpc < 3.0)).
- Also replace (FPC_FULLVERSION >= xxxx) with FPC_HAS_CPSTRING where appropriate.
- Replace the custom HasCP define with built in FPC_HAS_CPSTRING define.

git-svn-id: trunk@50498 -
2015-11-24 16:23:18 +00:00

1332 lines
38 KiB
ObjectPascal

{
All functions are thread safe unless explicitely stated
}
unit LazFileUtils;
{$mode objfpc}{$H+}
{$i lazutils_defines.inc}
interface
uses
Classes, SysUtils, SysConst, LazUTF8, LazUtilsStrConsts;
{$IFDEF Windows}
{$define CaseInsensitiveFilenames}
{$define HasUNCPaths}
{$ENDIF}
{$IFDEF darwin}
{$define CaseInsensitiveFilenames}
{$ENDIF}
{$IF defined(CaseInsensitiveFilenames) or defined(darwin)}
{$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 CompareFileExt(const Filename, Ext: string;
CaseSensitive: boolean): integer; overload;
function CompareFileExt(const Filename, Ext: string): integer; overload;
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 // false = use default
): integer;
function DirPathExists(DirectoryName: string): boolean;
function DirectoryIsWritable(const DirectoryName: string): boolean;
function ExtractFileNameOnly(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;
Procedure ForcePathDelims(Var FileName: string);
Function GetForcedPathDelims(Const FileName: 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 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 AppendPathDelim(const Path: string): string;
function ChompPathDelim(const Path: string): string;
// 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;
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;
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}
procedure SplitCmdLineParams(const Params: string; ParamList: TStrings;
ReadBackslash: boolean = false);
function StrToCmdLineParam(const Param: string): string;
function MergeCmdLineParams(ParamList: TStrings): 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 darwin}
MacOSAll,
{$ENDIF}
Unix, BaseUnix;
{$ENDIF}
{$I lazfileutils.inc}
{$IFDEF windows}
{$I winlazfileutils.inc}
{$ELSE}
{$I unixlazfileutils.inc}
{$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}
Result:=UTF8CompareText(Filename1, Filename2);
{$ENDIF}
end;
function CompareFileExt(const Filename, Ext: string; CaseSensitive: boolean): integer;
// Ext can contain a point or not
var
n, e : AnsiString;
FileLen, FilePos, ExtLen, ExtPos: integer;
begin
FileLen := length(Filename);
ExtLen := length(Ext);
FilePos := FileLen;
while (FilePos>=1) and (Filename[FilePos]<>'.') do dec(FilePos);
if FilePos < 1 then begin
// no extension in filename
Result:=1;
exit;
end;
// skip point
inc(FilePos);
ExtPos := 1;
if (ExtPos <= ExtLen) and (Ext[1] = '.') then inc(ExtPos);
// compare extensions
n := Copy(Filename, FilePos, length(FileName));
e := Copy(Ext, ExtPos, length(Ext));
if CaseSensitive then
Result := CompareStr(n, e)
else
Result := UTF8CompareText(n, e);
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, False);
end;
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)
{$IFDEF Windows}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;
{$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 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,Len1);
System.Move(Filename1^,File1[1],Len1);
SetLength(File2,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 = false): integer;
var
{$IFDEF darwin}
F1: CFStringRef;
F2: CFStringRef;
Flags: CFStringCompareFlags;
{$ELSE}
File1, File2: string;
Len1: SizeInt;
Len2: SizeInt;
{$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 begin
// compare case insensitive
Len1:=StrLen(Filename1);
SetLength(File1,Len1);
System.Move(Filename1^,File1[1],Len1);
Len2:=StrLen(Filename2);
SetLength(File2,Len2);
System.Move(Filename2^,File2[1],Len2);
Result:=UTF8CompareText(File1,File2);
end 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 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
Dir:=copy(DirectoryName,1,i-1);
if 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;
NewLine: boolean;
p: PChar;
ZeroAllowed: Boolean;
fHandle: THandle;
begin
Result:=false;
FileReadable:=true;
fHandle := FileOpenUtf8(AFileName, fmOpenRead or fmShareDenyNone);
if (THandle(fHandle) <> feInvalidHandle) then
begin
try
Len:=1024;
SetLength(Buf,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;
NewLine:=false;
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;
#10,#13: NewLine:=true;
end;
inc(p);
end;
if NewLine or (Len<1024) then
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;
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 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;
------------------------------------------------------------------------------}
function FileIsInPath(const Filename, Path: string): boolean;
var
ExpFile: String;
ExpPath: String;
l: integer;
begin
if Path='' then begin
Result:=false;
exit;
end;
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 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
if Path = '' then
exit;
Result:=Path;
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 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,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:=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 = '*';
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:=''''+Result+'''';
'''': 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;
{
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}
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}
end;
end;
end;
initialization
InitLazFileUtils;
finalization
FinalizeLazFileUtils;
end.
end.