{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UPath;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
interface
uses
SysUtils,
Classes,
{$IFDEF MSWINDOWS}
TntClasses,
{$ENDIF}
UUnicodeUtils;
type
IPath = interface;
{**
* TBinaryFileStream (inherited from THandleStream)
*}
{$IFDEF MSWINDOWS}
TBinaryFileStream = class(TTntFileStream)
{$ELSE}
TBinaryFileStream = class(TFileStream)
{$ENDIF}
public
{**
* @seealso TFileStream.Create for valid Mode parameters
*}
constructor Create(const FileName: IPath; Mode: Word);
end;
{**
* TUnicodeMemoryStream
*}
TUnicodeMemoryStream = class(TMemoryStream)
public
procedure LoadFromFile(const FileName: IPath);
procedure SaveToFile(const FileName: IPath);
end;
TTextFileStream = class(TBinaryFileStream)
function ReadLine(var Line: UTF8String): boolean; overload;
function ReadLine(var Line: AnsiString): boolean; overload;
procedure Write(Str: RawByteString);
procedure WriteLine(Line: RawByteString = '');
end;
{**
* pdKeep: Keep path as is, neither remove or append a delimiter
* pdAppend: Append a delimiter if path does not have a trailing one
* pdRemove: Remove a trailing delimiter from the path
*}
TPathDelimOption = (pdKeep, pdAppend, pdRemove);
IPathDynArray = array of IPath;
{**
* IPath
* The Path's pathname is immutable and cannot be changed after creation.
*}
IPath = interface
['{686BF103-CE43-4598-B85D-A2C3AF950897}']
{**
* Returns the path as an UTF8 encoded string.
* If UseNativeDelim is set to true, the native path delimiter ('\' on win32)
* is used. If it is set to false the (more) portable '/' delimiter will used.
*}
function ToUTF8(UseNativeDelim: boolean = true): UTF8String;
{**
* Returns the path as an UTF-16 encoded string.
* If UseNativeDelim is set to true, the native path delimiter ('\' on win32)
* is used. If it is set to false the delimiter will be '/'.
*}
function ToWide(UseNativeDelim: boolean = true): WideString;
{**
* Returns the path with the system's native encoding and path delimiter.
* Win32: ANSI (use the UTF-16 version IPath.ToWide() whenever possible)
* Mac: UTF8
* Unix: UTF8 or ANSI according to LC_CTYPE
*}
function ToNative(): RawByteString;
{**
* Note: File must be closed with FileClose(Handle) after usage
* @seealso SysUtils.FileOpen()
*}
function Open(Mode: LongWord): THandle;
{** @seealso SysUtils.ExtractFileDrive() *}
function GetDrive(): IPath;
{** @seealso SysUtils.ExtractFilePath() *}
function GetPath(): IPath;
{** @seealso SysUtils.ExtractFileDir() *}
function GetDir(): IPath;
{** @seealso SysUtils.ExtractFileName() *}
function GetName(): IPath;
{** @seealso SysUtils.ExtractFileExtension() *}
function GetExtension(): IPath;
{**
* Returns a copy of the path with the extension changed to Extension.
* The file itself is not changed, use Rename() for this task.
* @seealso SysUtils.ChangeFileExt()
*}
function SetExtension(const Extension: IPath): IPath; overload;
function SetExtension(const Extension: RawByteString): IPath; overload;
function SetExtension(const Extension: WideString): IPath; overload;
{**
* Returns the representation of the path relative to Basename.
* Note that the basename must be terminated with a path delimiter
* otherwise the last path component will be ignored.
* @seealso SysUtils.ExtractRelativePath()
*}
function GetRelativePath(const BaseName: IPath): IPath;
{** @seealso SysUtils.ExpandFileName() *}
function GetAbsolutePath(): IPath;
{**
* Returns the concatenation of this path with Child. If this path does not
* end with a path delimiter one is inserted in front of the Child path.
* Example: Path('parent').Append(Path('child')) -> Path('parent/child')
*}
function Append(const Child: IPath; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
function Append(const Child: RawByteString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
function Append(const Child: WideString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
{**
* Splits the path into its components. Path delimiters are not removed from
* components.
* Example: C:\test\my\dir -> ['C:\', 'test\', 'my\', 'dir']
*}
function SplitDirs(): IPathDynArray;
{**
* Returns the parent directory or PATH_NONE if none exists.
*}
function GetParent(): IPath;
{**
* Checks if this path is a subdir of or file inside Parent.
* If Direct is true this path must be a direct child.
* Example: C:\test\file is a direct child of C:\test and a child of C:\
*}
function IsChildOf(const Parent: IPath; Direct: boolean): boolean;
{**
* Adjusts the case of the path on case senstitive filesystems.
* If the path does not exist or the filesystem is case insensitive
* the original path will be returned. Otherwise a corrected copy.
*}
function AdjustCase(AdjustAllLevels: boolean): IPath;
{** @seealso SysUtils.IncludeTrailingPathDelimiter() *}
function AppendPathDelim(): IPath;
{** @seealso SysUtils.ExcludeTrailingPathDelimiter() *}
function RemovePathDelim(): IPath;
function Exists(): boolean;
function IsFile(): boolean;
function IsDirectory(): boolean;
function IsAbsolute(): boolean;
function GetFileAge(): integer; overload;
function GetFileAge(out FileDateTime: TDateTime): boolean; overload;
function GetAttr(): cardinal;
function SetAttr(Attr: Integer): boolean;
function IsReadOnly(): boolean;
function SetReadOnly(ReadOnly: boolean): boolean;
{**
* Checks if this path points to nothing, that means the path consists of
* the empty string '' and hence equals PATH_NONE.
* This is a shortcut for IPath.Equals('') or IPath.Equals(PATH_NONE).
* If IsUnset() returns true this path and PATH_NONE are equal but they must
* not be identical as the references might point to different objects.
*
* Example:
* Path('').Equals(PATH_EMPTY) -> true
* Path('') = PATH_EMPTY -> false
*}
function IsUnset(): boolean;
function IsSet(): boolean;
{**
* Compares this path with Other and returns true if both paths are
* equal. Both paths are expanded and trailing slashes excluded before
* comparison. If IgnoreCase is true, the case will be ignored on
* case-sensitive filesystems.
*}
function Equals(const Other: IPath; IgnoreCase: boolean = false): boolean; overload;
function Equals(const Other: RawByteString; IgnoreCase: boolean = false): boolean; overload;
function Equals(const Other: WideString; IgnoreCase: boolean = false): boolean; overload;
{**
* Searches for a file in DirList. The Result is nil if the file was
* not found. Use IFileSystem.FileFind() instead if you want to use
* wildcards.
* @seealso SysUtils.FileSearch()
*}
function FileSearch(const DirList: IPath): IPath;
{** File must be closed with FileClose(Handle) after usage }
function CreateFile(): THandle;
function DeleteFile(): boolean;
function CreateDirectory(Force: boolean = false): boolean;
function DeleteEmptyDir(): boolean;
function Rename(const NewName: IPath): boolean;
function CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
// TODO: Dirwatch stuff
// AddFileChangeListener(Listener: TFileChangeListener);
end;
{**
* Creates a new path with the given pathname. PathName can be either in UTF8
* or the local encoding.
* Notes:
* - On Apple only UTF8 is supported
* - Same applies to Unix with LC_CTYPE set to UTF8 encoding (default on newer systems)
*}
function Path(const PathName: RawByteString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
{**
* Creates a new path with the given UTF-16 pathname.
*}
function Path(const PathName: WideString; DelimOption: TPathDelimOption = pdKeep): IPath; overload;
{**
* Returns a singleton for Path('').
*}
function PATH_NONE(): IPath;
implementation
uses
UFilesystem;
type
TPathImpl = class(TInterfacedObject, IPath)
private
fName: UTF8String; //<** internal filename string, always UTF8 with PathDelim
{**
* Unifies the filename. Path-delimiters are replaced by '/'.
*}
procedure Unify(DelimOption: TPathDelimOption);
{**
* Returns a copy of fName with path delimiters changed to '/'.
*}
function GetPortableString(): UTF8String;
procedure AssertRefCount; inline;
public
constructor Create(const Name: UTF8String; DelimOption: TPathDelimOption);
destructor Destroy(); override;
function ToUTF8(UseNativeDelim: boolean): UTF8String;
function ToWide(UseNativeDelim: boolean): WideString;
function ToNative(): RawByteString;
function Open(Mode: LongWord): THandle;
function GetDrive(): IPath;
function GetPath(): IPath;
function GetDir(): IPath;
function GetName(): IPath;
function GetExtension(): IPath;
function SetExtension(const Extension: IPath): IPath; overload;
function SetExtension(const Extension: RawByteString): IPath; overload;
function SetExtension(const Extension: WideString): IPath; overload;
function GetRelativePath(const BaseName: IPath): IPath;
function GetAbsolutePath(): IPath;
function GetParent(): IPath;
function SplitDirs(): IPathDynArray;
function Append(const Child: IPath; DelimOption: TPathDelimOption): IPath; overload;
function Append(const Child: RawByteString; DelimOption: TPathDelimOption): IPath; overload;
function Append(const Child: WideString; DelimOption: TPathDelimOption): IPath; overload;
function Equals(const Other: IPath; IgnoreCase: boolean): boolean; overload;
function Equals(const Other: RawByteString; IgnoreCase: boolean): boolean; overload;
function Equals(const Other: WideString; IgnoreCase: boolean): boolean; overload;
function IsChildOf(const Parent: IPath; Direct: boolean): boolean;
function AdjustCase(AdjustAllLevels: boolean): IPath;
function AppendPathDelim(): IPath;
function RemovePathDelim(): IPath;
function GetFileAge(): integer; overload;
function GetFileAge(out FileDateTime: TDateTime): boolean; overload;
function Exists(): boolean;
function IsFile(): boolean;
function IsDirectory(): boolean;
function IsAbsolute(): boolean;
function GetAttr(): cardinal;
function SetAttr(Attr: Integer): boolean;
function IsReadOnly(): boolean;
function SetReadOnly(ReadOnly: boolean): boolean;
function IsUnset(): boolean;
function IsSet(): boolean;
function FileSearch(const DirList: IPath): IPath;
function CreateFile(): THandle;
function DeleteFile(): boolean;
function CreateDirectory(Force: boolean): boolean;
function DeleteEmptyDir(): boolean;
function Rename(const NewName: IPath): boolean;
function CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
end;
function Path(const PathName: RawByteString; DelimOption: TPathDelimOption): IPath;
begin
if (IsUTF8String(PathName)) then
Result := TPathImpl.Create(PathName, DelimOption)
else if (IsNativeUTF8()) then
Result := PATH_NONE
else
Result := TPathImpl.Create(AnsiToUtf8(PathName), DelimOption);
end;
function Path(const PathName: WideString; DelimOption: TPathDelimOption): IPath;
begin
Result := TPathImpl.Create(UTF8Encode(PathName), DelimOption);
end;
procedure TPathImpl.AssertRefCount;
begin
{$IFDEF FPC}
if (FRefCount <= 0) then
raise Exception.Create('RefCount error: ' + inttostr(FRefCount));
{$ENDIF}
end;
constructor TPathImpl.Create(const Name: UTF8String; DelimOption: TPathDelimOption);
begin
inherited Create();
fName := Name;
Unify(DelimOption);
end;
destructor TPathImpl.Destroy();
begin
inherited;
end;
procedure TPathImpl.Unify(DelimOption: TPathDelimOption);
var
I: integer;
begin
// convert all path delimiters to native ones
for I := 1 to Length(fName) do
begin
if (fName[I] in ['\', '/']) and (fName[I] <> PathDelim) then
fName[I] := PathDelim;
end;
// Include/ExcludeTrailingPathDelimiter need PathDelim as path delimiter
case DelimOption of
pdAppend: fName := IncludeTrailingPathDelimiter(fName);
pdRemove: fName := ExcludeTrailingPathDelimiter(fName);
end;
end;
function TPathImpl.GetPortableString(): UTF8String;
var
I: integer;
begin
Result := fName;
if (PathDelim = '/') then
Exit;
for I := 1 to Length(Result) do
begin
if (Result[I] = PathDelim) then
Result[I] := '/';
end;
end;
function TPathImpl.ToUTF8(UseNativeDelim: boolean): UTF8String;
begin
if (UseNativeDelim) then
Result := fName
else
Result := GetPortableString();
end;
function TPathImpl.ToWide(UseNativeDelim: boolean): WideString;
begin
if (UseNativeDelim) then
Result := UTF8Decode(fName)
else
Result := UTF8Decode(GetPortableString());
end;
function TPathImpl.ToNative(): RawByteString;
begin
if (IsNativeUTF8()) then
Result := fName
else
Result := Utf8ToAnsi(fName);
end;
function TPathImpl.GetDrive(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractFileDrive(Self);
end;
function TPathImpl.GetPath(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractFilePath(Self);
end;
function TPathImpl.GetDir(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractFileDir(Self);
end;
function TPathImpl.GetName(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractFileName(Self);
end;
function TPathImpl.GetExtension(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractFileExt(Self);
end;
function TPathImpl.SetExtension(const Extension: IPath): IPath;
begin
AssertRefCount;
Result := FileSystem.ChangeFileExt(Self, Extension);
end;
function TPathImpl.SetExtension(const Extension: RawByteString): IPath;
begin
Result := SetExtension(Path(Extension));
end;
function TPathImpl.SetExtension(const Extension: WideString): IPath;
begin
Result := SetExtension(Path(Extension));
end;
function TPathImpl.GetRelativePath(const BaseName: IPath): IPath;
begin
AssertRefCount;
Result := FileSystem.ExtractRelativePath(BaseName, Self);
end;
function TPathImpl.GetAbsolutePath(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExpandFileName(Self);
end;
function TPathImpl.GetParent(): IPath;
var
CurPath, ParentPath: IPath;
begin
AssertRefCount;
Result := PATH_NONE;
CurPath := Self.RemovePathDelim();
// check if current path has a parent (no further '/')
if (Pos(PathDelim, CurPath.ToUTF8()) = 0) then
Exit;
// set new path and check if it has changed to avoid endless loops
// e.g. with invalid paths like '/C:' (GetPath() uses ':' as delimiter too)
// on delphi/win32
ParentPath := CurPath.GetPath();
if (ParentPath.ToUTF8 = CurPath.ToUTF8) then
Exit;
Result := ParentPath;
end;
function TPathImpl.SplitDirs(): IPathDynArray;
var
CurPath: IPath;
TmpPath: IPath;
Components: array of IPath;
CurPathStr: UTF8String;
DelimPos: integer;
I: integer;
begin
SetLength(Result, 0);
if (Length(Self.ToUTF8(true)) = 0) then
Exit;
CurPath := Self;
SetLength(Components, 0);
repeat
SetLength(Components, Length(Components)+1);
CurPathStr := CurPath.ToUTF8();
DelimPos := LastDelimiter(PathDelim, SysUtils.ExcludeTrailingPathDelimiter(CurPathStr));
Components[High(Components)] := Path(Copy(CurPathStr, DelimPos+1, Length(CurPathStr)));
// TODO: remove this workaround for FPC bug
TmpPath := CurPath;
CurPath := TmpPath.GetParent();
until (CurPath = PATH_NONE);
// reverse list
SetLength(Result, Length(Components));
for I := 0 to High(Components) do
Result[I] := Components[High(Components)-I];
end;
function TPathImpl.Append(const Child: IPath; DelimOption: TPathDelimOption): IPath;
var
TmpResult: IPath;
begin
AssertRefCount;
if (fName = '') then
TmpResult := Child
else
TmpResult := Path(Self.AppendPathDelim().ToUTF8() + Child.ToUTF8());
case DelimOption of
pdKeep: Result := TmpResult;
pdAppend: Result := TmpResult.AppendPathDelim;
pdRemove: Result := TmpResult.RemovePathDelim;
end;
end;
function TPathImpl.Append(const Child: RawByteString; DelimOption: TPathDelimOption): IPath;
begin
Result := Append(Path(Child), DelimOption);
end;
function TPathImpl.Append(const Child: WideString; DelimOption: TPathDelimOption): IPath;
begin
Result := Append(Path(Child), DelimOption);
end;
function TPathImpl.Equals(const Other: IPath; IgnoreCase: boolean): boolean;
var
SelfPath, OtherPath: UTF8String;
begin
SelfPath := Self.GetAbsolutePath().RemovePathDelim().ToUTF8();
OtherPath := Other.GetAbsolutePath().RemovePathDelim().ToUTF8();
if (FileSystem.IsCaseSensitive() and not IgnoreCase) then
Result := (CompareStr(SelfPath, OtherPath) = 0)
else
Result := (CompareText(SelfPath, OtherPath) = 0);
end;
function TPathImpl.Equals(const Other: RawByteString; IgnoreCase: boolean): boolean;
begin
Result := Equals(Path(Other), IgnoreCase);
end;
function TPathImpl.Equals(const Other: WideString; IgnoreCase: boolean): boolean;
begin
Result := Equals(Path(Other), IgnoreCase);
end;
function TPathImpl.IsChildOf(const Parent: IPath; Direct: boolean): boolean;
var
SelfPath, ParentPath: UTF8String;
TmpPath, TmpPath2: IPath;
begin
Result := false;
if (Direct) then
begin
// TODO: remove workaround for fpc refcount bug
TmpPath := Self.GetParent();
TmpPath2 := TmpPath.GetAbsolutePath();
SelfPath := TmpPath2.AppendPathDelim().ToUTF8();
// TODO: remove workaround for fpc refcount bug
TmpPath := Parent.GetAbsolutePath();
ParentPath := TmpPath.AppendPathDelim().ToUTF8();
// simply check if this paths parent path (SelfPath) equals ParentPath
Result := (SelfPath = ParentPath);
end
else
begin
// TODO: remove workaround for fpc refcount bug
TmpPath := Self.GetAbsolutePath();
SelfPath := TmpPath.AppendPathDelim().ToUTF8();
// TODO: remove workaround for fpc refcount bug
TmpPath := Parent.GetAbsolutePath();
ParentPath := TmpPath.AppendPathDelim().ToUTF8();
if (Length(SelfPath) <= Length(ParentPath)) then
Exit;
// check if ParentPath is a substring of SelfPath
if (FileSystem.IsCaseSensitive()) then
Result := (StrLComp(PAnsiChar(SelfPath), PAnsiChar(ParentPath), Length(ParentPath)) = 0)
else
Result := (StrLIComp(PAnsiChar(SelfPath), PAnsiChar(ParentPath), Length(ParentPath)) = 0)
end;
end;
function AdjustCaseRecursive(CurPath: IPath; AdjustAllLevels: boolean): IPath;
var
OldParent, AdjustedParent: IPath;
TmpPath: IPath;
LocalName: IPath;
PathFound: IPath;
PathWithAdjParent: IPath;
SearchInfo: TFileInfo;
FileIter: IFileIterator;
Pattern: IPath;
begin
// if case-sensitive path exists there is no need to adjust case
if (CurPath.Exists()) then
begin
Result := CurPath;
Exit;
end;
// extract name component of current path
// TODO: remove workaround for fpc refcount bug
TmpPath := CurPath.RemovePathDelim();
LocalName := TmpPath.GetName();
// try to adjust parent
OldParent := CurPath.GetParent();
if (OldParent <> PATH_NONE) then
begin
if (not AdjustAllLevels) then
begin
AdjustedParent := OldParent;
end
else
begin
AdjustedParent := AdjustCaseRecursive(OldParent, AdjustAllLevels);
if (AdjustedParent = nil) then
begin
// parent path was not found case-insensitive
Result := nil;
Exit;
end;
// check if the path with adjusted parent can be found now
PathWithAdjParent := AdjustedParent.Append(LocalName);
if (PathWithAdjParent.Exists()) then
begin
Result := PathWithAdjParent;
Exit;
end;
end;
Pattern := AdjustedParent.Append(Path('*'));
end
else // path has no parent
begin
// the top path can either be absolute or relative
if (CurPath.IsAbsolute) then
begin
// the only absolute directory at Unix without a parent is root ('/')
// and hence does not need to be adjusted
Result := CurPath;
Exit;
end;
// this is a relative path, search in the current working dir
AdjustedParent := nil;
Pattern := Path('*');
end;
// compare name with all files in the current directory case-insensitive
FileIter := FileSystem.FileFind(Pattern, faAnyFile);
while (FileIter.HasNext()) do
begin
SearchInfo := FileIter.Next();
PathFound := SearchInfo.Name;
if (CompareText(LocalName.ToUTF8, PathFound.ToUTF8) = 0) then
begin
if (AdjustedParent <> nil) then
Result := AdjustedParent.Append(PathFound)
else
Result := PathFound;
Exit;
end;
end;
// no matching file found
Result := nil;
end;
function TPathImpl.AdjustCase(AdjustAllLevels: boolean): IPath;
begin
AssertRefCount;
Result := Self;
if (FileSystem.IsCaseSensitive) then
begin
Result := AdjustCaseRecursive(Self, AdjustAllLevels);
if (Result = nil) then
Result := Self;
end;
end;
function TPathImpl.AppendPathDelim(): IPath;
begin
AssertRefCount;
Result := FileSystem.IncludeTrailingPathDelimiter(Self);
end;
function TPathImpl.RemovePathDelim(): IPath;
begin
AssertRefCount;
Result := FileSystem.ExcludeTrailingPathDelimiter(Self);
end;
function TPathImpl.CreateFile(): THandle;
begin
Result := FileSystem.FileCreate(Self);
end;
function TPathImpl.CreateDirectory(Force: boolean): boolean;
begin
if (Force) then
Result := FileSystem.ForceDirectories(Self)
else
Result := FileSystem.DirectoryCreate(Self);
end;
function TPathImpl.Open(Mode: LongWord): THandle;
begin
Result := FileSystem.FileOpen(Self, Mode);
end;
function TPathImpl.GetFileAge(): integer;
begin
Result := FileSystem.FileAge(Self);
end;
function TPathImpl.GetFileAge(out FileDateTime: TDateTime): boolean;
begin
Result := FileSystem.FileAge(Self, FileDateTime);
end;
function TPathImpl.Exists(): boolean;
begin
// note the different specifications of FileExists() on Win32 <> Unix
{$IFDEF MSWINDOWS}
Result := IsFile() or IsDirectory();
{$ELSE}
Result := FileSystem.FileExists(Self);
{$ENDIF}
end;
function TPathImpl.IsFile(): boolean;
begin
// note the different specifications of FileExists() on Win32 <> Unix
{$IFDEF MSWINDOWS}
Result := FileSystem.FileExists(Self);
{$ELSE}
Result := Exists() and not IsDirectory();
{$ENDIF}
end;
function TPathImpl.IsDirectory(): boolean;
begin
Result := FileSystem.DirectoryExists(Self);
end;
function TPathImpl.IsAbsolute(): boolean;
begin
AssertRefCount;
Result := FileSystem.FileIsReadOnly(Self);
end;
function TPathImpl.GetAttr(): cardinal;
begin
Result := FileSystem.FileGetAttr(Self);
end;
function TPathImpl.SetAttr(Attr: Integer): boolean;
begin
Result := FileSystem.FileSetAttr(Self, Attr);
end;
function TPathImpl.IsReadOnly(): boolean;
begin
Result := FileSystem.FileIsReadOnly(Self);
end;
function TPathImpl.SetReadOnly(ReadOnly: boolean): boolean;
begin
Result := FileSystem.FileSetReadOnly(Self, ReadOnly);
end;
function TPathImpl.IsUnset(): boolean;
begin
Result := (fName = '');
end;
function TPathImpl.IsSet(): boolean;
begin
Result := (fName <> '');
end;
function TPathImpl.FileSearch(const DirList: IPath): IPath;
begin
AssertRefCount;
Result := FileSystem.FileSearch(Self, DirList);
end;
function TPathImpl.Rename(const NewName: IPath): boolean;
begin
Result := FileSystem.RenameFile(Self, NewName);
end;
function TPathImpl.DeleteFile(): boolean;
begin
Result := FileSystem.DeleteFile(Self);
end;
function TPathImpl.DeleteEmptyDir(): boolean;
begin
Result := FileSystem.RemoveDir(Self);
end;
function TPathImpl.CopyFile(const Target: IPath; FailIfExists: boolean): boolean;
begin
Result := FileSystem.CopyFile(Self, Target, FailIfExists);
end;
{ TBinaryFileStream }
constructor TBinaryFileStream.Create(const FileName: IPath; Mode: Word);
begin
{$IFDEF MSWINDOWS}
inherited Create(FileName.ToWide(), Mode);
{$ELSE}
inherited Create(FileName.ToNative(), Mode);
{$ENDIF}
end;
{ TTextFileStream }
function TTextFileStream.ReadLine(var Line: UTF8String): boolean;
begin
// TODO
end;
function TTextFileStream.ReadLine(var Line: AnsiString): boolean;
begin
// TODO
end;
procedure TTextFileStream.WriteLine(Line: RawByteString);
begin
Self.WriteBuffer(Line[1], Length(Line));
end;
procedure TTextFileStream.Write(Str: RawByteString);
begin
Self.WriteBuffer(Str[1], Length(Str));
end;
{ TUnicodeMemoryStream }
procedure TUnicodeMemoryStream.LoadFromFile(const FileName: IPath);
var
Stream: TStream;
begin
Stream := TBinaryFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TUnicodeMemoryStream.SaveToFile(const FileName: IPath);
var
Stream: TStream;
begin
Stream := TBinaryFileStream.Create(FileName, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
var
PATH_NONE_Singelton: IPath;
function PATH_NONE(): IPath;
begin
Result := PATH_NONE_Singelton;
end;
initialization
PATH_NONE_Singelton := Path('');
finalization
PATH_NONE_Singelton := nil;
end.