aboutsummaryrefslogblamecommitdiffstats
path: root/src/base/UFilesystem.pas
blob: 805bcfe5bd9f241f6bd31cb70068feb67220799c (plain) (tree)










































































                                                                        
                                                            
                                                        
                                                                          










































































                                                                                                 
                                                            
                                                        
                                                                          








































































































                                                                                                 
                                                                        








                                                                    
                                                                                      
































































































































































                                                                                                             
                                                                        








                                                                    
                                                                                      























































































































































































































































                                                                                                             
{* 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 UFilesystem;

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}

uses
  SysUtils,
  Classes,
  {$IFDEF MSWINDOWS}
  Windows,
  TntSysUtils,
  {$ENDIF}
  UPath;

type
  {$IFDEF MSWINDOWS}
  TSytemSearchRec = TSearchRecW;
  {$ELSE}
  TSytemSearchRec = TSearchRec;
  {$ENDIF}

  TFileInfo = record
    Time: integer;  // timestamp
    Size: int64;    // file size (byte)
    Attr: integer;  // file attributes
    Name: IPath;    // basename with extension
  end;

  {**
   * Iterates through the search results retrieved by FileFind().
   * Example usage:
   *   while(Iter.HasNext()) do
   *     SearchRec := Iter.Next();
   *}
  IFileIterator = interface
    function HasNext(): boolean;
    function Next(): TFileInfo;
  end;

  {**
   * Wrapper for SysUtils file functions.
   * For documentation and examples, check the SysUtils equivalent.
   *}
  IFileSystem = interface
    function ExpandFileName(const FileName: IPath): IPath;
    function FileCreate(const FileName: IPath): TFileHandle;
    function DirectoryCreate(const Dir: IPath): boolean;
    function FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
    function FileAge(const FileName: IPath): integer; overload;
    function FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean; overload;

    function DirectoryExists(const Name: IPath): boolean;

    {**
     * On Windows: returns true only for files (not directories)
     * On Apple/Unix: returns true for all kind of files (even directories)
     * @seealso SysUtils.FileExists()
     *}
    function FileExists(const Name: IPath): boolean;

    function FileGetAttr(const FileName: IPath): Cardinal;
    function FileSetAttr(const FileName: IPath; Attr: integer): boolean;
    function FileIsReadOnly(const FileName: IPath): boolean;
    function FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
    function FileIsAbsolute(const FileName: IPath): boolean;
    function ForceDirectories(const Dir: IPath): boolean;
    function RenameFile(const OldName, NewName: IPath): boolean;
    function DeleteFile(const FileName: IPath): boolean;
    function RemoveDir(const Dir: IPath): boolean;

    {**
     * Copies file Source to Target. If FailIfExists is true, the file is not
     * copied if it already exists.
     * Returns true if the file was successfully copied.
     *}
    function CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;

    function ExtractFileDrive(const FileName: IPath): IPath;
    function ExtractFilePath(const FileName: IPath): IPath;
    function ExtractFileDir(const FileName: IPath): IPath;
    function ExtractFileName(const FileName: IPath): IPath;
    function ExtractFileExt(const FileName: IPath): IPath;
    function ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;

    function ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;

    function IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
    function ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;

    {**
     * Searches for a file with filename Name in the directories given in DirList.
     *}
    function FileSearch(const Name: IPath; DirList: array of IPath): IPath;

    {**
     * More convenient version of FindFirst/Next/Close with iterator support.
     *}
    function FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;

    {**
     * Old style search functions. Use FileFind() instead.
     *}
    function FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
    function FindNext(var F: TSytemSearchRec): integer;
    procedure FindClose(var F: TSytemSearchRec);

    function GetCurrentDir: IPath;
    function SetCurrentDir(const Dir: IPath): boolean;

    {**
     * Returns true if the filesystem is case-sensitive.
     *}
    function IsCaseSensitive(): boolean;
  end;

  function FileSystem(): IFileSystem;

implementation

