{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: - simple file functions and fpc additions } unit FileProcs; {$ifdef FPC}{$mode objfpc}{$endif}{$H+} interface {$I codetools.inc} uses {$IFDEF MEM_CHECK} MemCheck, {$ENDIF} Classes, SysUtils, AVL_Tree, CodeToolsStrConsts; type TFPCStreamSeekType = int64; TFPCMemStreamSeekType = integer; PCharZ = Pointer; {$ifdef Windows} {$define CaseInsensitiveFilenames} {$endif} {$IF defined(CaseInsensitiveFilenames) or defined(darwin)} {$DEFINE NotLiteralFilenames} {$ENDIF} const FilenamesCaseSensitive = {$IFDEF CaseInsensitiveFilenames}false{$ELSE}true{$ENDIF};// lower and upper letters are treated the same FilenamesLiteral = {$IFDEF NotLiteralFilenames}false{$ELSE}true{$ENDIF};// file names can be compared using = string operator SpecialChar = '#'; // used to use PathDelim, e.g. #\ {$IFDEF MSWindows} FileMask = '*.*'; ExeExt = '.exe'; {$ELSE} FileMask = '*'; ExeExt = ''; {$ENDIF} type TCTSearchFileCase = ( ctsfcDefault, // e.g. case insensitive on windows ctsfcLoUpCase, // also search for lower and upper case ctsfcAllCase // search case insensitive ); function CompareFilenames(const Filename1, Filename2: string): integer; function CompareFilenamesIgnoreCase(const Filename1, Filename2: string): integer; function CompareFileExt(const Filename, Ext: string; CaseSensitive: boolean): 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); function FileIsExecutable(const AFilename: string): boolean; function FileIsReadable(const AFilename: string): boolean; function FileIsWritable(const AFilename: string): boolean; function FileIsText(const AFilename: string): boolean; function FilenameIsTrimmed(const TheFilename: string): boolean; function FilenameIsTrimmed(StartPos: PChar; NameLen: integer): boolean; function TrimFilename(const AFilename: string): string; function CleanAndExpandFilename(const Filename: string): string; function CleanAndExpandDirectory(const Filename: string): string; function CreateRelativePath(const Filename, BaseDirectory: string; UsePointDirectory: boolean = false): string; function FileIsInPath(const Filename, Path: string): boolean; function AppendPathDelim(const Path: string): string; function ChompPathDelim(const Path: string): string; function ClearFile(const Filename: string; RaiseOnError: boolean): boolean; function GetTempFilename(const Path, Prefix: string): string; function SearchFileInDir(const Filename, BaseDirectory: string; SearchCase: TCTSearchFileCase): string; function SearchFileInPath(const Filename, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; function FilenameIsMatching(const Mask, Filename: string; MatchExactly: boolean): boolean; function GetFilenameOnDisk(const AFilename: string): string; function FindDiskFilename(const Filename: string): string; {$IFDEF darwin} function GetDarwinSystemFilename(Filename: string): string; {$ENDIF} function CompareAnsiStringFilenames(Data1, data2: Pointer): integer; function CompareFilenameOnly(Filename: PChar; FilenameLen: integer; NameOnly: PChar; NameOnlyLen: integer; CaseSensitive: boolean): integer; // searching .pas, .pp, .p function FilenameIsPascalUnit(const Filename: string; CaseSensitive: boolean = false): boolean; function FilenameIsPascalUnit(Filename: PChar; FilenameLen: integer; CaseSensitive: boolean = false): boolean; function SearchPascalUnitInDir(const AnUnitName, BaseDirectory: string; SearchCase: TCTSearchFileCase): string; function SearchPascalUnitInPath(const AnUnitName, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; // searching .ppu function SearchPascalFileInDir(const ShortFilename, BaseDirectory: string; SearchCase: TCTSearchFileCase): string; function SearchPascalFileInPath(const ShortFilename, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; 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; // FPC function ReadNextFPCParameter(const CmdLine: string; var Position: integer; out StartPos: integer): boolean; type TCTPascalExtType = (petNone, petPAS, petPP, petP); const CTPascalExtension: array[TCTPascalExtType] of string = ('', '.pas', '.pp', '.p'); type TFileStateCacheItemFlag = ( fsciExists, // file or directory exists fsciDirectory, // file exists and is directory fsciReadable, // file is readable fsciWritable, // file is writable fsciDirectoryReadable, // file is directory and can be searched fsciDirectoryWritable, // file is directory and new files can be created fsciText, // file is text file (not binary) fsciExecutable // file is executable ); TFileStateCacheItemFlags = set of TFileStateCacheItemFlag; { TFileStateCacheItem } TFileStateCacheItem = class private FFilename: string; FFlags: TFileStateCacheItemFlags; FTestedFlags: TFileStateCacheItemFlags; FTimeStamp: integer; public constructor Create(const TheFilename: string; NewTimeStamp: integer); public property Filename: string read FFilename; property Flags: TFileStateCacheItemFlags read FFlags; property TestedFlags: TFileStateCacheItemFlags read FTestedFlags; property TimeStamp: integer read FTimeStamp; end; { TFileStateCache } TFileStateCache = class private FFiles: TAVLTree; FTimeStamp: integer; FLockCount: integer; FChangeTimeStampHandler: array of TNotifyEvent; procedure SetFlag(AFile: TFileStateCacheItem; AFlag: TFileStateCacheItemFlag; NewValue: boolean); public constructor Create; destructor Destroy; override; procedure Lock; procedure Unlock; function Locked: boolean; procedure IncreaseTimeStamp; function FileExistsCached(const Filename: string): boolean; function DirPathExistsCached(const Filename: string): boolean; function DirectoryIsWritableCached(const DirectoryName: string): boolean; function FileIsExecutableCached(const AFilename: string): boolean; function FileIsReadableCached(const AFilename: string): boolean; function FileIsWritableCached(const AFilename: string): boolean; function FileIsTextCached(const AFilename: string): boolean; function FindFile(const Filename: string; CreateIfNotExists: boolean): TFileStateCacheItem; function Check(const Filename: string; AFlag: TFileStateCacheItemFlag; out AFile: TFileStateCacheItem; var FlagIsSet: boolean): boolean; procedure WriteDebugReport; procedure AddChangeTimeStampHandler(const Handler: TNotifyEvent); procedure RemoveChangeTimeStampHandler(const Handler: TNotifyEvent); public property TimeStamp: integer read FTimeStamp; end; var FileStateCache: TFileStateCache; function FileExistsCached(const Filename: string): boolean; function DirPathExistsCached(const Filename: string): boolean; function DirectoryIsWritableCached(const DirectoryName: string): boolean; function FileIsExecutableCached(const AFilename: string): boolean; function FileIsReadableCached(const AFilename: string): boolean; function FileIsWritableCached(const AFilename: string): boolean; function FileIsTextCached(const AFilename: string): boolean; procedure InvalidateFileStateCache; function CompareFileStateItems(Data1, Data2: Pointer): integer; function CompareFilenameWithFileStateCacheItem(Key, Data: Pointer): integer; const FileStateCacheItemFlagNames: array[TFileStateCacheItemFlag] of string = ( 'fsciExists', 'fsciDirectory', 'fsciReadable', 'fsciWritable', 'fsciDirectoryReadable', 'fsciDirectoryWritable', 'fsciText', 'fsciExecutable' ); // basic utility -> should go to RTL function ComparePointers(p1, p2: Pointer): integer; procedure MergeSort(List: PPointer; ListLength: PtrInt; Compare: TListSortCompare); function GetNextDelimitedItem(const List: string; Delimiter: char; var Position: integer): string; function HasDelimitedItem(const List: string; Delimiter: char; FindItem: string ): boolean; function FindNextDelimitedItem(const List: string; Delimiter: char; var Position: integer; FindItem: string): string; function AVLTreeHasDoubles(Tree: TAVLTree): TAVLTreeNode; // debugging procedure DebugLn(Args: array of const); procedure DebugLn(const S: String; Args: array of const);// similar to Format(s,Args) procedure DebugLn; procedure DebugLn(const s: string); procedure DebugLn(const s1,s2: string); procedure DebugLn(const s1,s2,s3: string); procedure DebugLn(const s1,s2,s3,s4: string); procedure DebugLn(const s1,s2,s3,s4,s5: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7,s8: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7,s8,s9: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7,s8,s9,s10: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11: string); procedure DebugLn(const s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12: string); procedure DbgOut(const s: string); procedure DbgOut(const s1,s2: string); procedure DbgOut(const s1,s2,s3: string); procedure DbgOut(const s1,s2,s3,s4: string); procedure DbgOut(const s1,s2,s3,s4,s5: string); procedure DbgOut(const s1,s2,s3,s4,s5,s6: string); function DbgS(const c: char): string; overload; function DbgS(const c: cardinal): string; overload; function DbgS(const i: integer): string; overload; function DbgS(const i: QWord): string; overload; function DbgS(const i: int64): string; overload; function DbgS(const r: TRect): string; overload; function DbgS(const p: TPoint): string; overload; function DbgS(const p: pointer): string; overload; function DbgS(const e: extended; MaxDecimals: integer = 999): string; overload; function DbgS(const b: boolean): string; overload; function DbgSName(const p: TObject): string; overload; function DbgSName(const p: TClass): string; overload; function dbgMemRange(P: PByte; Count: integer; Width: integer = 0): string; overload; function DbgS(const i1,i2,i3,i4: integer): string; overload; function DbgStr(const StringWithSpecialChars: string): string; function GetTicks: int64; type TCTStackTracePointers = array of Pointer; TCTLineInfoCacheItem = record Addr: Pointer; Info: string; end; PCTLineInfoCacheItem = ^TCTLineInfoCacheItem; procedure CTDumpStack; function CTGetStackTrace(UseCache: boolean): string; procedure CTGetStackTracePointers(var AStack: TCTStackTracePointers); function CTStackTraceAsString(const AStack: TCTStackTracePointers; UseCache: boolean): string; function CTGetLineInfo(Addr: Pointer; UseCache: boolean): string; function CompareCTLineInfoCacheItems(Data1, Data2: Pointer): integer; function CompareAddrWithCTLineInfoCacheItem(Addr, Item: Pointer): integer; var FPUpChars: array[char] of char; // AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX // but normally these OS use UTF-8 as system encoding so the widestringmanager // is not needed. function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8 procedure SetNeedRTLAnsi(NewValue: boolean); function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager // 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): string; function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint; function FindNextUTF8(var Rslt: TSearchRec): Longint; procedure FindCloseUTF8(var F: TSearchrec); 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): 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; // environment function ParamStrUTF8(Param: Integer): string; function GetEnvironmentStringUTF8(Index : Integer): String; function GetEnvironmentVariableUTF8(const EnvVar: String): String; implementation // to get more detailed error messages consider the os uses {$IFDEF MSWindows} Windows; {$ELSE} {$IFDEF darwin} MacOSAll, {$ENDIF} Unix, BaseUnix; {$ENDIF} var LineInfoCache: TAVLTree = nil; LastTick: int64 = 0; var FNeedRTLAnsi: boolean = false; FNeedRTLAnsiValid: boolean = false; function NeedRTLAnsi: boolean; {$IFDEF WinCE} // CP_UTF8 is missing in the windows unit of the Windows CE RTL const CP_UTF8 = 65001; {$ENDIF} {$IFNDEF Windows} var Lang: String; i: LongInt; Encoding: String; {$ENDIF} begin if FNeedRTLAnsiValid then exit(FNeedRTLAnsi); {$IFDEF Windows} FNeedRTLAnsi:=GetACP<>CP_UTF8; {$ELSE} FNeedRTLAnsi:=false; Lang := SysUtils.GetEnvironmentVariable('LC_ALL'); if Length(lang) = 0 then begin Lang := SysUtils.GetEnvironmentVariable('LC_MESSAGES'); if Length(Lang) = 0 then begin Lang := SysUtils.GetEnvironmentVariable('LANG'); end; end; i:=System.Pos('.',Lang); if (i>0) then begin Encoding:=copy(Lang,i+1,length(Lang)-i); FNeedRTLAnsi:=(SysUtils.CompareText(Encoding,'UTF-8')=0) or (SysUtils.CompareText(Encoding,'UTF8')=0); end; {$ENDIF} FNeedRTLAnsiValid:=true; Result:=FNeedRTLAnsi; end; procedure SetNeedRTLAnsi(NewValue: boolean); begin FNeedRTLAnsi:=NewValue; FNeedRTLAnsiValid:=true; end; function IsASCII(const s: string): boolean; inline; var i: Integer; begin for i:=1 to length(s) do if ord(s[i])>127 then exit(false); Result:=true; end; function UTF8ToSys(const s: string): string; begin if NeedRTLAnsi and (not IsASCII(s)) then Result:=UTF8ToAnsi(s) else Result:=s; end; function SysToUTF8(const s: string): string; begin if NeedRTLAnsi and (not IsASCII(s)) then Result:=AnsiToUTF8(s) else Result:=s; end; function FileExistsUTF8(const Filename: string): boolean; begin Result:=SysUtils.FileExists(UTF8ToSys(Filename)); end; function FileAgeUTF8(const FileName: String): Longint; begin Result:=SysUtils.FileAge(UTF8ToSys(Filename)); end; function DirectoryExistsUTF8(const Directory: string): Boolean; begin Result:=SysUtils.DirectoryExists(UTF8ToSys(Directory)); end; function ExpandFileNameUTF8(const FileName: string): string; begin Result:=SysToUTF8(SysUtils.ExpandFileName(UTF8ToSys(Filename))); end; function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec ): Longint; begin Result:=SysUtils.FindFirst(UTF8ToSys(Path),Attr,Rslt); Rslt.Name:=SysToUTF8(Rslt.Name); end; function FindNextUTF8(var Rslt: TSearchRec): Longint; begin Rslt.Name:=UTF8ToSys(Rslt.Name); Result:=SysUtils.FindNext(Rslt); Rslt.Name:=SysToUTF8(Rslt.Name); end; procedure FindCloseUTF8(var F: TSearchrec); begin SysUtils.FindClose(F); end; function FileSetDateUTF8(const FileName: String; Age: Longint): Longint; begin Result:=SysUtils.FileSetDate(UTF8ToSys(Filename),Age); end; function FileGetAttrUTF8(const FileName: String): Longint; begin Result:=SysUtils.FileGetAttr(UTF8ToSys(Filename)); end; function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint; begin Result:=SysUtils.FileSetAttr(UTF8ToSys(Filename),Attr); end; function DeleteFileUTF8(const FileName: String): Boolean; begin Result:=SysUtils.DeleteFile(UTF8ToSys(Filename)); end; function RenameFileUTF8(const OldName, NewName: String): Boolean; begin Result:=SysUtils.RenameFile(UTF8ToSys(OldName),UTF8ToSys(NewName)); end; function FileSearchUTF8(const Name, DirList: String): String; begin Result:=SysToUTF8(SysUtils.FileSearch(UTF8ToSys(Name),UTF8ToSys(DirList))); end; function FileIsReadOnlyUTF8(const FileName: String): Boolean; begin Result:=SysUtils.FileIsReadOnly(UTF8ToSys(Filename)); end; function GetCurrentDirUTF8: String; begin Result:=SysToUTF8(SysUtils.GetCurrentDir); end; function SetCurrentDirUTF8(const NewDir: String): Boolean; begin Result:=SysUtils.SetCurrentDir(UTF8ToSys(NewDir)); end; function CreateDirUTF8(const NewDir: String): Boolean; begin Result:=SysUtils.CreateDir(UTF8ToSys(NewDir)); end; function RemoveDirUTF8(const Dir: String): Boolean; begin Result:=SysUtils.RemoveDir(UTF8ToSys(Dir)); end; function ForceDirectoriesUTF8(const Dir: string): Boolean; begin Result:=SysUtils.ForceDirectories(UTF8ToSys(Dir)); end; function ParamStrUTF8(Param: Integer): string; begin Result:=SysToUTF8(ObjPas.ParamStr(Param)); end; function GetEnvironmentStringUTF8(Index: Integer): String; begin Result:=SysToUTF8(SysUtils.GetEnvironmentString(Index)); end; function GetEnvironmentVariableUTF8(const EnvVar: String): String; begin Result:=SysToUTF8(SysUtils.GetEnvironmentVariable(UTF8ToSys(EnvVar))); end; {------------------------------------------------------------------------------- function ClearFile(const Filename: string; RaiseOnError: boolean): boolean; -------------------------------------------------------------------------------} function ClearFile(const Filename: string; RaiseOnError: boolean): boolean; var fs: TFileStream; begin if FileExistsUTF8(Filename) then begin try InvalidateFileStateCache; fs:=TFileStream.Create(UTF8ToSys(Filename),fmOpenWrite); fs.Size:=0; fs.Free; except on E: Exception do begin Result:=false; if RaiseOnError then raise; exit; end; end; end; Result:=true; end; function DirectoryIsWritable(const DirectoryName: string): boolean; var TempFilename: String; fs: TFileStream; s: String; begin TempFilename:=GetTempFilename(AppendPathDelim(DirectoryName),'tstperm'); Result:=false; try fs:=TFileStream.Create(UTF8ToSys(TempFilename),fmCreate); s:='WriteTest'; fs.Write(s[1],length(s)); fs.Free; if not DeleteFileUTF8(TempFilename) then InvalidateFileStateCache; Result:=true; except end; end; function GetTempFilename(const Path, Prefix: string): string; var i: Integer; CurPath: String; CurName: String; begin Result:=ExpandFileNameUTF8(Path); CurPath:=AppendPathDelim(ExtractFilePath(Result)); CurName:=Prefix+ExtractFileNameOnly(Result); i:=1; repeat Result:=CurPath+CurName+IntToStr(i)+'.tmp'; if not FileExistsUTF8(Result) then exit; inc(i); until false; end; function FindDiskFilename(const Filename: string): string; // Searches for the filename case on disk. // if it does not exist, only the found path will be improved // For example: // If Filename='file' and there is only a 'File' then 'File' will be returned. var StartPos: Integer; EndPos: LongInt; FileInfo: TSearchRec; CurDir: String; CurFile: String; AliasFile: String; Ambiguous: Boolean; FileNotFound: Boolean; begin Result:=Filename; // check every directory and filename StartPos:=1; {$IFDEF MSWindows} // uppercase Drive letter and skip it if ((length(Result)>=2) and (Result[1] in ['A'..'Z','a'..'z']) and (Result[2]=':')) then begin StartPos:=3; if Result[1] in ['a'..'z'] then Result[1]:=FPUpChars[Result[1]]; end; {$ENDIF} FileNotFound:=false; repeat // skip PathDelim while (StartPos<=length(Result)) and (Result[StartPos]=PathDelim) do inc(StartPos); // find end of filename part EndPos:=StartPos; while (EndPos<=length(Result)) and (Result[EndPos]<>PathDelim) do inc(EndPos); if EndPos>StartPos then begin // search file CurDir:=copy(Result,1,StartPos-1); CurFile:=copy(Result,StartPos,EndPos-StartPos); AliasFile:=''; Ambiguous:=false; if FindFirstUTF8(CurDir+FileMask,faAnyFile,FileInfo)=0 then begin repeat // check if special file if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then continue; if CompareFilenamesIgnoreCase(FileInfo.Name,CurFile)=0 then begin //writeln('FindDiskFilename ',FileInfo.Name,' ',CurFile); if FileInfo.Name=CurFile then begin // file found, has already the correct name AliasFile:=''; break; end else begin // alias found, but has not the correct name if AliasFile='' then begin AliasFile:=FileInfo.Name; end else begin // there are more than one candidate Ambiguous:=true; break; end; end; end; until FindNextUTF8(FileInfo)<>0; end else FileNotFound:=true; FindCloseUTF8(FileInfo); if FileNotFound then break; if (AliasFile<>'') and (not Ambiguous) then begin // better filename found -> replace Result:=CurDir+AliasFile+copy(Result,EndPos,length(Result)); end; end; StartPos:=EndPos+1; until StartPos>length(Result); 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; {$ENDIF} function CompareAnsiStringFilenames(Data1, data2: Pointer): integer; var s1: String; s2: String; begin s1:=''; s2:=''; Pointer(s1):=Data1; Pointer(s2):=Data2; Result:=CompareFilenames(s1,s2); Pointer(s1):=nil; Pointer(s2):=nil; end; function CompareFilenameOnly(Filename: PChar; FilenameLen: integer; NameOnly: PChar; NameOnlyLen: integer; CaseSensitive: boolean): integer; // compare only the filename (without extension and path) var EndPos: integer; StartPos: LongInt; p: Integer; l: LongInt; FilenameOnlyLen: Integer; begin StartPos:=FilenameLen; while (StartPos>0) and (Filename[StartPos-1]<>PathDelim) do dec(StartPos); EndPos:=FilenameLen; while (EndPos>StartPos) and (Filename[EndPos]<>'.') do dec(EndPos); if (EndPos=StartPos) and (EndPos'.') then EndPos:=FilenameLen; FilenameOnlyLen:=EndPos-StartPos; l:=FilenameOnlyLen; if l>NameOnlyLen then l:=NameOnlyLen; //DebugLn('CompareFilenameOnly NameOnly="',copy(NameOnly,1,NameOnlyLen),'" FilenameOnly="',copy(Filename,StartPos,EndPos-StartPos),'"'); p:=0; if CaseSensitive then begin while p0 then exit; inc(p); end; end else begin while p0 then exit; inc(p); end; end; Result:=FilenameOnlyLen-NameOnlyLen; end; 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); CFRelease(F1); CFRelease(F2); {$ELSE} {$IFDEF CaseInsensitiveFilenames} Result:=AnsiCompareText(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:=AnsiCompareText(Filename1, Filename2); {$ENDIF} end; function FileIsExecutable(const AFilename: string): boolean; {$IFNDEF WINDOWS} var Info : Stat; {$ENDIF} begin {$IFDEF WINDOWS} Result:=FileExistsUTF8(AFilename); {$ELSE} // first check AFilename is not a directory and then check if executable Result:= (FpStat(AFilename,info)<>-1) and FPS_ISREG(info.st_mode) and (BaseUnix.FpAccess(AFilename,BaseUnix.X_OK)=0); {$ENDIF} end; procedure CheckIfFileIsExecutable(const AFilename: string); {$IFNDEF MSWindows} var AText: string; {$ENDIF} begin // TProcess does not report, if a program can not be executed // to get good error messages consider the OS if not FileExistsUTF8(AFilename) then begin raise Exception.CreateFmt(ctsFileDoesNotExists,[AFilename]); end; {$IFNDEF MSWindows} if not(BaseUnix.FpAccess(AFilename,BaseUnix.X_OK)=0) then begin AText:='"'+AFilename+'"'; case fpGetErrno of ESysEAcces: AText:='read access denied for '+AText; ESysENoEnt: AText:='a directory component in '+AText +' does not exist or is a dangling symlink'; ESysENotDir: AText:='a directory component in '+Atext+' is not a directory'; ESysENoMem: AText:='insufficient memory'; ESysELoop: AText:=AText+' has a circular symbolic link'; else AText:=Format(ctsFileIsNotExecutable,[AText]); end; raise Exception.Create(AText); end; {$ENDIF} // ToDo: windows and xxxbsd end; function ExtractFileNameOnly(const AFilename: string): string; var ExtLen: integer; begin // beware: filename.ext1.ext2 Result:=ExtractFilename(AFilename); ExtLen:=length(ExtractFileExt(Result)); Result:=copy(Result,1,length(Result)-ExtLen); end; function FilenameIsAbsolute(const TheFilename: string):boolean; begin {$IFDEF MSWindows} // windows Result:=FilenameIsWinAbsolute(TheFilename); {$ELSE} // unix Result:=FilenameIsUnixAbsolute(TheFilename); {$ENDIF} end; function FilenameIsWinAbsolute(const TheFilename: string): boolean; begin Result:=((length(TheFilename)>=2) and (TheFilename[1] in ['A'..'Z','a'..'z']) and (TheFilename[2]=':')) or ((length(TheFilename)>=2) and (TheFilename[1]='\') and (TheFilename[2]='\')); end; function FilenameIsUnixAbsolute(const TheFilename: string): boolean; begin Result:=(TheFilename<>'') and (TheFilename[1]='/'); end; function GetFilenameOnDisk(const AFilename: string): string; begin Result:=AFilename; {$IFDEF darwin} Result:=GetDarwinSystemFilename(Result); {$ELSE} {$IFDEF NotLiteralFilenames} Result:=FindDiskFilename(Result); {$ENDIF} {$ENDIF} end; function DirPathExists(DirectoryName: string): boolean; begin Result:=DirectoryExistsUTF8(ChompPathDelim(DirectoryName)); end; function ForceDirectory(DirectoryName: string): boolean; var i: integer; Dir: string; begin DoDirSeparators(DirectoryName); DirectoryName:=AppendPathDelim(DirectoryName); i:=1; while i<=length(DirectoryName) do begin if DirectoryName[i]=PathDelim 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 FileIsReadable(const AFilename: string): boolean; begin {$IFDEF MSWindows} Result:=true; {$ELSE} Result:= BaseUnix.FpAccess(AFilename,BaseUnix.R_OK)=0; {$ENDIF} end; function FileIsWritable(const AFilename: string): boolean; begin {$IFDEF MSWindows} Result:=((FileGetAttrUTF8(AFilename) and faReadOnly)=0); {$ELSE} Result:= BaseUnix.FpAccess(AFilename,BaseUnix.W_OK)=0; {$ENDIF} end; function FileIsText(const AFilename: string): boolean; var fs: TFileStream; Buf: string; Len, i: integer; NewLine: boolean; Size: Int64; begin Result:=false; try fs:=TFileStream.Create(UTF8ToSys(AFilename),fmOpenRead); try // read the first 1024 bytes Len:=1024; Size:=fs.Size; if Len>Size then Len:=integer(Size); if Len>0 then begin SetLength(Buf,Len); fs.Read(Buf[1],length(Buf)); NewLine:=false; for i:=1 to length(Buf) do begin case Buf[i] of // #10,#13: new line // #12: form feed // #26: end of file #0..#8,#11,#14..#25,#27..#31: exit; #10,#13: NewLine:=true; end; end; if NewLine or (Len<1024) then Result:=true; end else Result:=true; finally fs.Free; end; except end; 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; 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]=PathDelim) then exit; i:=0; while iPathDelim then inc(i) else begin inc(i); if i=NameLen then break; // check for double path delimiter if (StartPos[i]=PathDelim) then exit; if (StartPos[i]='.') and (i>0) then begin inc(i); // check /./ or /. at end if (StartPos[i]=PathDelim) or (i=NameLen) then exit; if StartPos[i]='.' then begin inc(i); // check /../ or /.. at end if (StartPos[i]=PathDelim) or (i=NameLen) then exit; end; end; end; end; Result:=true; end; function TrimFilename(const AFilename: string): string; // trim double path delims, heading and trailing spaces // and special dirs . and .. var SrcPos, DestPos, l, DirStart: integer; c: char; MacroPos: LongInt; begin Result:=AFilename; if FilenameIsTrimmed(Result) then exit; l:=length(AFilename); SrcPos:=1; DestPos:=1; // skip trailing spaces while (l>=1) and (AFilename[l]=' ') do dec(l); // skip heading spaces while (SrcPos<=l) and (AFilename[SrcPos]=' ') do inc(SrcPos); // trim double path delimiters and special dirs . and .. while (SrcPos<=l) do begin c:=AFilename[SrcPos]; // check for double path delims if (c=PathDelim) then begin inc(SrcPos); {$IFDEF MSWindows} if (DestPos>2) {$ELSE} if (DestPos>1) {$ENDIF} and (Result[DestPos-1]=PathDelim) then begin // skip second PathDelim continue; end; Result[DestPos]:=c; inc(DestPos); continue; end; // check for special dirs . and .. if (c='.') then begin if (SrcPos skip inc(SrcPos,2); continue; end else if (AFilename[SrcPos+1]='.') and (SrcPos+1=l) or (AFilename[SrcPos+2]=PathDelim) then begin // special dir .. // 1. .. -> copy // 2. /.. -> skip .., keep / // 3. C:.. -> copy // 4. C:\.. -> skip .., keep C:\ // 5. \\.. -> skip .., keep \\ // 6. xxx../.. -> copy // 7. xxxdir/.. -> trim dir and skip .. // 8. xxxdir/.. -> trim dir and skip .. if DestPos=1 then begin // 1. .. -> copy end else if (DestPos=2) and (Result[1]=PathDelim) then begin // 2. /.. -> skip .., keep / inc(SrcPos,2); continue; {$IFDEF MSWindows} end else if (DestPos=3) and (Result[2]=':') and (Result[1] in ['a'..'z','A'..'Z']) then begin // 3. C:.. -> copy end else if (DestPos=4) and (Result[2]=':') and (Result[3]=PathDelim) and (Result[1] in ['a'..'z','A'..'Z']) then begin // 4. C:\.. -> skip .., keep C:\ inc(SrcPos,2); continue; end else if (DestPos=3) and (Result[1]=PathDelim) and (Result[2]=PathDelim) then begin // 5. \\.. -> skip .., keep \\ inc(SrcPos,2); continue; {$ENDIF} end else if (DestPos>1) and (Result[DestPos-1]=PathDelim) then begin if (DestPos>3) and (Result[DestPos-2]='.') and (Result[DestPos-3]='.') and ((DestPos=4) or (Result[DestPos-4]=PathDelim)) then begin // 6. ../.. -> copy end else begin // 7. xxxdir/.. -> trim dir and skip .. DirStart:=DestPos-2; while (DirStart>1) and (Result[DirStart-1]<>PathDelim) do dec(DirStart); MacroPos:=DirStart; while MacroPos keep break; end; inc(MacroPos); end; if MacroPos=DestPos then begin DestPos:=DirStart; inc(SrcPos,2); continue; end; end; end; end; end else begin // special dir . at end of filename if DestPos=1 then begin Result:='.'; exit; end else begin // skip break; end; end; end; // copy directory repeat Result[DestPos]:=c; inc(DestPos); inc(SrcPos); if (SrcPos>l) then break; c:=AFilename[SrcPos]; if c=PathDelim then break; until false; end; // trim result if DestPos<=length(AFilename) then SetLength(Result,DestPos-1); 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 CreateRelativePath(const Filename, BaseDirectory: string; UsePointDirectory: boolean): string; var FileNameLength: Integer; BaseDirLen: Integer; MinLen: Integer; SamePos: Integer; UpDirCount: Integer; BaseDirPos: Integer; ResultPos: Integer; i: Integer; FileNameRestLen: Integer; CmpBaseDirectory: String; CmpFilename: String; p: Integer; DirCount: Integer; begin Result:=Filename; if (BaseDirectory='') or (Filename='') then exit; {$IFDEF MSWindows} // check for different windows file drives if (CompareText(ExtractFileDrive(Filename), ExtractFileDrive(BaseDirectory))<>0) then exit; {$ENDIF} CmpBaseDirectory:=BaseDirectory; CmpFilename:=Filename; {$IFDEF darwin} CmpBaseDirectory:=GetDarwinSystemFilename(CmpBaseDirectory); CmpFilename:=GetDarwinSystemFilename(CmpFilename); {$ENDIF} {$IFDEF CaseInsensitiveFilenames} CmpBaseDirectory:=AnsiUpperCaseFileName(CmpBaseDirectory); CmpFilename:=AnsiUpperCaseFileName(CmpFilename); {$ENDIF} FileNameLength:=length(CmpFilename); if CmpFilename[FileNameLength]=PathDelim then dec(FileNameLength); BaseDirLen:=length(CmpBaseDirectory); if CmpBaseDirectory[BaseDirLen]=PathDelim then dec(BaseDirLen); if BaseDirLen=0 then exit; // skip matching directories MinLen:=FileNameLength; if MinLen>BaseDirLen then MinLen:=BaseDirLen; p:=1; DirCount:=0; while (p<=MinLen) and (CmpFileName[p]=CmpBaseDirectory[p]) do begin if CmpFilename[p]=PathDelim then inc(DirCount); inc(p); end; if ((p>BaseDirLen) or (CmpBaseDirectory[p]=PathDelim)) and ((p>FileNameLength) or (CmpFilename[p]=PathDelim)) then inc(DirCount); if DirCount=0 then exit; // calculate needed up directories BaseDirLen:=length(BaseDirectory); UpDirCount:=-DirCount; BaseDirPos:=1; while (BaseDirPos<=BaseDirLen) do begin if (BaseDirectory[BaseDirPos]=PathDelim) then inc(UpDirCount); inc(BaseDirPos); end; if (BaseDirLen>0) and (BaseDirectory[BaseDirLen]<>PathDelim) then inc(UpDirCount); // create relative filename SamePos:=1; p:=0; FileNameLength:=length(Filename); while (SamePos<=FileNameLength) do begin if (Filename[SamePos]=PathDelim) then begin inc(p); if p>=DirCount then begin inc(SamePos); break; end; end; inc(SamePos); end; FileNameRestLen:=FileNameLength-SamePos+1; //writeln('DirCount=',DirCount,' UpDirCount=',UpDirCount,' FileNameRestLen=',FileNameRestLen,' SamePos=',SamePos); SetLength(Result,3*UpDirCount+FileNameRestLen); ResultPos:=1; for i:=1 to UpDirCount do begin Result[ResultPos]:='.'; Result[ResultPos+1]:='.'; Result[ResultPos+2]:=PathDelim; inc(ResultPos,3); end; if FileNameRestLen>0 then System.Move(Filename[SamePos],Result[ResultPos],FileNameRestLen); 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:=TrimFilename(Filename); ExpPath:=AppendPathDelim(TrimFilename(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 (Path[length(Path)]<>PathDelim) then Result:=Path+PathDelim else Result:=Path; end; function ChompPathDelim(const Path: string): string; var Len: Integer; begin Result:=Path; Len:=length(Result); while (Len>1) and (Result[Len]=PathDelim) do dec(Len); if Len=0) and (Filename[StartPos]<>'.') do dec(StartPos); if StartPos<=0 then exit(false); // check extension ExtLen:=FilenameLen-StartPos; for e:=Low(CTPascalExtension) to High(CTPascalExtension) do begin if (CTPascalExtension[e]='') or (length(CTPascalExtension[e])<>ExtLen) then continue; i:=0; p:=PChar(Pointer(CTPascalExtension[e]));// pointer type cast avoids #0 check if CaseSensitive then begin while (i0; end; FindCloseUTF8(FileInfo); if Result<>'' then Result:=Base+Result; end; function SearchPascalUnitInPath(const AnUnitName, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; var p, StartPos, l: integer; CurPath, Base: string; begin Base:=ExpandFileNameUTF8(AppendPathDelim(BasePath)); // search in current directory Result:=SearchPascalUnitInDir(AnUnitName,Base,SearchCase); if Result<>'' then exit; // search in search path StartPos:=1; l:=length(SearchPath); while StartPos<=l do begin p:=StartPos; while (p<=l) and (pos(SearchPath[p],Delimiter)<1) do inc(p); CurPath:=Trim(copy(SearchPath,StartPos,p-StartPos)); if CurPath<>'' then begin if not FilenameIsAbsolute(CurPath) then CurPath:=Base+CurPath; CurPath:=ExpandFileNameUTF8(AppendPathDelim(CurPath)); Result:=SearchPascalUnitInDir(AnUnitName,CurPath,SearchCase); if Result<>'' then exit; end; StartPos:=p+1; end; Result:=''; end; function SearchPascalFileInDir(const ShortFilename, BaseDirectory: string; SearchCase: TCTSearchFileCase): string; procedure RaiseNotImplemented; begin raise Exception.Create('not implemented'); end; var Base: String; FileInfo: TSearchRec; LowerCaseFilename: string; UpperCaseFilename: string; begin Base:=AppendPathDelim(BaseDirectory); Base:=TrimFilename(Base); // search file Result:=''; if SearchCase=ctsfcAllCase then Base:=FindDiskFilename(Base); if SearchCase in [ctsfcDefault,ctsfcLoUpCase] then begin LowerCaseFilename:=lowercase(ShortFilename); UpperCaseFilename:=uppercase(ShortFilename); end else begin LowerCaseFilename:=''; UpperCaseFilename:=''; end; if FindFirstUTF8(Base+FileMask,faAnyFile,FileInfo)=0 then begin repeat // check if special file if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then continue; case SearchCase of ctsfcDefault,ctsfcLoUpCase: if (ShortFilename=FileInfo.Name) then begin Result:=FileInfo.Name; break; end else if (LowerCaseFilename=FileInfo.Name) or (UpperCaseFilename=FileInfo.Name) then Result:=FileInfo.Name; ctsfcAllCase: if CompareFilenamesIgnoreCase(ShortFilename,FileInfo.Name)=0 then begin Result:=FileInfo.Name; if ShortFilename=FileInfo.Name then break; end; else RaiseNotImplemented; end; until FindNextUTF8(FileInfo)<>0; end; FindCloseUTF8(FileInfo); if Result<>'' then Result:=Base+Result; end; function SearchPascalFileInPath(const ShortFilename, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; var p, StartPos, l: integer; CurPath, Base: string; begin Base:=ExpandFileNameUTF8(AppendPathDelim(BasePath)); // search in current directory if not FilenameIsAbsolute(Base) then Base:=''; if Base<>'' then begin Result:=SearchPascalFileInDir(ShortFilename,Base,SearchCase); if Result<>'' then exit; end; // search in search path StartPos:=1; l:=length(SearchPath); while StartPos<=l do begin p:=StartPos; while (p<=l) and (pos(SearchPath[p],Delimiter)<1) do inc(p); CurPath:=Trim(copy(SearchPath,StartPos,p-StartPos)); if CurPath<>'' then begin if not FilenameIsAbsolute(CurPath) then CurPath:=Base+CurPath; CurPath:=ExpandFileNameUTF8(AppendPathDelim(CurPath)); if FilenameIsAbsolute(CurPath) then begin Result:=SearchPascalFileInDir(ShortFilename,CurPath,SearchCase); if Result<>'' then exit; end; end; StartPos:=p+1; end; Result:=''; 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 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 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; begin Result:=nil; if SearchPath=nil then exit; if APath=nil then exit; // ignore trailing PathDelim at end while (APathLen>1) and (APath[APathLen-1]=PathDelim) do dec(APathLen); StartPos:=0; while StartPos';') and (NextStartPosStartPos+1) and (SearchPath[EndPos-1]=PathDelim) do dec(EndPos); // compare current path if EndPos-StartPos=APathLen then begin CmpPos:=0; while CmpPosSearchPath[StartPos+CmpPos] then break; inc(CmpPos); end; if CmpPos=APathLen then begin Result:=@SearchPath[StartPos]; exit; end; end; StartPos:=NextStartPos+1; end; end; function ReadNextFPCParameter(const CmdLine: string; var Position: integer; out StartPos: integer): boolean; begin StartPos:=Position; while (StartPos<=length(CmdLine)) and (CmdLine[StartPos] in [' ',#9,#10,#13]) do inc(StartPos); Position:=StartPos; while (Position<=length(CmdLine)) do begin case CmdLine[Position] of ' ',#9,#10,#13: break; '''': repeat inc(Position); until (Position>length(CmdLine)) or (CmdLine[Position]=''''); '"': repeat inc(Position); until (Position>length(CmdLine)) or (CmdLine[Position]=''''); end; inc(Position); end; Result:=StartPos<=length(CmdLine); end; function SearchFileInDir(const Filename, BaseDirectory: string; SearchCase: TCTSearchFileCase): string; procedure RaiseNotImplemented; begin raise Exception.Create('not implemented'); end; var Base: String; ShortFile: String; FileInfo: TSearchRec; begin Base:=AppendPathDelim(BaseDirectory); ShortFile:=Filename; if System.Pos(PathDelim,ShortFile)>0 then begin Base:=Base+ExtractFilePath(ShortFile); ShortFile:=ExtractFilename(ShortFile); end; Base:=TrimFilename(Base); case SearchCase of ctsfcDefault: begin Result:=Base+ShortFile; if not FileExistsCached(Result) then Result:=''; end; ctsfcLoUpCase: begin Result:=Base+ShortFile; if not FileExistsCached(Result) then begin Result:=lowercase(Result); if not FileExistsCached(Result) then begin Result:=uppercase(Result); if not FileExistsCached(Result) then Result:=''; end; end; end; ctsfcAllCase: begin // search file Result:=''; Base:=FindDiskFilename(Base); if FindFirstUTF8(Base+FileMask,faAnyFile,FileInfo)=0 then begin repeat // check if special file if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then continue; if CompareFilenamesIgnoreCase(FileInfo.Name,ShortFile)=0 then begin if FileInfo.Name=ShortFile then begin // file found, with correct name Result:=FileInfo.Name; break; end else begin // alias found, but has not the correct name Result:=FileInfo.Name; end; end; until FindNextUTF8(FileInfo)<>0; end; FindCloseUTF8(FileInfo); if Result<>'' then Result:=Base+Result; end; else RaiseNotImplemented; end; end; function SearchFileInPath(const Filename, BasePath, SearchPath, Delimiter: string; SearchCase: TCTSearchFileCase): string; var p, StartPos, l: integer; CurPath, Base: string; begin //debugln('[SearchFileInPath] Filename="',Filename,'" BasePath="',BasePath,'" SearchPath="',SearchPath,'" Delimiter="',Delimiter,'"'); if (Filename='') then begin Result:=Filename; exit; end; // check if filename absolute if FilenameIsAbsolute(Filename) then begin if SearchCase=ctsfcDefault then begin if FileExistsCached(Filename) then begin Result:=ExpandFileNameUTF8(Filename); end else begin Result:=''; end; end else Result:=SearchFileInPath(ExtractFilename(Filename), ExtractFilePath(BasePath),'',';',SearchCase); exit; end; Base:=ExpandFileNameUTF8(AppendPathDelim(BasePath)); // search in current directory Result:=SearchFileInDir(Filename,Base,SearchCase); if Result<>'' then exit; // search in search path StartPos:=1; l:=length(SearchPath); while StartPos<=l do begin p:=StartPos; while (p<=l) and (pos(SearchPath[p],Delimiter)<1) do inc(p); CurPath:=Trim(copy(SearchPath,StartPos,p-StartPos)); if CurPath<>'' then begin if not FilenameIsAbsolute(CurPath) then CurPath:=Base+CurPath; CurPath:=ExpandFileNameUTF8(AppendPathDelim(CurPath)); Result:=SearchFileInDir(Filename,CurPath,SearchCase); if Result<>'' then exit; end; StartPos:=p+1; end; Result:=''; end; function FilenameIsMatching(const Mask, Filename: string; MatchExactly: boolean): boolean; (* check if Filename matches Mask if MatchExactly then the complete Filename must match, else only the start Filename matches exactly or is a file/directory in a subdirectory of mask Mask can contain the wildcards * and ? and the set operator {,} The wildcards will _not_ match PathDelim If you need the asterisk, the question mark or the PathDelim as character just put the SpecialChar character in front of it. Examples: /abc matches /abc, /abc/p, /abc/xyz/filename but not /abcd /abc/x?z/www matches /abc/xyz/www, /abc/xaz/www but not /abc/x/z/www /abc/x*z/www matches /abc/xz/www, /abc/xyz/www, /abc/xAAAz/www but not /abc/x/z/www /abc/x\*z/www matches /abc/x*z/www, /abc/x*z/www/ttt /a{b,c,d}e matches /abe, /ace, /ade *) function FindDirectoryStart(const AFilename: string; CurPos: integer): integer; begin Result:=CurPos; while (Result<=length(AFilename)) and (AFilename[Result]=PathDelim) do inc(Result); end; function FindDirectoryEnd(const AFilename: string; CurPos: integer): integer; begin Result:=CurPos; while (Result<=length(AFilename)) do begin if AFilename[Result]=SpecialChar then inc(Result,2) else if (AFilename[Result]=PathDelim) then break else inc(Result); end; end; function CharsEqual(c1, c2: char): boolean; begin {$ifdef CaseInsensitiveFilenames} Result:=(FPUpChars[c1]=FPUpChars[c2]); {$else} Result:=(c1=c2); {$endif} end; var DirStartMask, DirEndMask, DirStartFile, DirEndFile, AsteriskPos, BracketMaskPos, BracketFilePos: integer; begin //debugln('[FilenameIsMatching] Mask="',Mask,'" Filename="',Filename,'" MatchExactly=',MatchExactly); Result:=false; if (Filename='') then exit; if (Mask='') then begin Result:=true; exit; end; // test every directory DirStartMask:=1; DirStartFile:=1; repeat // find start of directories DirStartMask:=FindDirectoryStart(Mask,DirStartMask); DirStartFile:=FindDirectoryStart(Filename,DirStartFile); // find ends of directories DirEndMask:=FindDirectoryEnd(Mask,DirStartMask); DirEndFile:=FindDirectoryEnd(Filename,DirStartFile); // debugln(' Compare "',copy(Mask,DirStartMask,DirEndMask-DirStartMask),'"', // ' "',copy(Filename,DirStartFile,DirEndFile-DirStartFile),'"'); // compare directories AsteriskPos:=0; BracketMaskPos:=0; while (DirStartMask0 then begin // Bracket operator fits complete // -> skip rest of Bracket operator repeat inc(DirStartMask); if DirStartMask>=DirEndMask then exit; // error, missing } if Mask[DirStartMask]=SpecialChar then begin // special char -> next char is normal char inc(DirStartMask); end else if Mask[DirStartMask]='}' then begin // bracket found (= end of Or operator) inc(DirStartMask); break; end; until false; BracketMaskPos:=0; continue; end; '}': begin if BracketMaskPos>0 then begin // Bracket operator fits complete inc(DirStartMask); BracketMaskPos:=0; continue; end; end; end; if Mask[DirStartMask]=SpecialChar then begin // special char -> next char is normal char inc(DirStartMask); if (DirStartMask>=DirEndMask) then exit; end; // compare char if CharsEqual(Mask[DirStartMask],Filename[DirStartFile]) then begin inc(DirStartMask); inc(DirStartFile); end else begin // chars different if BracketMaskPos>0 then begin // try next Or repeat inc(DirStartMask); if DirStartMask>=DirEndMask then exit; // error, missing } if Mask[DirStartMask]=SpecialChar then begin // special char -> next char is normal char inc(DirStartMask); end else if Mask[DirStartMask]='}' then begin // bracket found (= end of Or operator) // -> filename does not match exit; end else if Mask[DirStartMask]=',' then begin // next Or found // -> reset filename position and compare inc(DirStartMask); DirStartFile:=BracketFilePos; break; end; until false; end else if AsteriskPos>0 then begin // * operator always fits inc(DirStartFile); end else begin // filename does not match exit; end; end; end; if BracketMaskPos>0 then exit; if (DirStartMasklength(Filename)) or (DirStartMask>length(Mask)); DirStartMask:=FindDirectoryStart(Mask,DirStartMask); // check that complete mask matches Result:=(DirStartMask>length(Mask)); if MatchExactly then begin DirStartFile:=FindDirectoryStart(Filename,DirStartFile); // check that the complete Filename matches Result:=(Result and (DirStartFile>length(Filename))); end; //debugl(' [FilenameIsMatching] Result=',Result,' ',DirStartMask,',',length(Mask),' ',DirStartFile,',',length(Filename)); end; function CompareFileExt(const Filename, Ext: string; CaseSensitive: boolean): integer; var FileLen, FilePos, ExtLen, ExtPos: integer; FileChar, ExtChar: char; 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 while true do begin if FilePos<=FileLen then begin if ExtPos<=ExtLen then begin FileChar:=Filename[FilePos]; ExtChar:=Ext[ExtPos]; if not CaseSensitive then begin FileChar:=FPUpChars[FileChar]; ExtChar:=FPUpChars[ExtChar]; end; if FileChar=ExtChar then begin inc(FilePos); inc(ExtPos); end else if FileChar>ExtChar then begin Result:=1; exit; end else begin Result:=-1; exit; end; end else begin // fileext longer than ext Result:=1; exit; end; end else begin if ExtPos<=ExtLen then begin // fileext shorter than ext Result:=-1; exit; end else begin // equal Result:=0; exit; end; end; end; end; function ComparePointers(p1, p2: Pointer): integer; begin if p1>p2 then Result:=1 else if p1=Pos2) and (Compare(List[Pos2-1],List[Pos3])<=0) do dec(Pos3); if (Pos1>=Pos2) or (Pos2>Pos3) then exit; Src1Pos:=Pos2-1; Src2Pos:=Pos3; DestPos:=Pos3; while (Src2Pos>=Pos2) and (Src1Pos>=Pos1) do begin cmp:=Compare(List[Src1Pos],List[Src2Pos]); if cmp>0 then begin MergeList[DestPos]:=List[Src1Pos]; dec(Src1Pos); end else begin MergeList[DestPos]:=List[Src2Pos]; dec(Src2Pos); end; dec(DestPos); end; while Src2Pos>=Pos2 do begin MergeList[DestPos]:=List[Src2Pos]; dec(Src2Pos); dec(DestPos); end; for i:=DestPos+1 to Pos3 do List[i]:=MergeList[i]; end; procedure Sort(const Pos1, Pos2: PtrInt); // sort List from Pos1 to Pos2, usig MergeList as temporary buffer var cmp, mid: PtrInt; begin if Pos1>=Pos2 then begin // one element is always sorted -> nothing to do end else if Pos1+1=Pos2 then begin // two elements can be sorted easily cmp:=Compare(List[Pos1],List[Pos2]); if cmp>0 then begin MergeList[Pos1]:=List[Pos1]; List[Pos1]:=List[Pos2]; List[Pos2]:=MergeList[Pos1]; end; end else begin mid:=(Pos1+Pos2) shr 1; Sort(Pos1,mid); Sort(mid+1,Pos2); Merge(Pos1,mid+1,Pos2); end; end; // sort ascending begin if ListLength<=1 then exit; GetMem(MergeList,SizeOf(Pointer)*ListLength); try Sort(0,ListLength-1); finally FreeMem(MergeList); end; end; function GetNextDelimitedItem(const List: string; Delimiter: char; var Position: integer): string; var StartPos: LongInt; begin StartPos:=Position; while (Position<=length(List)) and (List[Position]<>Delimiter) do inc(Position); Result:=copy(List,StartPos,Position-StartPos); if Position<=length(List) then inc(Position); // skip Delimiter end; function HasDelimitedItem(const List: string; Delimiter: char; FindItem: string ): boolean; var p: Integer; begin p:=1; Result:=FindNextDelimitedItem(List,Delimiter,p,FindItem)<>''; end; function FindNextDelimitedItem(const List: string; Delimiter: char; var Position: integer; FindItem: string): string; begin while Position<=length(List) do begin Result:=GetNextDelimitedItem(List,Delimiter,Position); if Result=FindItem then exit; end; Result:=''; end; function AVLTreeHasDoubles(Tree: TAVLTree): TAVLTreeNode; var Next: TAVLTreeNode; begin if Tree=nil then exit(nil); Result:=Tree.FindLowest; while Result<>nil do begin Next:=Tree.FindSuccessor(Result); if (Next<>nil) and (Tree.OnCompare(Result.Data,Next.Data)=0) then exit; Result:=Next; end; end; procedure DebugLn(Args: array of const); var i: Integer; begin for i:=Low(Args) to High(Args) do begin case Args[i].VType of vtInteger: DbgOut(dbgs(Args[i].vinteger)); vtInt64: DbgOut(dbgs(Args[i].VInt64^)); vtQWord: DbgOut(dbgs(Args[i].VQWord^)); vtBoolean: DbgOut(dbgs(Args[i].vboolean)); vtExtended: DbgOut(dbgs(Args[i].VExtended^)); {$ifdef FPC_CURRENCY_IS_INT64} // MWE: // fpc 2.x has troubles in choosing the right dbgs() // so we convert here vtCurrency: DbgOut(dbgs(int64(Args[i].vCurrency^)/10000 , 4)); {$else} vtCurrency: DbgOut(dbgs(Args[i].vCurrency^)); {$endif} vtString: DbgOut(Args[i].VString^); vtAnsiString: DbgOut(AnsiString(Args[i].VAnsiString)); vtChar: DbgOut(Args[i].VChar); vtPChar: DbgOut(Args[i].VPChar); vtPWideChar: DbgOut(Args[i].VPWideChar); vtWideChar: DbgOut(Args[i].VWideChar); vtWidestring: DbgOut(WideString(Args[i].VWideString)); vtObject: DbgOut(DbgSName(Args[i].VObject)); vtClass: DbgOut(DbgSName(Args[i].VClass)); vtPointer: DbgOut(Dbgs(Args[i].VPointer)); else DbgOut('?unknown variant?'); end; end; DebugLn; end; procedure DebugLn(const S: String; Args: array of const); begin DebugLn(Format(S, Args)); end; procedure DebugLn; begin DebugLn(''); end; procedure DebugLn(const s: string); begin if TextRec(Output).Mode<>fmClosed then writeln(s); end; procedure DebugLn(const s1, s2: string); begin DebugLn(s1+s2); end; procedure DebugLn(const s1, s2, s3: string); begin DebugLn(s1+s2+s3); end; procedure DebugLn(const s1, s2, s3, s4: string); begin DebugLn(s1+s2+s3+s4); end; procedure DebugLn(const s1, s2, s3, s4, s5: string); begin DebugLn(s1+s2+s3+s4+s5); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6: string); begin DebugLn(s1+s2+s3+s4+s5+s6); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7, s8: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7+s8); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7, s8, s9: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7+s8+s9); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7, s8, s9, s10: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10+s11); end; procedure DebugLn(const s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12: string); begin DebugLn(s1+s2+s3+s4+s5+s6+s7+s8+s9+s10+s11+s12); end; procedure DBGOut(const s: string); begin if TextRec(Output).Mode<>fmClosed then write(s); end; procedure DBGOut(const s1, s2: string); begin DbgOut(s1+s2); end; procedure DbgOut(const s1, s2, s3: string); begin DbgOut(s1+s2+s3); end; procedure DbgOut(const s1, s2, s3, s4: string); begin DbgOut(s1+s2+s3+s4); end; procedure DbgOut(const s1, s2, s3, s4, s5: string); begin DbgOut(s1+s2+s3+s4+s5); end; procedure DbgOut(const s1, s2, s3, s4, s5, s6: string); begin DbgOut(s1+s2+s3+s4+s5+s6); end; function DbgS(const c: char): string; begin case c of ' '..#126: Result:=c; else Result:='#'+IntToStr(ord(c)); end; end; function DbgS(const c: cardinal): string; begin Result:=IntToStr(c); end; function DbgS(const i: integer): string; begin Result:=IntToStr(i); end; function DbgS(const i: QWord): string; begin Result:=IntToStr(i); end; function DbgS(const i: int64): string; begin Result:=IntToStr(i); end; function DbgS(const r: TRect): string; begin Result:=' l='+IntToStr(r.Left)+',t='+IntToStr(r.Top) +',r='+IntToStr(r.Right)+',b='+IntToStr(r.Bottom); end; function DbgS(const p: TPoint): string; begin Result:=' x='+IntToStr(p.x)+',y='+IntToStr(p.y); end; function DbgS(const p: pointer): string; begin Result:=HexStr(p-nil,2*sizeof(PtrInt)); end; function DbgS(const e: extended; MaxDecimals: integer = 999): string; begin Result:=copy(FloatToStr(e),1,MaxDecimals); end; function DbgS(const b: boolean): string; begin if b then Result:='True' else Result:='False'; end; function DbgS(const i1, i2, i3, i4: integer): string; begin Result:=dbgs(i1)+','+dbgs(i2)+','+dbgs(i3)+','+dbgs(i4); end; function DbgSName(const p: TObject): string; begin if p=nil then Result:='nil' else if p is TComponent then Result:=TComponent(p).Name+':'+p.ClassName else Result:=p.ClassName; end; function DbgSName(const p: TClass): string; begin if p=nil then Result:='nil' else Result:=p.ClassName; end; function dbgMemRange(P: PByte; Count: integer; Width: integer): string; const HexChars: array[0..15] of char = '0123456789ABCDEF'; LineEnd: shortstring = LineEnding; var i: Integer; NewLen: Integer; Dest: PChar; Col: Integer; j: Integer; begin Result:=''; if (p=nil) or (Count<=0) then exit; NewLen:=Count*2; if Width>0 then begin inc(NewLen,(Count div Width)*length(LineEnd)); end; SetLength(Result,NewLen); Dest:=PChar(Result); Col:=1; for i:=0 to Count-1 do begin Dest^:=HexChars[PByte(P)[i] shr 4]; inc(Dest); Dest^:=HexChars[PByte(P)[i] and $f]; inc(Dest); inc(Col); if (Width>0) and (Col>Width) then begin Col:=1; for j:=1 to length(LineEnd) do begin Dest^:=LineEnd[j]; inc(Dest); end; end; end; end; function DbgStr(const StringWithSpecialChars: string): string; var i: Integer; s: String; begin Result:=StringWithSpecialChars; i:=1; while (i<=length(Result)) do begin case Result[i] of ' '..#126: inc(i); else s:='#'+IntToStr(ord(Result[i])); Result:=copy(Result,1,i-1)+s+copy(Result,i+1,length(Result)-i); inc(i,length(s)); end; end; end; function GetTicks: int64; var CurTick: Int64; begin CurTick:=round(Now*86400000); Result:=CurTick-LastTick; LastTick:=CurTick; end; procedure CTDumpStack; begin DebugLn(CTGetStackTrace(true)); end; function CTGetStackTrace(UseCache: boolean): string; var bp: Pointer; addr: Pointer; oldbp: Pointer; CurAddress: Shortstring; begin Result:=''; { retrieve backtrace info } bp:=get_caller_frame(get_frame); while bp<>nil do begin addr:=get_caller_addr(bp); CurAddress:=CTGetLineInfo(addr,UseCache); //DebugLn('GetStackTrace ',CurAddress); Result:=Result+CurAddress+LineEnding; oldbp:=bp; bp:=get_caller_frame(bp); if (bp<=oldbp) or (bp>(StackBottom + StackLength)) then bp:=nil; end; end; procedure CTGetStackTracePointers(var AStack: TCTStackTracePointers); var Depth: Integer; bp: Pointer; oldbp: Pointer; begin // get stack depth Depth:=0; bp:=get_caller_frame(get_frame); while bp<>nil do begin inc(Depth); oldbp:=bp; bp:=get_caller_frame(bp); if (bp<=oldbp) or (bp>(StackBottom + StackLength)) then bp:=nil; end; SetLength(AStack,Depth); if Depth>0 then begin Depth:=0; bp:=get_caller_frame(get_frame); while bp<>nil do begin AStack[Depth]:=get_caller_addr(bp); inc(Depth); oldbp:=bp; bp:=get_caller_frame(bp); if (bp<=oldbp) or (bp>(StackBottom + StackLength)) then bp:=nil; end; end; end; function CTStackTraceAsString(const AStack: TCTStackTracePointers; UseCache: boolean ): string; var i: Integer; CurAddress: String; begin Result:=''; for i:=0 to length(AStack)-1 do begin CurAddress:=CTGetLineInfo(AStack[i],UseCache); Result:=Result+CurAddress+LineEnding; end; end; function CTGetLineInfo(Addr: Pointer; UseCache: boolean): string; var ANode: TAVLTreeNode; Item: PCTLineInfoCacheItem; begin if UseCache then begin if LineInfoCache=nil then LineInfoCache:=TAVLTree.Create(@CompareCTLineInfoCacheItems); ANode:=LineInfoCache.FindKey(Addr,@CompareAddrWithCTLineInfoCacheItem); if ANode=nil then begin Result:=BackTraceStrFunc(Addr); New(Item); Item^.Addr:=Addr; Item^.Info:=Result; LineInfoCache.Add(Item); end else begin Result:=PCTLineInfoCacheItem(ANode.Data)^.Info; end; end else Result:=BackTraceStrFunc(Addr); end; function CompareCTLineInfoCacheItems(Data1, Data2: Pointer): integer; begin Result:=ComparePointers(PCTLineInfoCacheItem(Data1)^.Addr, PCTLineInfoCacheItem(Data2)^.Addr); end; function CompareAddrWithCTLineInfoCacheItem(Addr, Item: Pointer): integer; begin Result:=ComparePointers(Addr,PCTLineInfoCacheItem(Item)^.Addr); end; function FileExistsCached(const Filename: string): boolean; begin Result:=FileStateCache.FileExistsCached(Filename); end; function DirPathExistsCached(const Filename: string): boolean; begin Result:=FileStateCache.DirPathExistsCached(Filename); end; function DirectoryIsWritableCached(const DirectoryName: string): boolean; begin Result:=FileStateCache.DirectoryIsWritableCached(DirectoryName); end; function FileIsExecutableCached(const AFilename: string): boolean; begin Result:=FileStateCache.FileIsExecutableCached(AFilename); end; function FileIsReadableCached(const AFilename: string): boolean; begin Result:=FileStateCache.FileIsReadableCached(AFilename); end; function FileIsWritableCached(const AFilename: string): boolean; begin Result:=FileStateCache.FileIsWritableCached(AFilename); end; function FileIsTextCached(const AFilename: string): boolean; begin Result:=FileStateCache.FileIsTextCached(AFilename); end; procedure InvalidateFileStateCache; begin FileStateCache.IncreaseTimeStamp; end; function CompareFileStateItems(Data1, Data2: Pointer): integer; begin Result:=CompareFilenames(TFileStateCacheItem(Data1).FFilename, TFileStateCacheItem(Data2).FFilename); end; function CompareFilenameWithFileStateCacheItem(Key, Data: Pointer): integer; begin Result:=CompareFilenames(AnsiString(Key),TFileStateCacheItem(Data).FFilename); //debugln('CompareFilenameWithFileStateCacheItem Key=',AnsiString(Key),' Data=',TFileStateCacheItem(Data).FFilename,' Result=',dbgs(Result)); end; //------------------------------------------------------------------------------ procedure InternalInit; var c: char; begin FileStateCache:=TFileStateCache.Create; for c:=Low(char) to High(char) do begin FPUpChars[c]:=upcase(c); end; end; { TFileStateCacheItem } constructor TFileStateCacheItem.Create(const TheFilename: string; NewTimeStamp: integer); begin FFilename:=TheFilename; FTimeStamp:=NewTimeStamp; end; { TFileStateCache } procedure TFileStateCache.SetFlag(AFile: TFileStateCacheItem; AFlag: TFileStateCacheItemFlag; NewValue: boolean); begin if AFile.FTimeStamp<>FTimeStamp then begin AFile.FTestedFlags:=[]; AFile.FTimeStamp:=FTimeStamp; end; Include(AFile.FTestedFlags,AFlag); if NewValue then Include(AFile.FFlags,AFlag) else Exclude(AFile.FFlags,AFlag); //debugln('TFileStateCache.SetFlag AFile.Filename=',AFile.Filename,' ',FileStateCacheItemFlagNames[AFlag],'=',dbgs(AFlag in AFile.FFlags),' Valid=',dbgs(AFlag in AFile.FTestedFlags)); end; constructor TFileStateCache.Create; begin FFiles:=TAVLTree.Create(@CompareFileStateItems); FTimeStamp:=1; // one higher than default for new files end; destructor TFileStateCache.Destroy; begin FFiles.FreeAndClear; FFiles.Free; SetLength(FChangeTimeStampHandler,0); inherited Destroy; end; procedure TFileStateCache.Lock; begin inc(FLockCount); end; procedure TFileStateCache.Unlock; procedure RaiseTooManyUnlocks; begin raise Exception.Create('TFileStateCache.Unlock'); end; begin if FLockCount<=0 then RaiseTooManyUnlocks; dec(FLockCount); end; function TFileStateCache.Locked: boolean; begin Result:=FLockCount>0; end; procedure TFileStateCache.IncreaseTimeStamp; var i: Integer; begin if Self<>nil then begin if FTimeStampnil then Result:=TFileStateCacheItem(ANode.Data) else if CreateIfNotExists then begin Result:=TFileStateCacheItem.Create(TrimmedFilename,FTimeStamp); FFiles.Add(Result); if FFiles.FindKey(Pointer(TrimmedFilename), @CompareFilenameWithFileStateCacheItem)=nil then begin DebugLn(format('FileStateCache.FindFile: "%s"',[FileName])); WriteDebugReport; raise Exception.Create(''); end; end else Result:=nil; end; function TFileStateCache.Check(const Filename: string; AFlag: TFileStateCacheItemFlag; out AFile: TFileStateCacheItem; var FlagIsSet: boolean): boolean; begin AFile:=FindFile(Filename,true); if FTimeStamp=AFile.FTimeStamp then begin Result:=AFlag in AFile.FTestedFlags; FlagIsSet:=AFlag in AFile.FFlags; end else begin AFile.FTestedFlags:=[]; AFile.FTimeStamp:=FTimeStamp; Result:=false; FlagIsSet:=false; end; //debugln('TFileStateCache.Check Filename=',Filename,' AFile.Filename=',AFile.Filename,' ',FileStateCacheItemFlagNames[AFlag],'=',dbgs(FlagIsSet),' Valid=',dbgs(Result)); end; procedure TFileStateCache.WriteDebugReport; var ANode: TAVLTreeNode; AFile: TFileStateCacheItem; begin debugln('TFileStateCache.WriteDebugReport FTimeStamp=',dbgs(FTimeStamp)); ANode:=FFiles.FindLowest; while ANode<>nil do begin AFile:=TFileStateCacheItem(ANode.Data); debugln(' "',AFile.Filename,'" TimeStamp=',dbgs(AFile.TimeStamp)); ANode:=FFiles.FindSuccessor(ANode); end; debugln(' FFiles=',dbgs(FFiles.ConsistencyCheck)); debugln(FFiles.ReportAsString); end; procedure TFileStateCache.AddChangeTimeStampHandler(const Handler: TNotifyEvent ); begin SetLength(FChangeTimeStampHandler,length(FChangeTimeStampHandler)+1); FChangeTimeStampHandler[length(FChangeTimeStampHandler)-1]:=Handler; end; procedure TFileStateCache.RemoveChangeTimeStampHandler( const Handler: TNotifyEvent); var i: Integer; begin for i:=length(FChangeTimeStampHandler)-1 downto 0 do begin if Handler=FChangeTimeStampHandler[i] then begin if inil do begin Item:=PCTLineInfoCacheItem(ANode.Data); Dispose(Item); ANode:=LineInfoCache.FindSuccessor(ANode); end; LineInfoCache.Free; LineInfoCache:=nil; end; initialization {$IFDEF MEM_CHECK}CheckHeapWrtMemCnt('fileprocs.pas: initialization');{$ENDIF} InternalInit; finalization FileStateCache.Free; FileStateCache:=nil; FreeLineInfoCache; end.