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