(**************************************************************************** * WANT - A build management tool. * * Copyright (c) 2001-2003 Juancarlo Anez, Caracas, Venezuela. * * All rights reserved. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ****************************************************************************) { @brief @author Juanco Aņez } unit WildPaths; {: @BUG: SplitPath is not consisten with PathConcat. The output of the first can not be put back together with the second. This is a problem for UNC paths. @TODO: File-by-file matching currently relies too much on the OS, so it may not work consistently in Windows and Linux. To make it consistent: * Allways retreive lists of files with an '*' pattern * Filter the resulting list using IsMatch and our own pattern. Besides consistency, our own matching would allow us to use extended, Unix-like file matching ([-] ranges, etc.) on Windows. Do not make these changes until specific user stories prove them necessary. @TODO: Factor out stuff that JCL already handles well. } interface uses Windows, SysUtils, Math, Classes, JclFileUtils, WantUtils; const WildChars = '?*'; InvalidPathChars = string(';' {$IFDEF LINUX} + ':' {$ENDIF}); {$IFDEF LINUX} SystemPathDelimiter: string = '/'; {$ELSE} SystemPathDelimiter: string = '\'; {$ENDIF} type TPath = type string; TPattern = TPath; TPaths = array of TPath; TPatterns = TPaths; TSystemPath = string; TSystemPaths = array of TSystemPath; EPathException = class(Exception); EFileOpException = class(EPathException); TFileAttribute = ( ReadOnly, {= $00000001 } Hidden, {= $00000002 } SysFile, {= $00000004 } VolumeID, {= $00000008 } Directory, {= $00000010 } Archive, {= $00000020 } NoFile {= -1} ); TFileAttributes = set of TFileAttribute; const AnyFileAttribute = []; function IsLocalPath(const Path: TPath): boolean; procedure AssertIsLocalPath(const Path: TPath); function IsWindowsPath(const Path: TPath):boolean; function PathDrive(const Path: TPath): string; function PathServer(Path: TPath): string; function PathFile(const Path: TPath): TPath; function PathDir(const Path: TPath): TPath; function RemovePathDrive(Path: TPath):TPath; function RemovePathServer(Path: TPath):TPath; function PathConcat(Path1, Path2: TPath): TPath; function ToSystemPath(const Path: TPath; const BasePath: TPath = ''):TSystemPath; function ToPath(SystemPath: TSystemPath; const BasePath: TPath = ''):TPath; function ToSystemPaths(const Paths: TPaths; const BasePath: TPath = ''): TSystemPaths; overload; function ToPaths(OSPaths: TSystemPaths; const BasePath: TPath = ''): TPaths; procedure ToSystemPaths(Paths: TStrings; const BasePath: TPath = ''); overload; function StringsToPaths(S: TStrings):TPaths; function SplitPath(Path: TPath): TPaths; function JoinPaths(Paths: TPaths): TPath; function MovePath(Path, FromBase: TPath; ToBase: TPath = ''): TPath; function MovePaths(const Paths: TPaths; const FromBase: TPath; const ToBase: TPath = ''): TPaths; function ToRelativePath(Path, BasePath: TPath):TPath; function ToRelativePaths(const Paths: TPaths; const BasePath: TPath):TPaths; overload; function ToRelativePaths(Paths: TStrings; const BasePath: TPath):TPaths; overload; function NormalizePath(Path :TPath) :TPath; function PathIsAbsolute(Path: TPath): boolean; procedure ForceRelativePath(var Path, BasePath: TPath); function FindPaths(Path: TPath; BasePath: TPath = ''; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ): TPaths; function FindFiles(const Path: TPath; const BasePath: TPath = ''): TPaths; function FindDirs(const Path: TPath; const BasePath: TPath = ''): TPaths; function Wild( Pattern: TPath; BasePath: TPath = ''; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ):TPaths; overload; procedure Wild(Files: TStrings; Pattern: TPath; BasePath: TPath = ''; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ); overload; function IsMatch(Pattern, Path: TPath):boolean; overload; function IsMatch(const Patterns: TPatterns; const Paths: TPaths; s: Integer = 0; p: Integer = 0):boolean; overload; function PathExists(Path: TPath):boolean; function PathIsDir(Path: TPath):boolean; function PathIsFile(Path: TPath):boolean; function SuperPath(Path: TPath): TPath; // file operations procedure MakeDir(const Path: TPath); function ChangeDir(const Path: TPath; Verify :boolean = true) :boolean; function CurrentDir: TPath; function CurrentDrive :TPath; procedure CopyFile(const Src, Dst: TPath); procedure CopyFiles(const Sources, Dests: TPaths); overload; procedure CopyFiles(const Files: TPaths; FromPath, ToPath: TPath); overload; procedure CopyFiles(const Pattern: TPattern; const FromPath, ToPath: TPath); overload; procedure MoveFile(const Src, Dst: TPath); procedure MoveFiles(const Sources, Dests: TPaths); overload; procedure MoveFiles(const Files: TPaths; const FromPath, ToPath: TPath); overload; procedure MoveFiles(const Pattern: TPattern; const FromPath, ToPath: TPath); overload; procedure DeleteFile(const Path: TPath; DeleteReadOnly: boolean = false); procedure DeleteFiles(const Files: TPaths; DeleteReadOnly: boolean = false); overload; procedure DeleteFiles(const Pattern: TPath; const BasePath: TPath= ''; DeleteReadOnly: boolean = false); overload; procedure TouchFile(const Path: TPath; When: TDateTime = 0); overload; procedure TouchFile(const Path: TPath; When: string); overload; function FileAttributes(const Path: TPath):TFileAttributes; procedure SetFileAttributes(const Path: TPath; const Attr: TFileAttributes); function FileTime(const Path: TPath): TDateTime; function SystemFileAttributes(const Path: TPath): Integer; function SystemFileTime(const Path: TPath): Longint; function TimeToSystemFileTime(const Time: TDateTime):Integer; function FileAttributesToSystemAttributes(const Attr: TFileAttributes):Byte; function SystemAttributesToFileAttributes(Attr: Integer) :TFileAttributes; function ChangeExtension( Path: TPath; extension : String ): TPath; implementation procedure Wild( Files: TStrings; const Patterns: TPatterns; BasePath: TPath = ''; Index: Integer = 0; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ); overload; forward; function IsLocalPath(const Path: TPath): boolean; begin Result := ( Pos(SystemPathDelimiter, Path) = 0 ); end; function IsWindowsPath(const Path: TPath):boolean; begin Result := Pos(':', Path) <> 0; end; function CurrentDrive :TPath; begin Result := ToPath(ExtractFileDrive(GetCurrentDir)); end; function PathDrive(const Path: TPath): string; var p: Integer; begin if not IsWindowsPath(Path) then Result := '' else begin p := Pos(':', Path); Result := StrLeft(Path, p); end; end; function PathServer(Path: TPath): string; var P: string; begin Path := ToPath(Path); if StrLeft(Path, 2) <> '//' then Result := PathDrive(Path) else begin P := Copy(Path, 3, Length(Path)); Result := '//'+ StrToken(P, '/'); end; end; function PathFile(const Path: TPath): TPath; var splits :TPaths; begin Result := ''; splits := SplitPath(Path); if Length(splits) > 0 then Result := splits[High(splits)]; end; function PathDir(const Path: TPath): TPath; var splits :TPaths; begin Result := ''; splits := SplitPath(Path); if Length(splits) > 0 then begin SetLength(splits, Length(splits)-1); Result := JoinPaths(splits); end; end; function RemovePathDrive(Path: TPath):TPath; var p: Integer; begin Result := Path; p := Pos(':', Result); Delete(Result, 1, p); end; function RemovePathServer(Path: TPath):TPath; var Server: string; begin Result := Path; Server := PathServer(Path); if Server <> '' then Delete(Result, 1, 3 + Length(Server)); end; procedure AssertIsLocalPath(const Path: TPath); begin if Pos(SystemPathDelimiter, Path) <> 0 then raise EPathException.Create( Format( '"%s" looks like a system path. Expected a system independent one.', [Path]) ); end; function PathConcat(Path1, Path2: TPath): TPath; var Parts: TPaths; i : Integer; P1 : TPath; P2 : TPath; begin Path1 := ToPath(Path1); Path2 := ToPath(Path2); Parts := nil; if (Length(Path1) = 0) //or (P1 = '.') or PathIsAbsolute(Path2) then Result := Path2 else if Length(Path2) = 0 then Result := Path1 else begin P1 := Path1; P2 := Path2; if P1[Length(P1)] = '/' then Delete(P1, Length(P1), 1); Result := P1; Parts := SplitPath(P2); for i := Low(Parts) to High(Parts) do begin if Parts[i] = '..' then Result := SuperPath(Result) else if Parts[i] = '.' then // do nothing else Result := Result + '/' + Parts[i]; end; end; Assert(Pos('//', Result) <= 1); end; procedure CheckPath(Path :TPath); var i :Integer; begin for i := 1 to Length(InvalidPathChars) do begin if Pos(InvalidPathChars[i], Path) <> 0 then raise EPathException.Create('invalid path chars in ' + Path); end; end; function ToPath(SystemPath: TSystemPath; const BasePath: TPath): TPath; begin CheckPath(SystemPath); CheckPath(BasePath); Result := SystemPath; if (Length(Result) >= 2) and (Result[2] = ':') and (Result[1] in ['a'..'z', 'A'..'Z']) then begin Result[1] := LowerCase(''+Result[1])[1]; Result := SystemPathDelimiter + Result; end; Result := StringReplace(Result, SystemPathDelimiter, '/', [rfReplaceAll]); if (BasePath <> '') then begin if (Length(Result) = 0) then Result := ToPath(BasePath) else if Result[1] <> '/' then begin Result := ToPath(BasePath) + '/' + Result; end; end; end; function ToSystemPath(const Path: TPath; const BasePath: TPath): string; begin CheckPath(Path); CheckPath(BasePath); Result := MovePath(Path, '', BasePath); if (Length(Result) >= 3) and (Result[3] = ':') and (Result[1] = '/') then Delete(Result,1, 1); if (Length(Result) >= 1) and (Result[Length(Result)] = '/') then Delete(Result,Length(Result), 1); Result := StringReplace(Result, '/', SystemPathDelimiter, [rfReplaceAll]); if PathIsAbsolute(Path) then Result := ExpandFileName(Result); end; function ToSystemPaths(const Paths: TPaths; const BasePath: TPath = ''): TSystemPaths; var i: Integer; begin SetLength(Result, Length(Paths)); for i := 0 to High(Result) do Result[i] := ToSystemPath(Paths[i], BasePath); end; procedure ToSystemPaths(Paths: TStrings; const BasePath: TPath = ''); var i: Integer; begin for i := 0 to Paths.Count-1 do Paths[i] := ToSystemPath(Paths[i], BasePath); end; function ToPaths(OSPaths: TSystemPaths; const BasePath: TPath = ''): TPaths; var i: Integer; begin SetLength(Result, Length(OSPaths)); for i := 0 to High(Result) do Result[i] := ToPath(OSPaths[i], BasePath); end; function FindPaths( Path: TPath; BasePath: TPath; IncludeAttr: TFileAttributes; ExcludeAttr: TFileAttributes ):TPaths; var S: TStringList; Search: TSearchRec; SearchResult: Integer; Pattern :TPath; Dir :TPath; Lookup :TPath; begin Path := ToPath(Path); Dir := PathDir(Path); Pattern := PathFile(Path); if (Pos('*', Pattern) > 0) or (Pos('?', Pattern) > 0)then Lookup := ToSystemPath(PathConcat(Dir, '*'), BasePath) else Lookup := ToSystemPath(Path, BasePath); S := TStringList.Create; S.Sorted := True; try SearchResult := FindFirst(Lookup, faAnyFile, Search); try while SearchResult = 0 do begin if (Search.Name <> '.' ) and (Search.Name <> '..' ) and ((IncludeAttr = AnyFileAttribute) or ((SystemAttributesToFileAttributes(Search.Attr) * IncludeAttr) <> [])) and ((SystemAttributesToFileAttributes(Search.Attr) * ExcludeAttr) = []) and IsMatch(Pattern, Search.Name) then S.Add(ToPath(Search.Name, BasePath)); SearchResult := FindNext(Search); end; finally FindClose(Search); end; Result := StringsToPaths(S); finally FreeAndNil(S); end; end; function FindDirs(const Path: TPath; const BasePath: TPath): TPaths; begin Result := FindPaths(Path, BasePath, [Directory]); end; function FindFiles(const Path: TPath; const BasePath: TPath): TPaths; begin Result := FindPaths(Path, BasePath, AnyFileAttribute, [Directory]); end; function SplitPath(Path: TPath): TPaths; var S : TStrings; n, i : Integer; Base: TPath; begin Path := ToPath(Path); S := TStringList.Create; try n := 0; if PathIsAbsolute(Path) then begin ForceRelativePath(Path, Base); SetLength(Result, 1); Result[0] := Base; n := 1; end; StrToStrings(Path, '/', S); SetLength(Result, n+S.Count); for i := 0 to S.Count-1 do Result[i+n] := S[i]; finally FreeAndNil(S); end; end; function JoinPaths(Paths: TPaths): TPath; var i :Integer; begin if Length(Paths) = 0 then Result := '' else begin Result := Paths[0]; for i := 1 to High(Paths) do Result := PathConcat(Result, Paths[i]); end; end; function StringsToPaths(S: TStrings): TPaths; var i: Integer; begin SetLength(Result, S.Count); for i := 0 to S.Count-1 do Result[i] := S[i]; end; function MovePath(Path, FromBase, ToBase: TPath): TPath; begin Path := ToPath(Path); FromBase := ToPath(FromBase); ToBase := ToPath(ToBase); if (FromBase <> '') and (Pos(FromBase+'/', Path) = 1) then Result := PathConcat(ToBase, Copy(Path, 2+Length(FromBase), Length(Path))) else if PathIsAbsolute(Path) then Result := Path else Result := PathConcat(ToBase, Path); end; function MovePaths(const Paths: TPaths; const FromBase: TPath; const ToBase: TPath): TPaths; var i: Integer; begin SetLength(Result, Length(Paths)); for i := Low(Paths) to High(Paths) do Result[i] := MovePath(Paths[i], FromBase, ToBase); end; function NormalizePath(Path :TPath) :TPath; begin Result := ToPath(Path); if not PathIsAbsolute(Path) then Result := PathConcat(CurrentDir, Result); if PathDrive(Result) = '' then Result := CurrentDrive + Result; end; function ToRelativePath(Path, BasePath: TPath):TPath; var P, B : TPaths; i, j: Integer; begin P := nil; B := nil; Path := PathConcat('.', ToPath(Path)); if not PathIsAbsolute(Path) and PathIsAbsolute(BasePath) then Result := Path else begin BasePath := ToPath(BasePath); if (PathDrive(Path) <> PathDrive(BasePath)) then begin Path := NormalizePath(Path); BasePath := NormalizePath(BasePath); end; if (PathDrive(Path) <> PathDrive(BasePath)) then Result := Path else begin Result := ''; P := SplitPath(Path); B := SplitPath(BasePath); i := 0; j := 0; while (i <= High(P)) and (j <= High(B)) and (P[i] = B[j]) do begin Inc(i); Inc(j); end; if j > High(B) then Result := '.' else begin while j <= High(B) do begin if Result = '' then Result := '..' else Result := Result + '/..'; Inc(j); end; end; while i <= High(P) do begin Result := PathConcat(Result, P[i]); Inc(i); end; end; if Result = '' then Result := '.'; end; end; function ToRelativePaths(const Paths: TPaths; const BasePath: TPath):TPaths; begin Result := MovePaths(Paths, BasePath, ''); end; function ToRelativePaths(Paths: TStrings; const BasePath: TPath):TPaths; var i :Integer; begin for i := 0 to Paths.Count-1 do Paths[i] := ToRelativePath(Paths[i], BasePath); end; function PathIsAbsolute(Path: TPath): boolean; begin Path := ToPath(Path); Result := (Length(Path) > 0) and (Path[1] = '/') or (Length(Path) >= 3) and (Path[2] = ':') and (Path[3] = '/'); end; procedure ForceRelativePath(var Path, BasePath: TPath); var p: Integer; begin Path := ToPath(Path); BasePath := ToPath(BasePath); if PathIsAbsolute(Path) then begin BasePath := ''; p := Pos('/', Path); BasePath := BasePath + Copy(Path, 1, p); Delete(Path, 1, p); if PathIsAbsolute(Path) then begin // must be UNC, URI, or C:/ style p := 1+Pos('/', Copy(Path, 2, Length(Path))); if p = 1 then begin BasePath := BasePath + Path; Path := ''; end else begin BasePath := BasePath + Copy(Path, 1, p-1); Delete(Path, 1, p); end; end; end; end; function Wild( Pattern: TPath; BasePath: TPath; IncludeAttr: TFileAttributes; ExcludeAttr: TFileAttributes ):TPaths; var Files: TStringList; begin Pattern := ToPath(Pattern); BasePath := ToPath(BasePath); Files := TStringList.Create; try Files.Sorted := True; Wild(Files, Pattern, BasePath, IncludeAttr, ExcludeAttr); Result := StringsToPaths(Files); finally FreeAndNil(Files); end; end; procedure Wild(Files: TStrings; Pattern: TPath; BasePath: TPath; IncludeAttr: TFileAttributes; ExcludeAttr: TFileAttributes ); var Pats: string; Pat : TPath; begin Pattern := ToPath(Pattern); BasePath := ToPath(BasePath); Pats := Pattern; Pat := StrToken(Pats, ','); while Pat <> '' do begin //ForceRelativePath(Pattern, BasePath); if PathIsAbsolute(Pattern) then Wild(Files, SplitPath(Pat), '', 0, IncludeAttr, ExcludeAttr) else Wild(Files, SplitPath(Pat), BasePath, 0, IncludeAttr, ExcludeAttr); Pat := StrToken(Pats, ','); end; end; procedure Wild( Files: TStrings; const Patterns: TPatterns; BasePath: TPath; Index: Integer; IncludeAttr: TFileAttributes; ExcludeAttr: TFileAttributes ); var i : Integer; Matches: TPaths; NewBase: TPath; begin BasePath := ToPath(BasePath); Matches := nil; if Index > High(Patterns) then EXIT; NewBase := BasePath; // absorb all non-wildcard patterns while (Index < High(Patterns)) and ((LastDelimiter(WildChars, Patterns[Index]) = 0) or (Patterns[Index] = '.') ) do begin NewBase := PathConcat(NewBase, Patterns[Index]); Inc(Index); end; Assert(Index <= High(Patterns)); if Index = High(Patterns) then begin // add files (works for '**' too) Matches := FindPaths(Patterns[Index], NewBase, IncludeAttr, ExcludeAttr); for i := Low(Matches) to High(Matches) do Files.Add(PathConcat(NewBase, Matches[i])) end; // handle wildcards if Patterns[Index] = '**' then begin // match anything and recurse Wild(Files, Patterns, NewBase, Index+1, IncludeAttr, ExcludeAttr); Matches := FindDirs('*', NewBase); // use same Index ('**') to recurse for i := Low(Matches) to High(Matches) do Wild(Files, Patterns, PathConcat(NewBase, Matches[i]), Index, IncludeAttr, ExcludeAttr); end else if Index < High(Patterns) then begin // match directories Matches := FindDirs(Patterns[Index], NewBase); for i := Low(Matches) to High(Matches) do Wild(Files, Patterns, PathConcat(NewBase, Matches[i]), Index+1, IncludeAttr, ExcludeAttr); end; end; function Matches(A, B: TPath; i: Integer = 1; j: Integer = 1):boolean; begin while (i <= Length(A)) and (j <= Length(B)) and (UpCase(A[i]) = UpCase(B[j])) do begin Inc(i); Inc(j); end; if B = '.' then Result := True else if j > Length(B) then Result := i > Length(A) else if i > Length(A) then Result := False else if B[j] = '?' then Result := Matches(A, B, i+1, j+1) or Matches(A, B, i, j+1) else if B[j] = '*' then Result := Matches(A, B, i+1, j+1) or Matches(A, B, i+1, j) or Matches(A, B, i , j+1) else Result := False; end; function IsMatch(const Patterns: TPatterns; const Paths: TPaths; s: Integer = 0; p: Integer = 0):boolean; begin while (p <= High(Paths)) and (s <= High(Patterns)) and (Patterns[s] <> '**') and Matches(Paths[p], Patterns[s]) do begin Inc(p); Inc(s); end; if s > High(Patterns) then Result := p > High(Paths) else if Patterns[s] = '**' then begin if p > High(Paths) then Result := s = High(Patterns) else Result := IsMatch(Patterns, Paths, s+1, p) or IsMatch(Patterns, Paths, s+1, p+1) or IsMatch(Patterns, Paths, s, p+1) end else Result := False; end; function IsMatch(Pattern, Path : TPath):boolean; begin Pattern := ToPath(Pattern); Path := ToPath(Path); Result := IsMatch(SplitPath(Pattern), SplitPath(Path)); end; function PathExists(Path: TPath):boolean; begin Path := ToPath(Path); Result := Length(FindPaths(Path)) >= 1; end; function PathIsDir(Path: TPath):boolean; begin Path := ToPath(Path); Result := (FileAttributes(Path) * [NoFile, Directory]) = [Directory]; end; function PathIsFile(Path: TPath):boolean; begin Path := ToPath(Path); Result := (FileAttributes(Path) * [NoFile, Directory]) = []; end; function SuperPath(Path: TPath): TPath; var p : Integer; f : string; begin Path := ToPath(Path); if (Path = '.') or (Path = '') then Result := '..' else if Path = '..' then Result := '../..' else begin Result := Path; p := LastDelimiter('/', Result); f := Copy(Result, p+1, Length(Result)); if (p = Length(Result)) or (f = '.') then begin Result := Copy(Result, 1, p-1); p := LastDelimiter('/', Result); f := Copy(Result, p+1, Length(Result)) end; if f = '..' then Result := Result + '/..' else Result := Copy(Result, 1, p-1); end; end; // file operations procedure MakeDir(const Path: TPath); begin if (Length(Path) > 0) and (Path[Length(Path)] <> ':') // Oops! Windows specific! and not PathIsDir(Path) then begin MakeDir(SuperPath(Path)); SysUtils.CreateDir(ToSystemPath(Path)); if not PathIsDir(Path) then raise EFileOpException.Create(Format('Could not create directory "%s"', [Path])); end; end; function ChangeDir(const Path: TPath; Verify :boolean) :boolean; begin Result := True; if (Path <> '') and (Path <> CurrentDir) then Result := SetCurrentDir(ToSystemPath(Path)); if not Result and Verify then raise EFileOpException.CreateFmt('Could not change to directory "%s"',[Path]); end; function CurrentDir: TPath; begin Result := ToPath(SysUtils.GetCurrentDir); end; procedure CopyFile(const Src, Dst: TPath); begin MakeDir(SuperPath(Dst)); if PathIsDir(Src) then MakeDir(Dst) else if not Windows.CopyFile( PChar(ToSystemPath(Src)), PChar(ToSystemPath(Dst)), False) then raise EFileOpException.Create(SysErrorMessage(GetLastError)); end; procedure CopyFiles(const Pattern: TPattern; const FromPath, ToPath: TPath); begin CopyFiles(Wild(Pattern, FromPath), FromPath, ToPath); end; procedure CopyFiles(const Files: TPaths; FromPath, ToPath: TPath); begin CopyFiles(Files, MovePaths(Files, FromPath, ToPath)); end; procedure CopyFiles(const Sources, Dests: TPaths); var f : Integer; begin for f := Max(Low(Sources), Low(Dests)) to Min(High(Sources), High(Dests)) do begin CopyFile(Sources[f], Dests[f]); end; end; procedure MoveFile(const Src, Dst: TPath); begin if PathIsDir(Src) then begin raise EFileOpException.Create(Format('Don''t know how to move dir "%s" to "%s"', [Src, Dst])); end; (* MoveFileEx not implemented on Win9x. Some help files say it is, but see this link for current doc: http://msdn.microsoft.com/library/psdk/winbase/filesio_9oe0.htm In addition, MOVEFILE_REPLACE_EXISTING does not work with read only files if not Windows.MoveFileEx(PChar(ToSystemPath(Src)), PChar(ToSystemPath(Dst)), MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING or MOVEFILE_WRITE_THROUGH) -- Chrismo *) WildPaths.CopyFile(Src, Dst); WildPaths.DeleteFile(Src); end; procedure MoveFiles(const Pattern: TPattern; const FromPath, ToPath: TPath); begin MoveFiles(Wild(Pattern, FromPath), FromPath, ToPath); end; procedure MoveFiles(const Files: TPaths; const FromPath, ToPath: TPath); begin MoveFiles(Files, MovePaths(Files, FromPath, ToPath)); end; procedure MoveFiles(const Sources, Dests: TPaths); var f : Integer; begin for f := Max(Low(Sources), Low(Dests)) to Min(High(Sources), High(Dests)) do begin MoveFile(Sources[f], Dests[f]); end; end; procedure DeleteFile(const Path: TPath; DeleteReadOnly: boolean = false); var SysPath: string; FileAttr: Integer; begin SysPath := ToSystemPath(Path); if not PathIsDir(Path) then begin if DeleteReadOnly then begin { take off read only attribute if it exists } FileAttr := SysUtils.FileGetAttr(SysPath); FileAttr := FileAttr and (not $00000001); // faReadOnly. Avoid warning. SysUtils.FileSetAttr(SysPath, FileAttr); end; SysUtils.DeleteFile(SysPath) end else SysUtils.RemoveDir(SysPath); end; procedure DeleteFiles(const Pattern: TPath; const BasePath: TPath; DeleteReadOnly :boolean); begin DeleteFiles(Wild(Pattern, BasePath), DeleteReadOnly); end; procedure DeleteFiles(const Files: TPaths; DeleteReadOnly: boolean = false); var f: Integer; begin for f := Low(Files) to High(Files) do if not PathIsDir(Files[f]) then DeleteFile(Files[f], DeleteReadOnly); for f := Low(Files) to High(Files) do if PathIsDir(Files[f]) then DeleteFile(Files[f], DeleteReadOnly); end; procedure TouchFile(const Path: TPath; When: string); begin //!!! StrToDateTime changes with locale and platform!! TouchFile(Path, StrToDateTime(When)); end; procedure TouchFile(const Path: TPath; When: TDateTime); var Handle: Integer; begin if When = 0 then When := Now; MakeDir(SuperPath(Path)); Handle := FileOpen(ToSystemPath(Path), fmOpenWrite or fmShareDenyNone); if Handle < 0 then Handle := FileCreate(ToSystemPath(Path)); try FileSetDate(Handle, DateTimeToFileDate(When)) finally FileClose(Handle); end; end; function FileAttributes(const Path: TPath):TFileAttributes; var Attr :Integer; begin Attr := SystemFileAttributes(Path); if Attr < 0 then Result := [NoFile] else Result := TFileAttributes(Byte(Attr)); end; procedure SetFileAttributes(const Path: TPath; const Attr: TFileAttributes); begin SysUtils.FileSetAttr(ToSystemPath(Path), FileAttributesToSystemAttributes(Attr)); end; function FileTime(const Path: TPath): TDateTime; var SystemTime: Longint; begin SystemTime := SystemFileTime(Path); if SystemTime <= 0 then Result := 0 else Result := FileDateToDateTime(SystemTime); end; function SystemFileAttributes(const Path: TPath): Integer; begin Result := Byte(SysUtils.FileGetAttr(ToSystemPath(Path))); end; function SystemFileTime(const Path: TPath) : Longint; begin Result := SysUtils.FileAge(ToSystemPath(Path)); if Result < 0 then Result := 0; end; function TimeToSystemFileTime(const Time: TDateTime):Integer; begin Result := DateTimeToFileDate(Time); end; function FileAttributesToSystemAttributes(const Attr: TFileAttributes):Byte; begin Result := Byte(Attr); end; function SystemAttributesToFileAttributes(Attr: Integer) :TFileAttributes; begin Result := TFileAttributes(Byte(Attr)); end; function ChangeExtension( Path: TPath; extension : String ): TPath; var p : integer ; begin p:=LastDelimiter('.',Path); if (p < Length(Path)) then begin Result:=copy(Path,1,p)+extension; end else Result:=Path end; end.