type
  TFileSystemImpl = class(TInterfacedObject, IFileSystem)
  public
    function ExpandFileName(const FileName: IPath): IPath;
    function FileCreate(const FileName: IPath): TFileHandle;
    function DirectoryCreate(const Dir: IPath): boolean;
    function FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
    function FileAge(const FileName: IPath): integer; overload;
    function FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean; overload;
    function DirectoryExists(const Name: IPath): boolean;
    function FileExists(const Name: IPath): boolean;
    function FileGetAttr(const FileName: IPath): Cardinal;
    function FileSetAttr(const FileName: IPath; Attr: integer): boolean;
    function FileIsReadOnly(const FileName: IPath): boolean;
    function FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
    function FileIsAbsolute(const FileName: IPath): boolean;
    function ForceDirectories(const Dir: IPath): boolean;
    function RenameFile(const OldName, NewName: IPath): boolean;
    function DeleteFile(const FileName: IPath): boolean;
    function RemoveDir(const Dir: IPath): boolean;
    function CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;

    function ExtractFileDrive(const FileName: IPath): IPath;
    function ExtractFilePath(const FileName: IPath): IPath;
    function ExtractFileDir(const FileName: IPath): IPath;
    function ExtractFileName(const FileName: IPath): IPath;
    function ExtractFileExt(const FileName: IPath): IPath;
    function ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
    function ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
    function IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
    function ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;

    function FileSearch(const Name: IPath; DirList: array of IPath): IPath;
    function FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;

    function FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
    function FindNext(var F: TSytemSearchRec): integer;
    procedure FindClose(var F: TSytemSearchRec);

    function GetCurrentDir: IPath;
    function SetCurrentDir(const Dir: IPath): boolean;

    function IsCaseSensitive(): boolean;
  end;

  TFileIterator = class(TInterfacedObject, IFileIterator)
  private
    fHasNext: boolean;
    fSearchRec: TSytemSearchRec;
  public
    constructor Create(const FilePattern: IPath; Attr: integer);
    destructor Destroy(); override;

    function HasNext(): boolean;
    function Next(): TFileInfo;
  end;


var
  FileSystem_Singleton: IFileSystem;

function FileSystem(): IFileSystem;
begin
  Result := FileSystem_Singleton;
end;

function TFileSystemImpl.FileFind(const FilePattern: IPath; Attr: integer): IFileIterator;
begin
  Result := TFileIterator.Create(FilePattern, Attr);
end;

function TFileSystemImpl.IsCaseSensitive(): boolean;
begin
  // Windows and Mac OS X do not have case sensitive file systems
  {$IF Defined(MSWINDOWS) or Defined(DARWIN)}
    Result := false;
  {$ELSE}
    Result := true;
  {$IFEND}
end;

function TFileSystemImpl.FileIsAbsolute(const FileName: IPath): boolean;
var
  NameStr: UTF8String;
begin
  Result := true;
  NameStr := FileName.ToUTF8();

  {$IFDEF MSWINDOWS}
    // check if drive is given 'C:...'
    if (FileName.GetDrive().ToUTF8 <> '') then
      Exit;
    // check if path starts with '\\'
    if (Length(NameStr) >= 2) and
       (NameStr[1] = PathDelim) and (NameStr[2] = PathDelim) then
      Exit;
  {$ELSE} // Unix based systems
    // check if root dir given '/...'
    if (Length(NameStr) >= 1) and (NameStr[1] = PathDelim) then
      Exit;
  {$ENDIF}

  Result := false;
end;

{$IFDEF MSWINDOWS}

function TFileSystemImpl.ExpandFileName(const FileName: IPath): IPath;
begin
  Result := Path(WideExpandFileName(FileName.ToWide()));
end;

function TFileSystemImpl.FileCreate(const FileName: IPath): TFileHandle;
begin
  Result := WideFileCreate(FileName.ToWide());
end;

function TFileSystemImpl.DirectoryCreate(const Dir: IPath): boolean;
begin
  Result := WideCreateDir(Dir.ToWide());
end;

function TFileSystemImpl.FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
begin
  Result := WideFileOpen(FileName.ToWide(), Mode);
end;

function TFileSystemImpl.FileAge(const FileName: IPath): integer;
begin
  Result := WideFileAge(FileName.ToWide());
end;

function TFileSystemImpl.FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean;
begin
  Result := WideFileAge(FileName.ToWide(), FileDateTime);
end;

