{* 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.