function TFileSystemImpl.DirectoryExists(const Name: IPath): boolean;
begin
  Result := WideDirectoryExists(Name.ToWide());
end;

function TFileSystemImpl.FileExists(const Name: IPath): boolean;
begin
  Result := WideFileExists(Name.ToWide());
end;

function TFileSystemImpl.FileGetAttr(const FileName: IPath): Cardinal;
begin
  Result := WideFileGetAttr(FileName.ToWide());
end;

function TFileSystemImpl.FileSetAttr(const FileName: IPath; Attr: integer): boolean;
begin
  Result := WideFileSetAttr(FileName.ToWide(), Attr);
end;

function TFileSystemImpl.FileIsReadOnly(const FileName: IPath): boolean;
begin
  Result := WideFileIsReadOnly(FileName.ToWide());
end;

function TFileSystemImpl.FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
begin
  Result := WideFileSetReadOnly(FileName.ToWide(), ReadOnly);
end;

function TFileSystemImpl.ForceDirectories(const Dir: IPath): boolean;
begin
  Result := WideForceDirectories(Dir.ToWide());
end;

function TFileSystemImpl.FileSearch(const Name: IPath; DirList: array of IPath): IPath;
var
  I: integer;
  DirListStr: WideString;
begin
  DirListStr := '';
  for I := 0 to High(DirList) do
  begin
    if (I > 0) then
      DirListStr := DirListStr + PathSep;
    DirListStr := DirListStr + DirList[I].ToWide();
  end;
  Result := Path(WideFileSearch(Name.ToWide(), DirListStr));
end;

function TFileSystemImpl.RenameFile(const OldName, NewName: IPath): boolean;
begin
  Result := WideRenameFile(OldName.ToWide(), NewName.ToWide());
end;

function TFileSystemImpl.DeleteFile(const FileName: IPath): boolean;
begin
  Result := WideDeleteFile(FileName.ToWide());
end;

function TFileSystemImpl.RemoveDir(const Dir: IPath): boolean;
begin
  Result := WideRemoveDir(Dir.ToWide());
end;

function TFileSystemImpl.CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
begin
  Result := WideCopyFile(Source.ToWide(), Target.ToWide(), FailIfExists);
end;

function TFileSystemImpl.ExtractFileDrive(const FileName: IPath): IPath;
begin
  Result := Path(WideExtractFileDrive(FileName.ToWide()));
end;

function TFileSystemImpl.ExtractFilePath(const FileName: IPath): IPath;
begin
  Result := Path(WideExtractFilePath(FileName.ToWide()));
end;

function TFileSystemImpl.ExtractFileDir(const FileName: IPath): IPath;
begin
  Result := Path(WideExtractFileDir(FileName.ToWide()));
end;

function TFileSystemImpl.ExtractFileName(const FileName: IPath): IPath;
begin
  Result := Path(WideExtractFileName(FileName.ToWide()));
end;

function TFileSystemImpl.ExtractFileExt(const FileName: IPath): IPath;
begin
  Result := Path(WideExtractFileExt(FileName.ToWide()));
end;

function TFileSystemImpl.ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
begin
  Result := Path(WideExtractRelativePath(BaseName.ToWide(), FileName.ToWide()));
end;

function TFileSystemImpl.ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
begin
  Result := Path(WideChangeFileExt(FileName.ToWide(), Extension.ToWide()));
end;

function TFileSystemImpl.IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
begin
  Result := Path(WideIncludeTrailingPathDelimiter(FileName.ToWide()));
end;

function TFileSystemImpl.ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
begin
  Result := Path(WideExcludeTrailingPathDelimiter(FileName.ToWide()));
end;

function TFileSystemImpl.FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
begin
  Result := WideFindFirst(FilePattern.ToWide(), Attr, F);
end;

function TFileSystemImpl.FindNext(var F: TSytemSearchRec): integer;
begin
  Result := WideFindNext(F);
end;

procedure TFileSystemImpl.FindClose(var F: TSytemSearchRec);
begin
  WideFindClose(F);
end;

function TFileSystemImpl.GetCurrentDir: IPath;
begin
  Result := Path(WideGetCurrentDir());
end;

function TFileSystemImpl.SetCurrentDir(const Dir: IPath): boolean;
begin
  Result := WideSetCurrentDir(Dir.ToWide());
end;

{$ELSE} // UNIX

function TFileSystemImpl.ExpandFileName(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExpandFileName(FileName.ToNative()));
end;

function TFileSystemImpl.FileCreate(const FileName: IPath): TFileHandle;
begin
  Result := SysUtils.FileCreate(FileName.ToNative());
end;

function TFileSystemImpl.DirectoryCreate(const Dir: IPath): boolean;
begin
  Result := SysUtils.CreateDir(Dir.ToNative());
end;

function TFileSystemImpl.FileOpen(const FileName: IPath; Mode: longword): TFileHandle;
begin
  Result := SysUtils.FileOpen(FileName.ToNative(), Mode);
end;

function TFileSystemImpl.FileAge(const FileName: IPath): integer;
begin
  Result := SysUtils.FileAge(FileName.ToNative());
end;

function TFileSystemImpl.FileAge(const FileName: IPath; out FileDateTime: TDateTime): boolean;
var
  FileDate: integer;
begin
  FileDate := SysUtils.FileAge(FileName.ToNative());
  Result := (FileDate <> -1);
  if (Result) then
    FileDateTime := FileDateToDateTime(FileDate);
end;

function TFileSystemImpl.DirectoryExists(const Name: IPath): boolean;
begin
  Result := SysUtils.DirectoryExists(Name.ToNative());
end;

function TFileSystemImpl.FileExists(const Name: IPath): boolean;
begin
  Result := SysUtils.FileExists(Name.ToNative());
end;

function TFileSystemImpl.FileGetAttr(const FileName: IPath): Cardinal;
begin
  Result := SysUtils.FileGetAttr(FileName.ToNative());
end;

function TFileSystemImpl.FileSetAttr(const FileName: IPath; Attr: integer): boolean;
begin
  Result := (SysUtils.FileSetAttr(FileName.ToNative(), Attr) = 0);
end;

function TFileSystemImpl.FileIsReadOnly(const FileName: IPath): boolean;
begin
  Result := SysUtils.FileIsReadOnly(FileName.ToNative());
end;

function TFileSystemImpl.FileSetReadOnly(const FileName: IPath; ReadOnly: boolean): boolean;
begin
  Result := (SysUtils.FileSetAttr(FileName.ToNative(), faReadOnly) = 0);
end;

function TFileSystemImpl.ForceDirectories(const Dir: IPath): boolean;
begin
  Result := SysUtils.ForceDirectories(Dir.ToNative());
end;

function TFileSystemImpl.FileSearch(const Name: IPath; DirList: array of IPath): IPath;
var
  I: integer;
  DirListStr: AnsiString;
begin
  DirListStr := '';
  for I := 0 to High(DirList) do
  begin
    if (I > 0) then
      DirListStr := DirListStr + PathSep;
    DirListStr := DirListStr + DirList[I].ToNative();
  end;
  Result := Path(SysUtils.FileSearch(Name.ToNative(), DirListStr));
end;

function TFileSystemImpl.RenameFile(const OldName, NewName: IPath): boolean;
begin
  Result := SysUtils.RenameFile(OldName.ToNative(), NewName.ToNative());
end;

function TFileSystemImpl.DeleteFile(const FileName: IPath): boolean;
begin
  Result := SysUtils.DeleteFile(FileName.ToNative());
end;

function TFileSystemImpl.RemoveDir(const Dir: IPath): boolean;
begin
  Result := SysUtils.RemoveDir(Dir.ToNative());
end;

function TFileSystemImpl.CopyFile(const Source, Target: IPath; FailIfExists: boolean): boolean;
const
  COPY_BUFFER_SIZE = 4096; // a good tradeoff between speed and memory consumption
var
  SourceFile, TargetFile: TFileStream;
  FileCopyBuffer: array [0..COPY_BUFFER_SIZE-1] of byte; // temporary copy-buffer.
  NumberOfBytes: integer; // number of bytes read from SourceFile
begin
  Result := false;
  SourceFile := nil;
  TargetFile := nil;

  // if overwrite is disabled return if the target file already exists
  if (FailIfExists and FileExists(Target)) then
    Exit;

  try
    try
      // open source and target file (might throw an exception on error)
      SourceFile := TFileStream.Create(Source.ToNative(), fmOpenRead);
      TargetFile := TFileStream.Create(Target.ToNative(), fmCreate or fmOpenWrite);

      while true do
      begin
        // read a block from the source file and check for errors or EOF
        NumberOfBytes := SourceFile.Read(FileCopyBuffer, SizeOf(FileCopyBuffer));
        if (NumberOfBytes <= 0) then
          Break;
        // write block to target file and check if everything was written
        if (TargetFile.Write(FileCopyBuffer, NumberOfBytes) <> NumberOfBytes) then
          Exit;
      end;
    except
      Exit;
    end;
  finally
    SourceFile.Free;
    TargetFile.Free;
  end;

  Result := true;
end;

function TFileSystemImpl.ExtractFileDrive(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractFileDrive(FileName.ToNative()));
end;

function TFileSystemImpl.ExtractFilePath(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractFilePath(FileName.ToNative()));
end;

function TFileSystemImpl.ExtractFileDir(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractFileDir(FileName.ToNative()));
end;

function TFileSystemImpl.ExtractFileName(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractFileName(FileName.ToNative()));
end;

function TFileSystemImpl.ExtractFileExt(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractFileExt(FileName.ToNative()));
end;

function TFileSystemImpl.ExtractRelativePath(const BaseName: IPath; const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExtractRelativePath(BaseName.ToNative(), FileName.ToNative()));
end;

function TFileSystemImpl.ChangeFileExt(const FileName: IPath; const Extension: IPath): IPath;
begin
  Result := Path(SysUtils.ChangeFileExt(FileName.ToNative(), Extension.ToNative()));
end;

function TFileSystemImpl.IncludeTrailingPathDelimiter(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.IncludeTrailingPathDelimiter(FileName.ToNative()));
end;

function TFileSystemImpl.ExcludeTrailingPathDelimiter(const FileName: IPath): IPath;
begin
  Result := Path(SysUtils.ExcludeTrailingPathDelimiter(FileName.ToNative()));
end;

function TFileSystemImpl.FindFirst(const FilePattern: IPath; Attr: integer; var F: TSytemSearchRec): integer;
begin
  Result := SysUtils.FindFirst(FilePattern.ToNative(), Attr, F);
end;

function TFileSystemImpl.FindNext(var F: TSytemSearchRec): integer;
begin
  Result := SysUtils.FindNext(F);
end;

procedure TFileSystemImpl.FindClose(var F: TSytemSearchRec);
begin
  SysUtils.FindClose(F);
end;

function TFileSystemImpl.GetCurrentDir: IPath;
begin
  Result := Path(SysUtils.GetCurrentDir());
end;

function TFileSystemImpl.SetCurrentDir(const Dir: IPath): boolean;
begin
  Result := SysUtils.SetCurrentDir(Dir.ToNative());
end;

{$ENDIF}


{ TFileIterator }

constructor TFileIterator.Create(const FilePattern: IPath; Attr: integer);
begin
  inherited Create();
  fHasNext := (FileSystem.FindFirst(FilePattern, Attr, fSearchRec) = 0);
end;

destructor TFileIterator.Destroy();
begin
  FileSystem.FindClose(fSearchRec);
  inherited;
end;

function TFileIterator.HasNext(): boolean;
begin
  Result := fHasNext;
end;

function TFileIterator.Next(): TFileInfo;
begin
  if (not fHasNext) then
  begin
    // Note: do not use FillChar() on records with ref-counted fields
    Result.Time := 0;
    Result.Size := 0;
    Result.Attr := 0;
    Result.Name := nil;
    Exit;
  end;

  Result.Time := fSearchRec.Time;
  Result.Size := fSearchRec.Size;
  Result.Attr := fSearchRec.Attr;
  Result.Name := Path(fSearchRec.Name);

  // fetch next entry
  fHasNext := (FileSystem.FindNext(fSearchRec) = 0);
end;


initialization
  FileSystem_Singleton := TFileSystemImpl.Create;

finalization
  FileSystem_Singleton := nil;

end.