From 21c1082f916cc9a4d7be625c132e02b1fc1d8012 Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 23 Jul 2009 18:09:11 +0000 Subject: - IPath integration - BASS is now unicode compatible git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@1875 b956fd51-792f-4845-bead-9b4dfca2ff2c --- unicode/src/base/TextGL.pas | 22 +- unicode/src/base/UCatCovers.pas | 114 ++++---- unicode/src/base/UCommandLine.pas | 25 +- unicode/src/base/UCommon.pas | 82 +++--- unicode/src/base/UConfig.pas | 8 +- unicode/src/base/UCovers.pas | 51 ++-- unicode/src/base/UDLLManager.pas | 57 ++-- unicode/src/base/UDataBase.pas | 23 +- unicode/src/base/UFilename.pas | 464 --------------------------------- unicode/src/base/UFiles.pas | 142 +++++----- unicode/src/base/UFilesystem.pas | 2 +- unicode/src/base/UFont.pas | 47 ++-- unicode/src/base/UGraphic.pas | 22 +- unicode/src/base/UImage.pas | 63 +++-- unicode/src/base/UIni.pas | 179 ++++++------- unicode/src/base/ULanguage.pas | 31 ++- unicode/src/base/ULog.pas | 22 +- unicode/src/base/UMain.pas | 8 +- unicode/src/base/UMusic.pas | 53 ++-- unicode/src/base/UPath.pas | 260 +++++++++++++------ unicode/src/base/UPathUtils.pas | 120 +++++---- unicode/src/base/UPlatform.pas | 95 ++----- unicode/src/base/UPlatformLinux.pas | 98 ++----- unicode/src/base/UPlatformMacOSX.pas | 92 ++----- unicode/src/base/UPlatformWindows.pas | 125 ++------- unicode/src/base/UPlaylist.pas | 250 +++++++++--------- unicode/src/base/USkins.pas | 77 +++--- unicode/src/base/USong.pas | 291 ++++++++++++--------- unicode/src/base/USongs.pas | 68 ++--- unicode/src/base/UTextEncoding.pas | 474 +++++++++++++++++----------------- unicode/src/base/UTexture.pas | 45 ++-- unicode/src/base/UThemes.pas | 41 ++- unicode/src/base/UUnicodeUtils.pas | 10 + unicode/src/base/UXMLSong.pas | 86 +++--- 34 files changed, 1517 insertions(+), 2030 deletions(-) delete mode 100644 unicode/src/base/UFilename.pas (limited to 'unicode/src/base') diff --git a/unicode/src/base/TextGL.pas b/unicode/src/base/TextGL.pas index 65d716c2..7fe98d29 100644 --- a/unicode/src/base/TextGL.pas +++ b/unicode/src/base/TextGL.pas @@ -37,9 +37,10 @@ uses gl, glext, SDL, + Classes, UTexture, UFont, - Classes, + UPath, ULog; type @@ -75,26 +76,26 @@ uses UMain, UPathUtils; -function FindFontFile(FontIni: TCustomIniFile; Font: string): string; +function FindFontFile(FontIni: TCustomIniFile; Font: string): IPath; var - Filename: string; + Filename: IPath; begin - Filename := FontIni.ReadString(Font, 'File', ''); - Result := FontPath + Filename; + Filename := Path(FontIni.ReadString(Font, 'File', '')); + Result := FontPath.Append(Filename); // if path does not exist, try as an absolute path - if (not FileExists(Result)) then + if (not Result.IsFile) then Result := Filename; end; procedure BuildFont; var FontIni: TMemIniFile; - FontFile: string; + FontFile: IPath; begin ActFont := 0; SetLength(Fonts, 4); - FontIni := TMemIniFile.Create(FontPath + 'fonts.ini'); + FontIni := TMemIniFile.Create(FontPath.Append('fonts.ini').ToNative); try @@ -117,8 +118,9 @@ begin FontFile := FindFontFile(FontIni, 'Outline2'); Fonts[3].Font := TFTScalableOutlineFont.Create(FontFile, 64, 0.08); - except on E: Exception do - Log.LogCritical(E.Message, 'BuildFont'); + except + on E: Exception do + Log.LogCritical(E.Message, 'BuildFont'); end; // close ini-file diff --git a/unicode/src/base/UCatCovers.pas b/unicode/src/base/UCatCovers.pas index 86f675c5..6e004b22 100644 --- a/unicode/src/base/UCatCovers.pas +++ b/unicode/src/base/UCatCovers.pas @@ -38,20 +38,21 @@ interface {$I switches.inc} uses - UIni; + UIni, + UPath; type TCatCovers = class protected - cNames: array [0..high(ISorting)] of array of string; - cFiles: array [0..high(ISorting)] of array of string; + cNames: array [0..high(ISorting)] of array of UTF8String; + cFiles: array [0..high(ISorting)] of array of IPath; public constructor Create; procedure Load; //Load Cover aus Cover.ini and Cover Folder - procedure LoadPath(const CoversPath: string); - procedure Add(Sorting: integer; Name, Filename: string); //Add a Cover - function CoverExists(Sorting: integer; Name: string): boolean; //Returns True when a cover with the given Name exists - function GetCover(Sorting: integer; Name: string): string; //Returns the Filename of a Cover + procedure LoadPath(const CoversPath: IPath); + procedure Add(Sorting: integer; const Name: UTF8String; const Filename: IPath); //Add a Cover + function CoverExists(Sorting: integer; const Name: UTF8String): boolean; //Returns True when a cover with the given Name exists + function GetCover(Sorting: integer; const Name: UTF8String): IPath; //Returns the Filename of a Cover end; var @@ -63,9 +64,10 @@ uses IniFiles, SysUtils, Classes, - // UFiles, + UFilesystem, ULog, UMain, + UUnicodeUtils, UPathUtils; constructor TCatCovers.Create; @@ -79,25 +81,28 @@ var I: integer; begin for I := 0 to CoverPaths.Count-1 do - LoadPath(CoverPaths[I]); + LoadPath(CoverPaths[I] as IPath); end; (** * Load Cover from Cover.ini and Cover Folder *) -procedure TCatCovers.LoadPath(const CoversPath: string); +procedure TCatCovers.LoadPath(const CoversPath: IPath); var Ini: TMemIniFile; - SR: TSearchRec; List: TStringlist; I, J: Integer; - Name, Filename, Temp: string; + Filename: IPath; + Name, TmpName: UTF8String; + CatCover: IPath; + Iter: IFileIterator; + FileInfo: TFileInfo; begin Ini := nil; List := nil; try - Ini := TMemIniFile.Create(CoversPath + 'covers.ini'); + Ini := TMemIniFile.Create(CoversPath.Append('covers.ini').ToNative); List := TStringlist.Create; //Add every Cover in Covers Ini for Every Sorting option @@ -106,63 +111,65 @@ begin Ini.ReadSection(ISorting[I], List); for J := 0 to List.Count - 1 do - Add(I, List.Strings[J], CoversPath + Ini.ReadString(ISorting[I], List.Strings[J], 'NoCover.jpg')); + begin + CatCover := Path(Ini.ReadString(ISorting[I], List.Strings[J], 'NoCover.jpg')); + Add(I, List.Strings[J], CoversPath.Append(CatCover)); + end; end; finally Ini.Free; List.Free; end; - try - //Add Covers from Folder - if (FindFirst (CoversPath + '*.jpg', faAnyFile, SR) = 0) then - repeat - //Add Cover if it doesn't exist for every Section - Name := SR.Name; - Filename := CoversPath + Name; - Delete (Name, length(Name) - 3, 4); - - for I := 0 to high(ISorting) do - begin - Temp := Name; - if ((I = sTitle) or (I = sTitle2)) and (Pos ('Title', Temp) <> 0) then - Delete (Temp, Pos ('Title', Temp), 5) - else if (I = sArtist) or (I = sArtist2) and (Pos ('Artist', Temp) <> 0) then - Delete (Temp, Pos ('Artist', Temp), 6); - - if not CoverExists(I, Temp) then - Add (I, Temp, Filename); - end; - until FindNext (SR) <> 0; - finally - FindClose (SR); + //Add Covers from Folder + Iter := FileSystem.FileFind(CoversPath.Append('*.jpg'), 0); + while Iter.HasNext do + begin + FileInfo := Iter.Next; + + //Add Cover if it doesn't exist for every Section + Filename := CoversPath.Append(FileInfo.Name); + Name := FileInfo.Name.SetExtension('').ToUTF8; + + for I := 0 to high(ISorting) do + begin + TmpName := Name; + if ((I = sTitle) or (I = sTitle2)) and (UTF8Pos('Title', TmpName) <> 0) then + UTF8Delete(TmpName, UTF8Pos('Title', TmpName), 5) + else if (I = sArtist) or (I = sArtist2) and (UTF8Pos('Artist', TmpName) <> 0) then + UTF8Delete(TmpName, UTF8Pos('Artist', TmpName), 6); + + if not CoverExists(I, TmpName) then + Add(I, TmpName, Filename); + end; end; end; //Add a Cover -procedure TCatCovers.Add(Sorting: integer; Name, Filename: string); +procedure TCatCovers.Add(Sorting: integer; const Name: UTF8String; const Filename: IPath); begin - if FileExists (Filename) then //If Exists -> Add + if Filename.IsFile then //If Exists -> Add begin - SetLength (CNames[Sorting], Length(CNames[Sorting]) + 1); - SetLength (CFiles[Sorting], Length(CNames[Sorting]) + 1); + SetLength(CNames[Sorting], Length(CNames[Sorting]) + 1); + SetLength(CFiles[Sorting], Length(CNames[Sorting]) + 1); - CNames[Sorting][high(cNames[Sorting])] := Uppercase(Name); + CNames[Sorting][high(cNames[Sorting])] := UTF8Uppercase(Name); CFiles[Sorting][high(cNames[Sorting])] := FileName; end; end; //Returns True when a cover with the given Name exists -function TCatCovers.CoverExists(Sorting: integer; Name: string): boolean; +function TCatCovers.CoverExists(Sorting: integer; const Name: UTF8String): boolean; var I: Integer; + UpperName: UTF8String; begin Result := False; - Name := Uppercase(Name); //Case Insensitiv + UpperName := UTF8Uppercase(Name); //Case Insensitiv for I := 0 to high(cNames[Sorting]) do begin - if (cNames[Sorting][I] = Name) then //Found Name + if (cNames[Sorting][I] = UpperName) then //Found Name begin Result := true; break; //Break For Loop @@ -171,16 +178,18 @@ begin end; //Returns the Filename of a Cover -function TCatCovers.GetCover(Sorting: integer; Name: string): string; +function TCatCovers.GetCover(Sorting: integer; const Name: UTF8String): IPath; var I: Integer; + UpperName: UTF8String; + NoCoverPath: IPath; begin - Result := ''; - Name := Uppercase(Name); + Result := PATH_NONE; + UpperName := UTF8Uppercase(Name); for I := 0 to high(cNames[Sorting]) do begin - if cNames[Sorting][I] = Name then + if cNames[Sorting][I] = UpperName then begin Result := cFiles[Sorting][I]; Break; @@ -188,13 +197,14 @@ begin end; //No Cover - if (Result = '') then + if (Result.IsUnset) then begin for I := 0 to CoverPaths.Count-1 do begin - if (FileExists(CoverPaths[I] + 'NoCover.jpg')) then + NoCoverPath := (CoverPaths[I] as IPath).Append('NoCover.jpg'); + if (NoCoverPath.IsFile) then begin - Result := CoverPaths[I] + 'NoCover.jpg'; + Result := NoCoverPath; Break; end; end; diff --git a/unicode/src/base/UCommandLine.pas b/unicode/src/base/UCommandLine.pas index 281a480d..ac0db2c2 100644 --- a/unicode/src/base/UCommandLine.pas +++ b/unicode/src/base/UCommandLine.pas @@ -33,6 +33,9 @@ interface {$I switches.inc} +uses + UPath; + type TScreenMode = (scmDefault, scmFullscreen, scmWindowed); @@ -64,9 +67,9 @@ type Screens: integer; // some strings set when reading infos {Length=0: Not Set} - SongPath: string; - ConfigFile: string; - ScoreFile: string; + SongPath: IPath; + ConfigFile: IPath; + ScoreFile: IPath; // pseudo integer values property Language: integer read GetLanguage; @@ -144,9 +147,9 @@ begin Screens := -1; // some strings set when reading infos {Length=0 Not Set} - SongPath := ''; - ConfigFile := ''; - ScoreFile := ''; + SongPath := PATH_NONE; + ConfigFile := PATH_NONE; + ScoreFile := PATH_NONE; end; {** @@ -248,7 +251,7 @@ begin if (PCount > I) then begin // write value to string - SongPath := ParamStr(I + 1); + SongPath := Path(ParamStr(I + 1)); end; end @@ -258,11 +261,11 @@ begin if (PCount > I) then begin // write value to string - ConfigFile := ParamStr(I + 1); + ConfigFile := Path(ParamStr(I + 1)); // is this a relative path -> then add gamepath - if Not ((Length(ConfigFile) > 2) AND (ConfigFile[2] = ':')) then - ConfigFile := ExtractFilePath(ParamStr(0)) + Configfile; + if (not ConfigFile.IsAbsolute) then + ConfigFile := Platform.GetExecutionDir().Append(ConfigFile); end; end @@ -272,7 +275,7 @@ begin if (PCount > I) then begin // write value to string - ScoreFile := ParamStr(I + 1); + ScoreFile := Path(ParamStr(I + 1)); end; end; diff --git a/unicode/src/base/UCommon.pas b/unicode/src/base/UCommon.pas index d7c36196..c0a98815 100644 --- a/unicode/src/base/UCommon.pas +++ b/unicode/src/base/UCommon.pas @@ -41,7 +41,8 @@ uses {$ENDIF} sdl, UConfig, - ULog; + ULog, + UPath; type TMessageType = (mtInfo, mtError); @@ -65,8 +66,16 @@ procedure RestoreNumericLocale(); function MakeLong(a, b: word): longint; {$ENDIF} -function AdaptFilePaths(const aPath: widestring): widestring; -function FileExistsInsensitive(var FileName: string): boolean; +type + TDirectoryEntry = record + Name: IPath; + IsDirectory: boolean; + IsFile: boolean; + end; + + TDirectoryEntryArray = array of TDirectoryEntry; + +function DirectoryFindFiles(Dir: IPath; Filter: UTF8String; ReturnAllSubDirs: boolean): TDirectoryEntryArray; // A stable alternative to TList.Sort() (use TList.Sort() if applicable, see below) procedure MergeSort(List: TList; CompareFunc: TListSortCompare); @@ -82,6 +91,7 @@ uses {$IFDEF Delphi} Dialogs, {$ENDIF} + UFilesystem, UMain, UUnicodeUtils; @@ -206,12 +216,6 @@ begin exOverflow, exUnderflow, exPrecision]); end; -function AdaptFilePaths(const aPath: WideString): WideString; -begin - result := StringReplaceW(aPath, '\', PathDelim);//, [rfReplaceAll]); -end; - - {$IFNDEF MSWINDOWS} procedure ZeroMemory(Destination: pointer; Length: dword); begin @@ -225,42 +229,44 @@ end; {$ENDIF} -{ TODO: REMOVE } -// Checks if a regular files or directory with the given name exists. -// The comparison is case insensitive. -function FileExistsInsensitive(var FileName: string): boolean; +function DirectoryFindFiles(Dir: IPath; Filter: UTF8String; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; var - FilePath, LocalFileName: string; - SearchInfo: TSearchRec; + i: integer; + Iter: IFileIterator; + FileInfo: TFileInfo; + FileName: IPath; + Attrib: integer; begin -{$IF Defined(Linux) or Defined(FreeBSD)} - // speed up standard case - if FileExists(FileName) then - begin - Result := true; - exit; - end; + i := 0; + Filter := UTF8LowerCase(Filter); - Result := false; - - FilePath := ExtractFilePath(FileName); - if (FindFirst(FilePath + '*', faAnyFile, SearchInfo) = 0) then + // search for all files and directories + Iter := FileSystem.FileFind(Dir.Append('*'), faAnyFile); + while (Iter.HasNext) do begin - LocalFileName := ExtractFileName(FileName); - repeat - if (AnsiSameText(LocalFileName, SearchInfo.Name)) then + FileInfo := Iter.Next; + FileName := FileInfo.Name; + if (not FileName.Equals('.')) and (not FileName.Equals('..')) then + begin + Attrib := Dir.Append(FileName).GetAttr(); + if ReturnAllSubDirs and ((Attrib and faDirectory) <> 0) then begin - FileName := FilePath + SearchInfo.Name; - Result := true; - break; + SetLength(Result, i + 1); + Result[i].Name := FileName; + Result[i].IsDirectory := true; + Result[i].IsFile := false; + i := i + 1; + end + else if (Filter = '') or (Pos(Filter, LowerCase(FileName.ToUTF8)) > 0) then + begin + SetLength(Result, i + 1); + Result[i].Name := FileName; + Result[i].IsDirectory := false; + Result[i].IsFile := true; + i := i + 1; end; - until (FindNext(SearchInfo) <> 0); + end; end; - FindClose(SearchInfo); -{$ELSE} - // Windows and Mac OS X do not have case sensitive file systems - Result := FileExists(FileName); -{$IFEND} end; // +++++++++++++++++++++ helpers for RWOpsFromStream() +++++++++++++++ diff --git a/unicode/src/base/UConfig.pas b/unicode/src/base/UConfig.pas index 1214f36f..f6dc69a5 100644 --- a/unicode/src/base/UConfig.pas +++ b/unicode/src/base/UConfig.pas @@ -90,7 +90,7 @@ interface {$I switches.inc} uses - Sysutils; + SysUtils; const // IMPORTANT: @@ -156,6 +156,12 @@ const (FPC_RELEASE * VERSION_MINOR) + (FPC_PATCH * VERSION_RELEASE); + // FPC 2.2.0 unicode support is very buggy. The cwstring unit for example + // always crashes whenever UTF8ToAnsi() is called on a non UTF8 encoded string + // what is fixed in 2.2.2. + {$IF Defined(FPC) and (FPC_VERSION_INT < 2002002)} // < 2.2.2 + {$MESSAGE FATAL 'FPC >= 2.2.2 required!'} + {$IFEND} {$IFDEF HaveFFmpeg} diff --git a/unicode/src/base/UCovers.pas b/unicode/src/base/UCovers.pas index 8e7934b2..0dbe672a 100644 --- a/unicode/src/base/UCovers.pas +++ b/unicode/src/base/UCovers.pas @@ -50,7 +50,8 @@ uses SysUtils, Classes, UImage, - UTexture; + UTexture, + UPath; type ECoverDBException = class(Exception) @@ -59,9 +60,9 @@ type TCover = class private ID: int64; - Filename: WideString; + Filename: IPath; public - constructor Create(ID: int64; Filename: WideString); + constructor Create(ID: int64; Filename: IPath); function GetPreviewTexture(): TTexture; function GetTexture(): TTexture; end; @@ -76,19 +77,19 @@ type private DB: TSQLiteDatabase; procedure InitCoverDatabase(); - function CreateThumbnail(const Filename: WideString; var Info: TThumbnailInfo): PSDL_Surface; + function CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface; function LoadCover(CoverID: int64): TTexture; procedure DeleteCover(CoverID: int64); - function FindCoverIntern(const Filename: WideString): int64; + function FindCoverIntern(const Filename: IPath): int64; procedure Open(); function GetVersion(): integer; procedure SetVersion(Version: integer); public constructor Create(); destructor Destroy; override; - function AddCover(const Filename: WideString): TCover; - function FindCover(const Filename: WideString): TCover; - function CoverExists(const Filename: WideString): boolean; + function AddCover(const Filename: IPath): TCover; + function FindCover(const Filename: IPath): TCover; + function CoverExists(const Filename: IPath): boolean; function GetMaxCoverSize(): integer; procedure SetMaxCoverSize(Size: integer); end; @@ -141,7 +142,7 @@ end; { TCover } -constructor TCover.Create(ID: int64; Filename: WideString); +constructor TCover.Create(ID: int64; Filename: IPath); begin Self.ID := ID; Self.Filename := Filename; @@ -210,11 +211,11 @@ end; procedure TCoverDatabase.Open(); var Version: integer; - Filename: UTF8String; + Filename: IPath; begin - Filename := Platform.GetGameUserPath() + COVERDB_FILENAME; + Filename := Platform.GetGameUserPath().Append(COVERDB_FILENAME); - DB := TSQLiteDatabase.Create(Filename); + DB := TSQLiteDatabase.Create(Filename.ToUTF8()); Version := GetVersion(); // check version, if version is too old/new, delete database file @@ -223,10 +224,10 @@ begin Log.LogInfo('Outdated cover-database file found', 'TCoverDatabase.Open'); // close and delete outdated file DB.Free; - if (not DeleteFile(Filename)) then - raise ECoverDBException.Create('Could not delete ' + Filename); + if (not Filename.DeleteFile()) then + raise ECoverDBException.Create('Could not delete ' + Filename.ToNative); // reopen - DB := TSQLiteDatabase.Create(Filename); + DB := TSQLiteDatabase.Create(Filename.ToUTF8()); Version := 0; end; @@ -266,14 +267,14 @@ begin ')'); end; -function TCoverDatabase.FindCoverIntern(const Filename: WideString): int64; +function TCoverDatabase.FindCoverIntern(const Filename: IPath): int64; begin Result := DB.GetTableValue('SELECT [ID] FROM ['+COVER_TBL+'] ' + 'WHERE [Filename] = ?', - [UTF8Encode(Filename)]); + [Filename.ToUTF8]); end; -function TCoverDatabase.FindCover(const Filename: WideString): TCover; +function TCoverDatabase.FindCover(const Filename: IPath): TCover; var CoverID: int64; begin @@ -287,7 +288,7 @@ begin end; end; -function TCoverDatabase.CoverExists(const Filename: WideString): boolean; +function TCoverDatabase.CoverExists(const Filename: IPath): boolean; begin Result := false; try @@ -297,7 +298,7 @@ begin end; end; -function TCoverDatabase.AddCover(const Filename: WideString): TCover; +function TCoverDatabase.AddCover(const Filename: IPath): TCover; var CoverID: int64; Thumbnail: PSDL_Surface; @@ -329,7 +330,7 @@ begin DB.ExecSQL('INSERT INTO ['+COVER_TBL+'] ' + '([Filename], [Date], [Width], [Height]) VALUES' + '(?, ?, ?, ?)', - [UTF8Encode(Filename), DateTimeToUnixTime(FileDate), + [Filename.ToUTF8, DateTimeToUnixTime(FileDate), Info.CoverWidth, Info.CoverHeight]); // get auto-generated cover ID @@ -358,7 +359,7 @@ var PixelFmt: TImagePixelFmt; Data: PChar; DataSize: integer; - Filename: WideString; + Filename: IPath; Table: TSQLiteUniTable; begin Table := nil; @@ -371,7 +372,7 @@ begin 'USING(ID) ' + 'WHERE [ID] = %d', [CoverID])); - Filename := UTF8Decode(Table.FieldAsString(0)); + Filename := Path(Table.FieldAsString(0)); PixelFmt := TImagePixelFmt(Table.FieldAsInteger(1)); Width := Table.FieldAsInteger(2); Height := Table.FieldAsInteger(3); @@ -403,7 +404,7 @@ end; * Returns a pointer to an array of bytes containing the texture data in the * requested size *) -function TCoverDatabase.CreateThumbnail(const Filename: WideString; var Info: TThumbnailInfo): PSDL_Surface; +function TCoverDatabase.CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface; var //TargetAspect, SourceAspect: double; //TargetWidth, TargetHeight: integer; @@ -417,7 +418,7 @@ begin Thumbnail := LoadImage(Filename); if (not assigned(Thumbnail)) then begin - Log.LogError('Could not load cover: "'+ Filename +'"', 'TCoverDatabase.AddCover'); + Log.LogError('Could not load cover: "'+ Filename.ToNative +'"', 'TCoverDatabase.AddCover'); Exit; end; diff --git a/unicode/src/base/UDLLManager.pas b/unicode/src/base/UDLLManager.pas index dd05d733..d5bb1480 100644 --- a/unicode/src/base/UDLLManager.pas +++ b/unicode/src/base/UDLLManager.pas @@ -35,7 +35,9 @@ interface uses ModiSDK, - UFiles; + UFiles, + UPath, + UFilesystem; type TDLLMan = class @@ -47,14 +49,14 @@ type P_RData: pModi_RData; public Plugins: array of TPluginInfo; - PluginPaths: array of string; + PluginPaths: array of IPath; Selected: ^TPluginInfo; constructor Create; procedure GetPluginList; procedure ClearPluginInfo(No: cardinal); - function LoadPluginInfo(Filename: string; No: cardinal): boolean; + function LoadPluginInfo(const Filename: IPath; No: cardinal): boolean; function LoadPlugin(No: cardinal): boolean; procedure UnLoadPlugin; @@ -107,27 +109,26 @@ end; procedure TDLLMan.GetPluginList; var - SearchRecord: TSearchRec; + Iter: IFileIterator; + FileInfo: TFileInfo; begin - - if FindFirst(PluginPath + '*' + DLLExt, faAnyFile, SearchRecord) = 0 then + Iter := FileSystem.FileFind(PluginPath.Append('*' + DLLExt), 0); + while (Iter.HasNext) do begin - repeat - SetLength(Plugins, Length(Plugins)+1); - SetLength(PluginPaths, Length(Plugins)); + SetLength(Plugins, Length(Plugins)+1); + SetLength(PluginPaths, Length(Plugins)); - if LoadPluginInfo(SearchRecord.Name, High(Plugins)) then // loaded succesful - begin - PluginPaths[High(PluginPaths)] := SearchRecord.Name; - end - else // error loading - begin - SetLength(Plugins, Length(Plugins)-1); - SetLength(PluginPaths, Length(Plugins)); - end; - - until FindNext(SearchRecord) <> 0; - FindClose(SearchRecord); + FileInfo := Iter.Next; + + if LoadPluginInfo(FileInfo.Name, High(Plugins)) then // loaded succesful + begin + PluginPaths[High(PluginPaths)] := FileInfo.Name; + end + else // error loading + begin + SetLength(Plugins, Length(Plugins)-1); + SetLength(PluginPaths, Length(Plugins)); + end; end; end; @@ -164,7 +165,7 @@ begin Plugins[No].EnLineBonus_O := true; end; -function TDLLMan.LoadPluginInfo(Filename: string; No: cardinal): boolean; +function TDLLMan.LoadPluginInfo(const Filename: IPath; No: cardinal): boolean; var hLibg: THandle; Info: pModi_PluginInfo; @@ -182,7 +183,7 @@ begin } // load libary - hLibg := LoadLibrary(PChar(PluginPath + Filename)); + hLibg := LoadLibrary(PChar(PluginPath.Append(Filename).ToNative)); // if loaded if (hLibg <> 0) then begin @@ -197,19 +198,19 @@ begin Result := true; end else - Log.LogError('Could not load plugin "' + Filename + '": Info procedure not found'); + Log.LogError('Could not load plugin "' + Filename.ToNative + '": Info procedure not found'); FreeLibrary (hLibg); end else - Log.LogError('Could not load plugin "' + Filename + '": Libary not loaded'); + Log.LogError('Could not load plugin "' + Filename.ToNative + '": Libary not loaded'); end; function TDLLMan.LoadPlugin(No: cardinal): boolean; begin Result := true; // load libary - hLib := LoadLibrary(PChar(PluginPath + PluginPaths[No])); + hLib := LoadLibrary(PChar(PluginPath.Append(PluginPaths[No]).ToNative)); // if loaded if (hLib <> 0) then begin @@ -226,11 +227,11 @@ begin end else begin - Log.LogError('Could not load plugin "' + PluginPaths[No] + '": Procedures not found'); + Log.LogError('Could not load plugin "' + PluginPaths[No].ToNative + '": Procedures not found'); end; end else - Log.LogError('Could not load plugin "' + PluginPaths[No] + '": Libary not loaded'); + Log.LogError('Could not load plugin "' + PluginPaths[No].ToNative + '": Libary not loaded'); end; procedure TDLLMan.UnLoadPlugin; diff --git a/unicode/src/base/UDataBase.pas b/unicode/src/base/UDataBase.pas index 87e9519c..90cee974 100644 --- a/unicode/src/base/UDataBase.pas +++ b/unicode/src/base/UDataBase.pas @@ -34,10 +34,11 @@ interface {$I switches.inc} uses + Classes, + SQLiteTable3, USongs, USong, - Classes, - SQLiteTable3; + UPath; //-------------------- //DataBaseSystem - Class including all DB Methods @@ -88,16 +89,16 @@ type TDataBaseSystem = class private ScoreDB: TSQLiteDatabase; - fFilename: string; + fFilename: IPath; function GetVersion(): integer; procedure SetVersion(Version: integer); public - property Filename: string read fFilename; + property Filename: IPath read fFilename; destructor Destroy; override; - procedure Init(const Filename: string); + procedure Init(const Filename: IPath); procedure ReadScore(Song: TSong); procedure AddScore(Song: TSong; Level: integer; const Name: UTF8String; Score: integer); procedure WriteScore(Song: TSong); @@ -128,19 +129,19 @@ const (** * Opens Database and Create Tables if not Exist *) -procedure TDataBaseSystem.Init(const Filename: string); +procedure TDataBaseSystem.Init(const Filename: IPath); var Version: integer; begin if Assigned(ScoreDB) then Exit; - Log.LogStatus('Initializing database: "'+Filename+'"', 'TDataBaseSystem.Init'); + Log.LogStatus('Initializing database: "'+Filename.ToNative+'"', 'TDataBaseSystem.Init'); try // Open Database - ScoreDB := TSQLiteDatabase.Create(Filename); + ScoreDB := TSQLiteDatabase.Create(Filename.ToUTF8); fFilename := Filename; // Close and delete outdated file @@ -150,10 +151,10 @@ begin Log.LogInfo('Outdated cover-database file found', 'TDataBaseSystem.Init'); // Close and delete outdated file ScoreDB.Free; - if (not DeleteFile(Filename)) then - raise Exception.Create('Could not delete ' + Filename); + if (not Filename.DeleteFile()) then + raise Exception.Create('Could not delete ' + Filename.ToNative); // Reopen - ScoreDB := TSQLiteDatabase.Create(Filename); + ScoreDB := TSQLiteDatabase.Create(Filename.ToUTF8); Version := 0; end; diff --git a/unicode/src/base/UFilename.pas b/unicode/src/base/UFilename.pas deleted file mode 100644 index c2ccaec8..00000000 --- a/unicode/src/base/UFilename.pas +++ /dev/null @@ -1,464 +0,0 @@ -{* 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 UFilename; - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - -interface - -uses - {$IFDEF MSWINDOWS} - TntClasses, - {$ENDIF} - Classes; - -type - TPathEncoding = ( - // pencLossless, - pencSystemUTF8, // use system encoding. Mac: UTF8, Linux: None, Win: UTF8 - // Note: - // - On windows, this format is lossless but is not - // supported direclty (UTF-16 only). - // - This is the best format to store filenames as there is - // no dataloss. - // - Do not use with glPrint() or other display functions - // as the resulting string is neither guaranteed to be - // UTF-8 nor ANSI. - - pencSystemANSI, // use system encoding. Mac: UTF8, Linux: None, Win: ANSI - // Note: - // - On windows, the ANSI format may break filenames - // that are not encoded in the active codepage. - // - Use only if there is no equivalent WideString - // filesystem function. - // - Do not use with glPrint() or other display functions - // as the resulting string is neither guaranteed to be - // UTF-8 nor ANSI. - - // pencPrintable, - pencUTF8 // tries to present the filename as UTF-8 encoded string - // that can be used for printing etc. - // Note: - // - Some characters whose encodings are unknown are - // represented either by '?' or as the representative of - // a different encoding. - // - On linux not all filenames can be represented - // correclty in UTF-8 as it does not have filename - // encodings (though most filenames are stored in UTF-8). - // - Never use the result to store filenames or open files - // as the filenames may break due to conversion. - ); - - IPath = interface; - - {$IFDEF MSWINDOWS} - TUniFileStream = class(TTntFileStream) - {$ELSE} - TUniFileStream = class(TFileStream) - {$ENDIF} - public - constructor Create(const FileName: IPath; Mode: Word); - end; - - TUniMemoryStream = class(TMemoryStream) - public - procedure LoadFromFile(const FileName: IPath); - procedure SaveToFile(const FileName: IPath); - end; - - IPath = interface - function ToString(Encoding: TPathEncoding = pencSystemUTF8): AnsiString; - function ToWideString(): WideString; - - function Adjust(): boolean; - - function Expand(): IPath; - {** File must be closed with FileClose(Handle) after usage } - function CreateFile(): integer; - function CreateDir(): boolean; - {** File must be closed with FileClose(Handle) after usage } - function Open(Mode: LongWord): integer; - {** Stream must be freed with TUniFileStream.Free after usage } - function OpenStream(Mode: Word): TUniFileStream; - function GetFileAge(): integer; overload; - function GetFileAge(out FileDateTime: TDateTime): boolean; overload; - function Exists(): boolean; - function IsFile(): boolean; - function IsDirectory(): boolean; - function GetAttr(): cardinal; - function SetAttr(Attr: Integer): boolean; - function IsReadOnly(): boolean; - function SetReadOnly(ReadOnly: boolean): boolean; - function ForceDirectories(): boolean; //- - function FileSearch(const DirList: IPath): IPath; //- - function Rename(const NewName: IPath): boolean; - function DeleteFile(): boolean; - function DeleteEmptyDir(): boolean; - function CopyFile(const Target: IPath; FailIfExists: boolean): boolean; - end; - -function Path(const Name: AnsiString; Encoding: TPathEncoding = pencSystemANSI): IPath; overload; -function Path(const Name: WideString): IPath; overload; -function Path(PathComponents: array of const): IPath; overload; - -implementation - -uses - UFilesystem, - SysUtils; - -type - TPathImpl = class(TInterfacedObject, IPath) - private - fName: AnsiString; - public - constructor Create(const Name: string); - destructor Destroy(); override; - - function ToString(Encoding: TPathEncoding = pencSystemANSI): AnsiString; - function ToWideString(): WideString; - - //function Append(const IPath): IPath; - function Adjust(): boolean; - {** - * Removes trailing path-delimiter and replaces path-delimiters with '/'. - *} - procedure Unify(); - function Expand(): IPath; - - (* - function IncludeTrailingPathDelimiter(): IPath; - function ExcludeTrailingPathDelimiter(): IPath; - function ChangeFileExt(const Extension: IPath): IPath; - function ExtractFilePath(): IPath; - function ExtractFileDir(): IPath; - function ExtractFileDrive(): IPath; - function ExtractFileName(): IPath; - function ExtractFileExt(): IPath; - function ExtractRelativePath(const BaseName: IPath): IPath; - *) - - function CreateFile(): integer; - function CreateDir(): boolean; - function Open(Mode: LongWord): integer; - function OpenStream(Mode: Word): TUniFileStream; - function GetFileAge(): integer; overload; - function GetFileAge(out FileDateTime: TDateTime): boolean; overload; - function Exists(): boolean; - function IsFile(): boolean; - function IsDirectory(): boolean; - function GetAttr(): cardinal; - function SetAttr(Attr: Integer): boolean; - function IsReadOnly(): boolean; - function SetReadOnly(ReadOnly: boolean): boolean; - function ForceDirectories(): boolean; - function FileSearch(const DirList: IPath): IPath; - function Rename(const NewName: IPath): boolean; - function DeleteFile(): boolean; - function DeleteEmptyDir(): boolean; - function CopyFile(const Target: IPath; FailIfExists: boolean): boolean; - end; - - IPathIterator = interface - function Next(): IPath; - end; - - TPathIteratorImpl = class(TInterfacedObject, IPathIterator) - private - fPath: IPath; - fPos: integer; - public - constructor Create(Path: IPath); - - function Next(): IPath; - end; - -function Path(const Name: AnsiString; Encoding: TPathEncoding): IPath; -begin - {$IFDEF MSWINDOWS} - case Encoding of - pencUTF8: - // TODO: Check if UTF8 - Result := TPathImpl.Create(Name); - pencSystemANSI: - Result := TPathImpl.Create(AnsiToUtf8(Name)); - else - raise Exception.Create('Unhandled encoding'); - end; - {$ELSE} - Result := TPathImpl.Create(Name); - {$ENDIF} -end; - -function Path(const Name: WideString): IPath; -begin - Result := TPathImpl.Create(UTF8Encode(Name)); -end; - -function Path(PathComponents: array of const): IPath; -var - CompIndex: integer; - Name: string; -begin - Name := ''; - for CompIndex := 0 to High(PathComponents) do - begin - with PathComponents[CompIndex] do - begin - case (VType) of - vtString: - Name := Name + VString^; - vtAnsiString: - Name := Name + AnsiString(VAnsiString); - vtWideString: - Name := Name + UTF8Encode(WideString(VWideString)); - vtObject: - raise Exception.Create('Unhandled Object type'); - else - raise Exception.Create('Unhandled Path type'); - end; - end; - Name := Name + PathDelim; - end; - Result := Path(Name); -end; - -constructor TPathImpl.Create(const Name: string); -begin - inherited Create(); - fName := Name; - Unify(); -end; - -destructor TPathImpl.Destroy(); -begin - inherited; -end; - -procedure TPathImpl.Unify(); -var - I: integer; -begin - for I := 1 to Length(fName) do - begin - if (fName[I] in ['\', '/']) then - fName[I] := PathDelim; - end; - fName := ExcludeTrailingPathDelimiter(fName); -end; - -function TPathImpl.ToString(Encoding: TPathEncoding): AnsiString; -begin - {$IFDEF MSWINDOWS} - case Encoding of - pencUTF8: - Result := fName; - pencSystemANSI: - Result := Utf8ToAnsi(fName); - else - raise Exception.Create('Unhandled encoding'); - end; - {$ELSE} - Result := fName; - {$ENDIF} -end; - -function TPathImpl.ToWideString(): WideString; -begin - Result := UTF8Decode(fName); -end; - -function TPathImpl.Adjust(): boolean; -begin - Result := false; //TODO -end; - - - -function TPathImpl.Expand(): IPath; -begin - Result := FileSystem.ExpandFileName(Self); -end; - -function TPathImpl.CreateFile(): integer; -begin - Result := FileSystem.FileCreate(Self); -end; - -function TPathImpl.CreateDir(): boolean; -begin - Result := FileSystem.DirectoryCreate(Self); -end; - -function TPathImpl.Open(Mode: LongWord): integer; -begin - Result := FileSystem.FileOpen(Self, Mode); -end; - -function TPathImpl.OpenStream(Mode: Word): TUniFileStream; -begin - Result := TUniFileStream.Create(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 - Result := IsFile() or IsDirectory(); -end; - -function TPathImpl.IsFile(): boolean; -begin - Result := FileSystem.FileExists(Self); -end; - -function TPathImpl.IsDirectory(): boolean; -begin - Result := FileSystem.DirectoryExists(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.ForceDirectories(): boolean; -begin - Result := FileSystem.ForceDirectories(Self); -end; - -function TPathImpl.FileSearch(const DirList: IPath): IPath; -begin - 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; - - - -{ TPathIteratorImpl } - -constructor TPathIteratorImpl.Create(Path: IPath); -begin - fPath := Path; - fPos := -1; -end; - -function TPathIteratorImpl.Next(): IPath; -begin -end; - - -{ TUniFileStream } - -{$IFDEF MSWINDOWS} - -constructor TUniFileStream.Create(const FileName: IPath; Mode: Word); -begin - inherited Create(FileName.ToWideString(), Mode); -end; - -{$ELSE} - -constructor TUniFileStream.Create(const FileName: IPath; Mode: Word); -begin - inherited Create(FileName.ToSystemString(), Mode); -end; - -{$ENDIF} - -{ TUniMemoryStream } - -procedure TUniMemoryStream.LoadFromFile(const FileName: IPath); -var - Stream: TStream; -begin - Stream := TUniFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); - try - LoadFromStream(Stream); - finally - Stream.Free; - end; -end; - -procedure TUniMemoryStream.SaveToFile(const FileName: IPath); -var - Stream: TStream; -begin - Stream := TUniFileStream.Create(FileName, fmCreate); - try - SaveToStream(Stream); - finally - Stream.Free; - end; -end; - -end. diff --git a/unicode/src/base/UFiles.pas b/unicode/src/base/UFiles.pas index bfcd63d2..9996eeb1 100644 --- a/unicode/src/base/UFiles.pas +++ b/unicode/src/base/UFiles.pas @@ -34,10 +34,12 @@ interface uses SysUtils, + Classes, ULog, UMusic, USongs, - USong; + USong, + UPath; procedure ResetSingTemp; @@ -48,7 +50,7 @@ type * Throws a TEncodingException if the song's fields cannot be encoded in the * requested encoding. *} -function SaveSong(const Song: TSong; const Lines: TLines; const Name: string; Relative: boolean): TSaveSongResult; +function SaveSong(const Song: TSong; const Lines: TLines; const Name: IPath; Relative: boolean): TSaveSongResult; implementation @@ -81,7 +83,7 @@ end; //-------------------- // Saves a Song //-------------------- -function SaveSong(const Song: TSong; const Lines: TLines; const Name: string; Relative: boolean): TSaveSongResult; +function SaveSong(const Song: TSong; const Lines: TLines; const Name: IPath; Relative: boolean): TSaveSongResult; var C: integer; N: integer; @@ -89,9 +91,9 @@ var B: integer; RelativeSubTime: integer; NoteState: AnsiString; - SongFile: TextFile; + SongFile: TTextFileStream; - function EncodeToken(const Str: UTF8String): AnsiString; + function EncodeToken(const Str: UTF8String): RawByteString; var Success: boolean; begin @@ -100,89 +102,87 @@ var SaveSong := ssrEncodingError; end; - function EncodeFilename(const Filename: WideString): AnsiString; - begin - // TODO: Unicode - Result := Filename; - end; - begin // Relative := true; // override (idea - use shift+S to save with relative) - AssignFile(SongFile, Name); - Rewrite(SongFile); - Result := ssrOK; + SongFile := nil; - if (Song.Encoding = encUTF8) then - Write(SongFile, UTF8_BOM); + try + SongFile := TTextFileStream.Create(Name, fmCreate); - Writeln(SongFile, '#ENCODING:' + EncodingName(Song.Encoding)); - Writeln(SongFile, '#TITLE:' + EncodeToken(Song.Title)); - Writeln(SongFile, '#ARTIST:' + EncodeToken(Song.Artist)); + if (Song.Encoding = encUTF8) then + SongFile.Write(UTF8_BOM); - if Song.Creator <> '' then Writeln(SongFile, '#CREATOR:' + EncodeToken(Song.Creator)); - if Song.Edition <> 'Unknown' then Writeln(SongFile, '#EDITION:' + EncodeToken(Song.Edition)); - if Song.Genre <> 'Unknown' then Writeln(SongFile, '#GENRE:' + EncodeToken(Song.Genre)); - if Song.Language <> 'Unknown' then Writeln(SongFile, '#LANGUAGE:' + EncodeToken(Song.Language)); + SongFile.WriteLine('#ENCODING:' + EncodingName(Song.Encoding)); + SongFile.WriteLine('#TITLE:' + EncodeToken(Song.Title)); + SongFile.WriteLine('#ARTIST:' + EncodeToken(Song.Artist)); - Writeln(SongFile, '#MP3:' + EncodeFilename(Song.Mp3)); - if Song.Cover <> '' then Writeln(SongFile, '#COVER:' + EncodeFilename(Song.Cover)); - if Song.Background <> '' then Writeln(SongFile, '#BACKGROUND:' + EncodeFilename(Song.Background)); - if Song.Video <> '' then Writeln(SongFile, '#VIDEO:' + EncodeFilename(Song.Video)); + if Song.Creator <> '' then SongFile.WriteLine('#CREATOR:' + EncodeToken(Song.Creator)); + if Song.Edition <> 'Unknown' then SongFile.WriteLine('#EDITION:' + EncodeToken(Song.Edition)); + if Song.Genre <> 'Unknown' then SongFile.WriteLine('#GENRE:' + EncodeToken(Song.Genre)); + if Song.Language <> 'Unknown' then SongFile.WriteLine('#LANGUAGE:' + EncodeToken(Song.Language)); - if Song.VideoGAP <> 0 then Writeln(SongFile, '#VIDEOGAP:' + FloatToStr(Song.VideoGAP)); - if Song.Resolution <> 4 then Writeln(SongFile, '#RESOLUTION:' + IntToStr(Song.Resolution)); - if Song.NotesGAP <> 0 then Writeln(SongFile, '#NOTESGAP:' + IntToStr(Song.NotesGAP)); - if Song.Start <> 0 then Writeln(SongFile, '#START:' + FloatToStr(Song.Start)); - if Song.Finish <> 0 then Writeln(SongFile, '#END:' + IntToStr(Song.Finish)); - if Relative then Writeln(SongFile, '#RELATIVE:yes'); + SongFile.WriteLine('#MP3:' + EncodeToken(Song.Mp3.ToUTF8)); + if Song.Cover.IsSet then SongFile.WriteLine('#COVER:' + EncodeToken(Song.Cover.ToUTF8)); + if Song.Background.IsSet then SongFile.WriteLine('#BACKGROUND:' + EncodeToken(Song.Background.ToUTF8)); + if Song.Video.IsSet then SongFile.WriteLine('#VIDEO:' + EncodeToken(Song.Video.ToUTF8)); - Writeln(SongFile, '#BPM:' + FloatToStr(Song.BPM[0].BPM / 4)); - Writeln(SongFile, '#GAP:' + FloatToStr(Song.GAP)); + if Song.VideoGAP <> 0 then SongFile.WriteLine('#VIDEOGAP:' + FloatToStr(Song.VideoGAP)); + if Song.Resolution <> 4 then SongFile.WriteLine('#RESOLUTION:' + IntToStr(Song.Resolution)); + if Song.NotesGAP <> 0 then SongFile.WriteLine('#NOTESGAP:' + IntToStr(Song.NotesGAP)); + if Song.Start <> 0 then SongFile.WriteLine('#START:' + FloatToStr(Song.Start)); + if Song.Finish <> 0 then SongFile.WriteLine('#END:' + IntToStr(Song.Finish)); + if Relative then SongFile.WriteLine('#RELATIVE:yes'); - RelativeSubTime := 0; - for B := 1 to High(Song.BPM) do - Writeln(SongFile, 'B ' + FloatToStr(Song.BPM[B].StartBeat) + ' ' - + FloatToStr(Song.BPM[B].BPM/4)); + SongFile.WriteLine('#BPM:' + FloatToStr(Song.BPM[0].BPM / 4)); + SongFile.WriteLine('#GAP:' + FloatToStr(Song.GAP)); - for C := 0 to Lines.High do - begin - for N := 0 to Lines.Line[C].HighNote do + RelativeSubTime := 0; + for B := 1 to High(Song.BPM) do + SongFile.WriteLine('B ' + FloatToStr(Song.BPM[B].StartBeat) + ' ' + + FloatToStr(Song.BPM[B].BPM/4)); + + for C := 0 to Lines.High do begin - with Lines.Line[C].Note[N] do + for N := 0 to Lines.Line[C].HighNote do begin - //Golden + Freestyle Note Patch - case Lines.Line[C].Note[N].NoteType of - ntFreestyle: NoteState := 'F '; - ntNormal: NoteState := ': '; - ntGolden: NoteState := '* '; - end; // case - S := NoteState + IntToStr(Start-RelativeSubTime) + ' ' - + IntToStr(Length) + ' ' - + IntToStr(Tone) + ' ' - + EncodeToken(Text); - - Writeln(SongFile, S); - end; // with - end; // N - - if C < Lines.High then // don't write end of last sentence - begin - if not Relative then - S := '- ' + IntToStr(Lines.Line[C+1].Start) - else + with Lines.Line[C].Note[N] do + begin + //Golden + Freestyle Note Patch + case Lines.Line[C].Note[N].NoteType of + ntFreestyle: NoteState := 'F '; + ntNormal: NoteState := ': '; + ntGolden: NoteState := '* '; + end; // case + S := NoteState + IntToStr(Start-RelativeSubTime) + ' ' + + IntToStr(Length) + ' ' + + IntToStr(Tone) + ' ' + + EncodeToken(Text); + + SongFile.WriteLine(S); + end; // with + end; // N + + if C < Lines.High then // don't write end of last sentence begin - S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) + - ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime); - RelativeSubTime := Lines.Line[C+1].Start; + if not Relative then + S := '- ' + IntToStr(Lines.Line[C+1].Start) + else + begin + S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) + + ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime); + RelativeSubTime := Lines.Line[C+1].Start; + end; + SongFile.WriteLine(S); end; - Writeln(SongFile, S); - end; - end; // C + end; // C - Writeln(SongFile, 'E'); + SongFile.WriteLine('E'); + except + Result := ssrFileError; + end; - CloseFile(SongFile); + SongFile.Free; end; end. diff --git a/unicode/src/base/UFilesystem.pas b/unicode/src/base/UFilesystem.pas index 480d3376..1cfb6837 100644 --- a/unicode/src/base/UFilesystem.pas +++ b/unicode/src/base/UFilesystem.pas @@ -223,7 +223,7 @@ end; function TFileSystemImpl.CreateFileStream(const FileName: IPath; Mode: Word): THandleStream; begin - Result := TUnicodeFileStream.Create(FileName, Mode); + Result := TBinaryFileStream.Create(FileName, Mode); end; function TFileSystemImpl.FileFind(const FilePattern: IPath; Attr: Integer): IFileIterator; diff --git a/unicode/src/base/UFont.pas b/unicode/src/base/UFont.pas index ce52ed96..d91dca59 100644 --- a/unicode/src/base/UFont.pas +++ b/unicode/src/base/UFont.pas @@ -47,13 +47,14 @@ uses glext, glu, sdl, + Math, + Classes, + SysUtils, UUnicodeUtils, {$IFDEF BITMAP_FONT} UTexture, {$ENDIF} - Math, - Classes, - SysUtils; + UPath; type @@ -506,7 +507,7 @@ type procedure ResetIntern(); protected - fFilename: AnsiString; //**< filename of the font-file + fFilename: IPath; //**< filename of the font-file fSize: integer; //**< Font base size (in pixels) fOutset: single; //**< size of outset extrusion (in pixels) fFace: FT_Face; //**< Holds the height of the font @@ -537,7 +538,7 @@ type * @param LoadFlags flags passed to FT_Load_Glyph() * @raises Exception if the font-file could not be loaded *} - constructor Create(const Filename: AnsiString; + constructor Create(const Filename: IPath; Size: integer; Outset: single = 0.0; LoadFlags: FT_Int32 = FT_LOAD_DEFAULT); @@ -567,7 +568,7 @@ type * The extrusion in pixels is Size*OutsetAmount * (0.0 -> no extrusion, 0.1 -> 10%). *} - constructor Create(const Filename: AnsiString; + constructor Create(const Filename: IPath; Size: integer; OutsetAmount: single = 0.0; UseMipmaps: boolean = true); @@ -585,7 +586,7 @@ type *} TFTOutlineFont = class(TFont) private - fFilename: AnsiString; + fFilename: IPath; fSize: integer; fOutset: single; fInnerFont, fOutlineFont: TFTFont; @@ -612,7 +613,7 @@ type procedure SetReflectionPass(Enable: boolean); override; public - constructor Create(const Filename: AnsiString; + constructor Create(const Filename: IPath; Size: integer; Outset: single; LoadFlags: FT_Int32 = FT_LOAD_DEFAULT); destructor Destroy; override; @@ -646,7 +647,7 @@ type function CreateMipmap(Level: integer; Scale: single): TFont; override; public - constructor Create(const Filename: AnsiString; + constructor Create(const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean = true); @@ -688,7 +689,7 @@ type * @param InfoFile the name of the info (.dat) file * @raises Exception if the file is corrupted *} - procedure LoadFontInfo(const InfoFile: AnsiString); + procedure LoadFontInfo(const InfoFile: IPath); protected procedure Render(const Text: UCS4String); override; @@ -708,7 +709,7 @@ type * (y-axis up) and from the lower edge of the glyphs bounding box) * @param(Ascender pixels from baseline to top of highest glyph) *} - constructor Create(const Filename: AnsiString; Outline: integer; + constructor Create(const Filename: IPath; Outline: integer; Baseline, Ascender, Descender: integer); destructor Destroy(); override; @@ -1406,7 +1407,7 @@ end; *} constructor TFTFont.Create( - const Filename: AnsiString; + const Filename: IPath; Size: integer; Outset: single; LoadFlags: FT_Int32); var @@ -1422,8 +1423,8 @@ begin fPart := fpNone; // load font information - if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename), 0, fFace) <> 0) then - raise Exception.Create('FT_New_Face: Could not load font ''' + Filename + ''''); + if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename.ToNative), 0, fFace) <> 0) then + raise Exception.Create('FT_New_Face: Could not load font ''' + Filename.ToNative + ''''); // support scalable fonts only if (not FT_IS_SCALABLE(fFace)) then @@ -1645,7 +1646,7 @@ end; * TFTScalableFont *} -constructor TFTScalableFont.Create(const Filename: AnsiString; +constructor TFTScalableFont.Create(const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean); var @@ -1701,7 +1702,7 @@ end; *} constructor TFTOutlineFont.Create( - const Filename: AnsiString; + const Filename: IPath; Size: integer; Outset: single; LoadFlags: FT_Int32); begin @@ -1893,7 +1894,7 @@ end; *} constructor TFTScalableOutlineFont.Create( - const Filename: AnsiString; + const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean); var @@ -2555,7 +2556,7 @@ end; * TBitmapFont *} -constructor TBitmapFont.Create(const Filename: AnsiString; Outline: integer; +constructor TBitmapFont.Create(const Filename: IPath; Outline: integer; Baseline, Ascender, Descender: integer); begin inherited Create(); @@ -2567,7 +2568,7 @@ begin fAscender := Ascender; fDescender := Descender; - LoadFontInfo(ChangeFileExt(Filename, '.dat')); + LoadFontInfo(Filename.SetExtension('.dat')); ResetIntern(); end; @@ -2597,18 +2598,18 @@ begin fWidths[Count] := Round(fWidths[Count] * WidthMult) + WidthAdd; end; -procedure TBitmapFont.LoadFontInfo(const InfoFile: AnsiString); +procedure TBitmapFont.LoadFontInfo(const InfoFile: IPath); var - Stream: TFileStream; + Stream: THandleStream; begin FillChar(fWidths[0], Length(fWidths), 0); Stream := nil; try - Stream := TFileStream.Create(InfoFile, fmOpenRead); + Stream := TBinaryFileStream.Create(InfoFile, fmOpenRead); Stream.Read(fWidths, 256); except - raise Exception.Create('Could not read font info file ''' + InfoFile + ''''); + raise Exception.Create('Could not read font info file ''' + InfoFile.ToNative + ''''); end; Stream.Free; end; diff --git a/unicode/src/base/UGraphic.pas b/unicode/src/base/UGraphic.pas index 7c5b18fd..9c12d998 100644 --- a/unicode/src/base/UGraphic.pas +++ b/unicode/src/base/UGraphic.pas @@ -340,7 +340,7 @@ begin Tex_Cursor_Unpressed := Texture.LoadTexture(Skin.GetTextureFileName('Cursor'), TEXTURE_TYPE_TRANSPARENT, 0); - if (Skin.GetTextureFileName('Cursor_Pressed') <> '') then + if (Skin.GetTextureFileName('Cursor_Pressed').IsSet) then Tex_Cursor_Pressed := Texture.LoadTexture(Skin.GetTextureFileName('Cursor_Pressed'), TEXTURE_TYPE_TRANSPARENT, 0) else Tex_Cursor_Pressed.TexNum := 0; @@ -389,14 +389,14 @@ begin End; Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255); - Tex_SingLineBonusBack[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('LineBonusBack')), TEXTURE_TYPE_COLORIZED, Col); + Tex_SingLineBonusBack[P] := Texture.LoadTexture(Skin.GetTextureFileName('LineBonusBack'), TEXTURE_TYPE_COLORIZED, Col); end; //## backgrounds for the scores ## for P := 0 to 5 do begin LoadColor(R, G, B, 'P' + IntToStr(P+1) + 'Light'); Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255); - Tex_ScoreBG[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreBG')), TEXTURE_TYPE_COLORIZED, Col); + Tex_ScoreBG[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreBG'), TEXTURE_TYPE_COLORIZED, Col); end; @@ -411,23 +411,23 @@ begin //NoteBar ScoreBar LoadColor(R, G, B, 'P' + IntToStr(P) + 'Dark'); Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255); - Tex_Score_NoteBarLevel_Dark[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Dark')), TEXTURE_TYPE_COLORIZED, Col); - Tex_Score_NoteBarRound_Dark[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Dark_Round')), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarLevel_Dark[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Dark'), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarRound_Dark[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Dark_Round'), TEXTURE_TYPE_COLORIZED, Col); //LineBonus ScoreBar LoadColor(R, G, B, 'P' + IntToStr(P) + 'Light'); Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255); - Tex_Score_NoteBarLevel_Light[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Light')), TEXTURE_TYPE_COLORIZED, Col); - Tex_Score_NoteBarRound_Light[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Light_Round')), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarLevel_Light[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Light'), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarRound_Light[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Light_Round'), TEXTURE_TYPE_COLORIZED, Col); //GoldenNotes ScoreBar LoadColor(R, G, B, 'P' + IntToStr(P) + 'Lightest'); Col := $10000 * Round(R*255) + $100 * Round(G*255) + Round(B*255); - Tex_Score_NoteBarLevel_Lightest[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Lightest')), TEXTURE_TYPE_COLORIZED, Col); - Tex_Score_NoteBarRound_Lightest[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('ScoreLevel_Lightest_Round')), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarLevel_Lightest[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Lightest'), TEXTURE_TYPE_COLORIZED, Col); + Tex_Score_NoteBarRound_Lightest[P] := Texture.LoadTexture(Skin.GetTextureFileName('ScoreLevel_Lightest_Round'), TEXTURE_TYPE_COLORIZED, Col); end; //## rating pictures that show a picture according to your rate ## for P := 0 to 7 do begin - Tex_Score_Ratings[P] := Texture.LoadTexture(pchar(Skin.GetTextureFileName('Rating_'+IntToStr(P))), TEXTURE_TYPE_TRANSPARENT, 0); + Tex_Score_Ratings[P] := Texture.LoadTexture(Skin.GetTextureFileName('Rating_'+IntToStr(P)), TEXTURE_TYPE_TRANSPARENT, 0); end; Log.LogStatus('Loading Textures - Done', 'LoadTextures'); @@ -464,7 +464,7 @@ begin end; // load icon image (must be 32x32 for win32) - Icon := LoadImage(ResourcesPath + WINDOW_ICON); + Icon := LoadImage(ResourcesPath.Append(WINDOW_ICON)); if (Icon <> nil) then SDL_WM_SetIcon(Icon, 0); diff --git a/unicode/src/base/UImage.pas b/unicode/src/base/UImage.pas index 60b0a3a2..25b0d1e2 100644 --- a/unicode/src/base/UImage.pas +++ b/unicode/src/base/UImage.pas @@ -34,7 +34,8 @@ interface {$I switches.inc} uses - SDL; + SDL, + UPath; {$DEFINE HavePNG} {$DEFINE HaveBMP} @@ -131,20 +132,20 @@ type *******************************************************) {$IFDEF HavePNG} -function WritePNGImage(const FileName: string; Surface: PSDL_Surface): boolean; +function WritePNGImage(const FileName: IPath; Surface: PSDL_Surface): boolean; {$ENDIF} {$IFDEF HaveBMP} -function WriteBMPImage(const FileName: string; Surface: PSDL_Surface): boolean; +function WriteBMPImage(const FileName: IPath; Surface: PSDL_Surface): boolean; {$ENDIF} {$IFDEF HaveJPG} -function WriteJPGImage(const FileName: string; Surface: PSDL_Surface; Quality: integer): boolean; +function WriteJPGImage(const FileName: IPath; Surface: PSDL_Surface; Quality: integer): boolean; {$ENDIF} (******************************************************* * Image loading *******************************************************) -function LoadImage(const Filename: string): PSDL_Surface; +function LoadImage(const Filename: IPath): PSDL_Surface; (******************************************************* * Image manipulation @@ -282,17 +283,17 @@ end; procedure user_read_data(png_ptr: png_structp; data: png_bytep; length: png_size_t); cdecl; var - inFile: TFileStream; + inFile: THandleStream; begin - inFile := TFileStream(png_get_io_ptr(png_ptr)); + inFile := THandleStream(png_get_io_ptr(png_ptr)); inFile.Read(data^, length); end; procedure user_write_data(png_ptr: png_structp; data: png_bytep; length: png_size_t); cdecl; var - outFile: TFileStream; + outFile: THandleStream; begin - outFile := TFileStream(png_get_io_ptr(png_ptr)); + outFile := THandleStream(png_get_io_ptr(png_ptr)); outFile.Write(data^, length); end; @@ -323,11 +324,11 @@ end; (* * ImageData must be in RGB-format *) -function WritePNGImage(const FileName: string; Surface: PSDL_Surface): boolean; +function WritePNGImage(const FileName: IPath; Surface: PSDL_Surface): boolean; var png_ptr: png_structp; info_ptr: png_infop; - pngFile: TFileStream; + pngFile: THandleStream; row: integer; rowData: array of png_bytep; // rowStride: integer; @@ -339,9 +340,9 @@ begin // open file for writing try - pngFile := TFileStream.Create(FileName, fmCreate); + pngFile := TBinaryFileStream.Create(FileName, fmCreate); except - Log.LogError('Could not open file: "' + FileName + '"', 'WritePngImage'); + Log.LogError('Could not open file: "' + FileName.ToNative + '"', 'WritePngImage'); Exit; end; @@ -500,9 +501,9 @@ type (* * ImageData must be in BGR-format *) -function WriteBMPImage(const FileName: string; Surface: PSDL_Surface): boolean; +function WriteBMPImage(const FileName: IPath; Surface: PSDL_Surface): boolean; var - bmpFile: TFileStream; + bmpFile: THandleStream; FileInfo: BITMAPINFOHEADER; FileHeader: BITMAPFILEHEADER; Converted: boolean; @@ -513,9 +514,9 @@ begin // open file for writing try - bmpFile := TFileStream.Create(FileName, fmCreate); + bmpFile := TBinaryFileStream.Create(FileName, fmCreate); except - Log.LogError('Could not open file: "' + FileName + '"', 'WriteBMPImage'); + Log.LogError('Could not open file: "' + FileName.ToNative + '"', 'WriteBMPImage'); Exit; end; @@ -579,7 +580,7 @@ begin Result := true; finally - Log.LogError('Could not write file: "' + FileName + '"', 'WriteBMPImage'); + Log.LogError('Could not write file: "' + FileName.ToNative + '"', 'WriteBMPImage'); end; if (Converted) then @@ -597,7 +598,7 @@ end; {$IFDEF HaveJPG} -function WriteJPGImage(const FileName: string; Surface: PSDL_Surface; Quality: integer): boolean; +function WriteJPGImage(const FileName: IPath; Surface: PSDL_Surface; Quality: integer): boolean; var {$IFDEF Delphi} Bitmap: TBitmap; @@ -676,9 +677,9 @@ begin try // compress image (don't forget this line, otherwise it won't be compressed) Jpeg.Compress(); - Jpeg.SaveToFile(FileName); + Jpeg.SaveToFile(FileName.ToNative); except - Log.LogError('Could not save file: "' + FileName + '"', 'WriteJPGImage'); + Log.LogError('Could not save file: "' + FileName.ToNative + '"', 'WriteJPGImage'); Exit; end; Jpeg.Free; @@ -763,27 +764,25 @@ end; (* * Loads an image from the given file *) -function LoadImage(const Filename: string): PSDL_Surface; +function LoadImage(const Filename: IPath): PSDL_Surface; var - FilenameFound: string; + FilenameCaseAdj: IPath; begin - Result := nil; + Result := nil; - // FileExistsInsensitive() requires a var-arg - FilenameFound := Filename; - - // try to find the file case insensitive - if (not FileExistsInsensitive(FilenameFound)) then + // try to adjust filename's case and check if it exists + FilenameCaseAdj := Filename.AdjustCase(false); + if (not FilenameCaseAdj.IsFile) then begin - Log.LogError('Image-File does not exist "'+FilenameFound+'"', 'LoadImage'); + Log.LogError('Image-File does not exist "' + FilenameCaseAdj.ToNative + '"', 'LoadImage'); Exit; end; // load from file try - Result := IMG_Load(PChar(FilenameFound)); + Result := IMG_Load(PChar(FilenameCaseAdj.ToNative)); except - Log.LogError('Could not load from file "'+FilenameFound+'"', 'LoadImage'); + Log.LogError('Could not load from file "' + FilenameCaseAdj.ToNative + '"', 'LoadImage'); Exit; end; end; diff --git a/unicode/src/base/UIni.pas b/unicode/src/base/UIni.pas index bf068f11..85f925ad 100644 --- a/unicode/src/base/UIni.pas +++ b/unicode/src/base/UIni.pas @@ -36,9 +36,11 @@ interface uses Classes, IniFiles, + SysUtils, ULog, UTextEncoding, - SysUtils; + UFilesystem, + UPath; type // TInputDeviceConfig stores the configuration for an input device. @@ -71,11 +73,10 @@ type TBackgroundMusicOption = (bmoOff, bmoOn); TIni = class private - function RemoveFileExt(FullName: string): string; function ExtractKeyIndex(const Key, Prefix, Suffix: string): integer; function GetMaxKeyIndex(Keys: TStringList; const Prefix, Suffix: string): integer; - function GetArrayIndex(const SearchArray: array of string; Value: string; CaseInsensitiv: boolean = false): integer; - function ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile; + function GetArrayIndex(const SearchArray: array of UTF8String; Value: string; CaseInsensitiv: boolean = false): integer; + function ReadArrayIndex(const SearchArray: array of UTF8String; IniFile: TCustomIniFile; IniSection: string; IniProperty: string; Default: integer): integer; procedure LoadInputDeviceCfg(IniFile: TMemIniFile); @@ -92,7 +93,7 @@ type NameTemplate: array[0..11] of UTF8String; //Filename of the opened iniFile - Filename: string; + Filename: IPath; // Game Players: integer; @@ -165,19 +166,19 @@ type var Ini: TIni; - IResolution: array of string; - ILanguage: array of string; - ITheme: array of string; - ISkin: array of string; + IResolution: array of UTF8String; + ILanguage: array of UTF8String; + ITheme: array of UTF8String; + ISkin: array of UTF8String; const - IPlayers: array[0..4] of string = ('1', '2', '3', '4', '6'); - IPlayersVals: array[0..4] of integer = ( 1 , 2 , 3 , 4 , 6 ); + IPlayers: array[0..4] of UTF8String = ('1', '2', '3', '4', '6'); + IPlayersVals: array[0..4] of integer = ( 1 , 2 , 3 , 4 , 6 ); - IDifficulty: array[0..2] of string = ('Easy', 'Medium', 'Hard'); - ITabs: array[0..1] of string = ('Off', 'On'); + IDifficulty: array[0..2] of UTF8String = ('Easy', 'Medium', 'Hard'); + ITabs: array[0..1] of UTF8String = ('Off', 'On'); - ISorting: array[0..7] of string = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Title2', 'Artist2'); + ISorting: array[0..7] of UTF8String = ('Edition', 'Genre', 'Language', 'Folder', 'Title', 'Artist', 'Title2', 'Artist2'); sEdition = 0; sGenre = 1; sLanguage = 2; @@ -187,71 +188,71 @@ const sTitle2 = 6; sArtist2 = 7; - IDebug: array[0..1] of string = ('Off', 'On'); + IDebug: array[0..1] of UTF8String = ('Off', 'On'); - IScreens: array[0..1] of string = ('1', '2'); - IFullScreen: array[0..1] of string = ('Off', 'On'); - IDepth: array[0..1] of string = ('16 bit', '32 bit'); - IVisualizer: array[0..2] of string = ('Off', 'WhenNoVideo','On'); + IScreens: array[0..1] of UTF8String = ('1', '2'); + IFullScreen: array[0..1] of UTF8String = ('Off', 'On'); + IDepth: array[0..1] of UTF8String = ('16 bit', '32 bit'); + IVisualizer: array[0..2] of UTF8String = ('Off', 'WhenNoVideo','On'); - IBackgroundMusic: array[0..1] of string = ('Off', 'On'); + IBackgroundMusic: array[0..1] of UTF8String = ('Off', 'On'); - ITextureSize: array[0..3] of string = ('64', '128', '256', '512'); - ITextureSizeVals: array[0..3] of integer = ( 64, 128, 256, 512); + ITextureSize: array[0..3] of UTF8String = ('64', '128', '256', '512'); + ITextureSizeVals: array[0..3] of integer = ( 64, 128, 256, 512); - ISingWindow: array[0..1] of string = ('Small', 'Big'); + ISingWindow: array[0..1] of UTF8String = ('Small', 'Big'); //SingBar Mod - IOscilloscope: array[0..1] of string = ('Off', 'On'); + IOscilloscope: array[0..1] of UTF8String = ('Off', 'On'); - ISpectrum: array[0..1] of string = ('Off', 'On'); - ISpectrograph: array[0..1] of string = ('Off', 'On'); - IMovieSize: array[0..2] of string = ('Half', 'Full [Vid]', 'Full [BG+Vid]'); + ISpectrum: array[0..1] of UTF8String = ('Off', 'On'); + ISpectrograph: array[0..1] of UTF8String = ('Off', 'On'); + IMovieSize: array[0..2] of UTF8String = ('Half', 'Full [Vid]', 'Full [BG+Vid]'); - IClickAssist: array[0..1] of string = ('Off', 'On'); - IBeatClick: array[0..1] of string = ('Off', 'On'); - ISavePlayback: array[0..1] of string = ('Off', 'On'); + IClickAssist: array[0..1] of UTF8String = ('Off', 'On'); + IBeatClick: array[0..1] of UTF8String = ('Off', 'On'); + ISavePlayback: array[0..1] of UTF8String = ('Off', 'On'); - IThreshold: array[0..3] of string = ('5%', '10%', '15%', '20%'); + IThreshold: array[0..3] of UTF8String = ('5%', '10%', '15%', '20%'); IThresholdVals: array[0..3] of single = (0.05, 0.10, 0.15, 0.20); - IVoicePassthrough: array[0..1] of string = ('Off', 'On'); + IVoicePassthrough: array[0..1] of UTF8String = ('Off', 'On'); - IAudioOutputBufferSize: array[0..9] of string = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536'); - IAudioOutputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 ); + IAudioOutputBufferSize: array[0..9] of UTF8String = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536'); + IAudioOutputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 ); - IAudioInputBufferSize: array[0..9] of string = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536'); - IAudioInputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 ); + IAudioInputBufferSize: array[0..9] of UTF8String = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536'); + IAudioInputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 ); //Song Preview - IPreviewVolume: array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%'); - IPreviewVolumeVals: array[0..10] of single = ( 0, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 ); + IPreviewVolume: array[0..10] of UTF8String = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%'); + IPreviewVolumeVals: array[0..10] of single = ( 0, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 ); - IPreviewFading: array[0..5] of string = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs'); - IPreviewFadingVals: array[0..5] of integer = ( 0, 1, 2, 3, 4, 5 ); + IPreviewFading: array[0..5] of UTF8String = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs'); + IPreviewFadingVals: array[0..5] of integer = ( 0, 1, 2, 3, 4, 5 ); - ILyricsFont: array[0..2] of string = ('Plain', 'OLine1', 'OLine2'); - ILyricsEffect: array[0..4] of string = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift'); - ISolmization: array[0..3] of string = ('Off', 'Euro', 'Jap', 'American'); - INoteLines: array[0..1] of string = ('Off', 'On'); + ILyricsFont: array[0..2] of UTF8String = ('Plain', 'OLine1', 'OLine2'); + ILyricsEffect: array[0..4] of UTF8String = ('Simple', 'Zoom', 'Slide', 'Ball', 'Shift'); + ISolmization: array[0..3] of UTF8String = ('Off', 'Euro', 'Jap', 'American'); + INoteLines: array[0..1] of UTF8String = ('Off', 'On'); - IColor: array[0..8] of string = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black'); + IColor: array[0..8] of UTF8String = ('Blue', 'Green', 'Pink', 'Red', 'Violet', 'Orange', 'Yellow', 'Brown', 'Black'); // Advanced - ILoadAnimation: array[0..1] of string = ('Off', 'On'); - IEffectSing: array[0..1] of string = ('Off', 'On'); - IScreenFade: array[0..1] of string = ('Off', 'On'); - IAskbeforeDel: array[0..1] of string = ('Off', 'On'); - IOnSongClick: array[0..2] of string = ('Sing', 'Select Players', 'Open Menu'); - ILineBonus: array[0..1] of string = ('Off', 'On'); - IPartyPopup: array[0..1] of string = ('Off', 'On'); + ILoadAnimation: array[0..1] of UTF8String = ('Off', 'On'); + IEffectSing: array[0..1] of UTF8String = ('Off', 'On'); + IScreenFade: array[0..1] of UTF8String = ('Off', 'On'); + IAskbeforeDel: array[0..1] of UTF8String = ('Off', 'On'); + IOnSongClick: array[0..2] of UTF8String = ('Sing', 'Select Players', 'Open Menu'); + ILineBonus: array[0..1] of UTF8String = ('Off', 'On'); + IPartyPopup: array[0..1] of UTF8String = ('Off', 'On'); - IJoypad: array[0..1] of string = ('Off', 'On'); - IMouse: array[0..2] of string = ('Off', 'Hardware Cursor', 'Software Cursor'); + IJoypad: array[0..1] of UTF8String = ('Off', 'On'); + IMouse: array[0..2] of UTF8String = ('Off', 'Hardware Cursor', 'Software Cursor'); // Recording options - IChannelPlayer: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6'); - IMicBoost: array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB'); + IChannelPlayer: array[0..6] of UTF8String = ('Off', '1', '2', '3', '4', '5', '6'); + IMicBoost: array[0..3] of UTF8String = ('Off', '+6dB', '+12dB', '+18dB'); implementation @@ -267,14 +268,6 @@ uses UPathUtils, UUnicodeUtils; -(** - * Returns the filename without its fileextension - *) -function TIni.RemoveFileExt(FullName: string): string; -begin - Result := ChangeFileExt(FullName, ''); -end; - (** * Extracts an index of a key that is surrounded by a Prefix/Suffix pair. * Example: ExtractKeyIndex('MyKey[1]', '[', ']') will return 1. @@ -325,7 +318,7 @@ end; * Returns the index of Value in SearchArray * or -1 if Value is not in SearchArray. *) -function TIni.GetArrayIndex(const SearchArray: array of string; Value: string; +function TIni.GetArrayIndex(const SearchArray: array of UTF8String; Value: string; CaseInsensitiv: boolean = false): integer; var i: integer; @@ -349,7 +342,7 @@ end; * If SearchArray does not contain the property value, the default value is * returned. *) -function TIni.ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile; +function TIni.ReadArrayIndex(const SearchArray: array of UTF8String; IniFile: TCustomIniFile; IniSection: string; IniProperty: string; Default: integer): integer; var StrValue: string; @@ -464,7 +457,7 @@ begin begin if (Pos('SONGDIR', UpperCase(PathStrings[I])) = 1) then begin - AddSongPath(IniFile.ReadString('Directories', PathStrings[I], '')); + AddSongPath(Path(IniFile.ReadString('Directories', PathStrings[I], ''))); end; end; @@ -477,18 +470,23 @@ var ThemeIni: TMemIniFile; ThemeName: string; I: integer; + Iter: IFileIterator; + FileInfo: TFileInfo; begin // Theme SetLength(ITheme, 0); - Log.LogStatus('Searching for Theme : ' + ThemePath + '*.ini', 'Theme'); + Log.LogStatus('Searching for Theme : ' + ThemePath.ToNative + '*.ini', 'Theme'); + - FindFirst(ThemePath + '*.ini',faAnyFile, SearchResult); - Repeat - Log.LogStatus('Found Theme: ' + SearchResult.Name, 'Theme'); + Iter := FileSystem.FileFind(ThemePath.Append('*.ini'), 0); + while (Iter.HasNext) do + begin + FileInfo := Iter.Next; + Log.LogStatus('Found Theme: ' + FileInfo.Name.ToNative, 'Theme'); //Read Themename from Theme - ThemeIni := TMemIniFile.Create(SearchResult.Name); - ThemeName := UpperCase(ThemeIni.ReadString('Theme','Name', RemoveFileExt(SearchResult.Name))); + ThemeIni := TMemIniFile.Create(FileInfo.Name.ToNative); + ThemeName := UpperCase(ThemeIni.ReadString('Theme','Name', FileInfo.Name.SetExtension('').ToNative)); ThemeIni.Free; //Search for Skins for this Theme @@ -497,12 +495,11 @@ begin if UpperCase(Skin.Skin[I].Theme) = ThemeName then begin SetLength(ITheme, Length(ITheme)+1); - ITheme[High(ITheme)] := RemoveFileExt(SearchResult.Name); + ITheme[High(ITheme)] := FileInfo.Name.SetExtension('').ToNative; break; end; end; - until FindNext(SearchResult) <> 0; - FindClose(SearchResult); + end; // No Theme Found if (Length(ITheme) = 0) then @@ -523,7 +520,7 @@ end; procedure TIni.LoadScreenModes(IniFile: TCustomIniFile); // swap two strings - procedure swap(var s1, s2: string); + procedure swap(var s1, s2: UTF8String); var s3: string; begin @@ -631,19 +628,15 @@ var begin GamePath := Platform.GetGameUserPath; - Log.LogStatus( 'GamePath : ' +GamePath , '' ); + Log.LogStatus( 'GamePath : ' +GamePath.ToNative , '' ); - if (Params.ConfigFile <> '') then - try - FileName := Params.ConfigFile; - except - FileName := GamePath + 'config.ini'; - end + if (Params.ConfigFile.IsSet) then + FileName := Params.ConfigFile else - FileName := GamePath + 'config.ini'; + FileName := GamePath.Append('config.ini'); - Log.LogStatus( 'Using config : ' + FileName , 'Ini'); - IniFile := TMemIniFile.Create( FileName ); + Log.LogStatus('Using config : ' + FileName.ToNative, 'Ini'); + IniFile := TMemIniFile.Create(FileName.ToNative); // Name for I := 0 to 11 do @@ -784,13 +777,13 @@ procedure TIni.Save; var IniFile: TIniFile; begin - if (FileExists(Filename) and FileIsReadOnly(Filename)) then + if (Filename.IsFile and Filename.IsReadOnly) then begin Log.LogError('Config-file is read-only', 'TIni.Save'); Exit; end; - IniFile := TIniFile.Create(Filename); + IniFile := TIniFile.Create(Filename.ToNative); // Players IniFile.WriteString('Game', 'Players', IPlayers[Players]); @@ -930,9 +923,9 @@ var IniFile: TIniFile; I: integer; begin - if not FileIsReadOnly(Filename) then + if not Filename.IsReadOnly() then begin - IniFile := TIniFile.Create(Filename); + IniFile := TIniFile.Create(Filename.ToNative); //Name Templates for Names Mod for I := 0 to High(Name) do @@ -950,9 +943,9 @@ procedure TIni.SaveLevel; var IniFile: TIniFile; begin - if not FileIsReadOnly(Filename) then + if not Filename.IsReadOnly() then begin - IniFile := TIniFile.Create(Filename); + IniFile := TIniFile.Create(Filename.ToNative); // Difficulty IniFile.WriteString('Game', 'Difficulty', IDifficulty[Difficulty]); diff --git a/unicode/src/base/ULanguage.pas b/unicode/src/base/ULanguage.pas index 9571358e..88b6647f 100644 --- a/unicode/src/base/ULanguage.pas +++ b/unicode/src/base/ULanguage.pas @@ -79,6 +79,8 @@ uses Classes, SysUtils, ULog, + UPath, + UFilesystem, UPathUtils; {** @@ -129,23 +131,26 @@ end; *} procedure TLanguage.LoadList; var - SR: TSearchRec; // for parsing directory + Iter: IFileIterator; + IniInfo: TFileInfo; + LangName: string; begin SetLength(List, 0); SetLength(ILanguage, 0); - if FindFirst(LanguagesPath + '*.ini', 0, SR) = 0 then + Iter := FileSystem.FileFind(LanguagesPath.Append('*.ini'), 0); + while(Iter.HasNext) do begin - repeat - SetLength(List, Length(List)+1); - SetLength(ILanguage, Length(ILanguage)+1); - SR.Name := ChangeFileExt(SR.Name, ''); - - List[High(List)].Name := SR.Name; - ILanguage[High(ILanguage)] := SR.Name; - until (FindNext(SR) <> 0); - SysUtils.FindClose(SR); - end; // if FindFirst + IniInfo := Iter.Next; + + LangName := IniInfo.Name.SetExtension('').ToUTF8; + + SetLength(List, Length(List)+1); + List[High(List)].Name := LangName; + + SetLength(ILanguage, Length(ILanguage)+1); + ILanguage[High(ILanguage)] := LangName; + end; end; {** @@ -158,7 +163,7 @@ var S: TStringList; begin SetLength(Entry, 0); - IniFile := TIniFile.Create(LanguagesPath + Language + '.ini'); + IniFile := TIniFile.Create(LanguagesPath.Append(Language + '.ini').ToNative); S := TStringList.Create; IniFile.ReadSectionValues('Text', S); diff --git a/unicode/src/base/ULog.pas b/unicode/src/base/ULog.pas index 6f7aa644..08a64e2b 100644 --- a/unicode/src/base/ULog.pas +++ b/unicode/src/base/ULog.pas @@ -34,7 +34,8 @@ interface {$I switches.inc} uses - Classes; + Classes, + UPath; (* * LOG_LEVEL_[TYPE] defines the "minimum" index for logs of type TYPE. Each @@ -198,7 +199,7 @@ begin if not BenchmarkFileOpened then begin BenchmarkFileOpened := true; - AssignFile(BenchmarkFile, LogPath + 'Benchmark.log'); + AssignFile(BenchmarkFile, LogPath.Append('Benchmark.log').ToNative); {$I-} Rewrite(BenchmarkFile); if IOResult = 0 then @@ -270,7 +271,7 @@ procedure TLog.LogToFile(const Text: string); begin if (FileOutputEnabled and not LogFileOpened) then begin - AssignFile(LogFile, LogPath + 'Error.log'); + AssignFile(LogFile, LogPath.Append('Error.log').ToNative); {$I-} Rewrite(LogFile); if IOResult = 0 then @@ -399,20 +400,19 @@ end; procedure TLog.LogVoice(SoundNr: integer); var - FS: TFileStream; - FileName: string; + FS: TBinaryFileStream; + Prefix: string; + FileName: IPath; Num: integer; begin for Num := 1 to 9999 do begin - FileName := IntToStr(Num); - while Length(FileName) < 4 do - FileName := '0' + FileName; - FileName := LogPath + 'Voice' + FileName + '.raw'; - if not FileExists(FileName) then + Prefix := Format('Voice%.4d', [Num]); + FileName := LogPath.Append(Prefix + '.raw'); + if not FileName.Exists() then break end; - FS := TFileStream.Create(FileName, fmCreate); + FS := TBinaryFileStream.Create(FileName, fmCreate); AudioInputProcessor.Sound[SoundNr].LogBuffer.Seek(0, soBeginning); FS.CopyFrom(AudioInputProcessor.Sound[SoundNr].LogBuffer, AudioInputProcessor.Sound[SoundNr].LogBuffer.Size); diff --git a/unicode/src/base/UMain.pas b/unicode/src/base/UMain.pas index 5b07c9ae..6ab4e187 100644 --- a/unicode/src/base/UMain.pas +++ b/unicode/src/base/UMain.pas @@ -196,7 +196,7 @@ begin // Theme Log.BenchmarkStart(1); Log.LogStatus('Load Themes', 'Initialization'); - Theme := TTheme.Create(ThemePath + ITheme[Ini.Theme] + '.ini', Ini.Color); + Theme := TTheme.Create(ThemePath.Append(ITheme[Ini.Theme] + '.ini'), Ini.Color); Log.BenchmarkEnd(1); Log.LogBenchmark('Loading Themes', 1); @@ -252,10 +252,10 @@ begin Log.LogStatus('DataBase System', 'Initialization'); DataBase := TDataBaseSystem.Create; - if (Params.ScoreFile = '') then - DataBase.Init (Platform.GetGameUserPath + 'Ultrastar.db') + if (Params.ScoreFile.IsUnset) then + DataBase.Init(Platform.GetGameUserPath.Append('Ultrastar.db')) else - DataBase.Init (Params.ScoreFile); + DataBase.Init(Params.ScoreFile); Log.BenchmarkEnd(1); Log.LogBenchmark('Loading DataBase System', 1); diff --git a/unicode/src/base/UMusic.pas b/unicode/src/base/UMusic.pas index b959ea05..51759b6f 100644 --- a/unicode/src/base/UMusic.pas +++ b/unicode/src/base/UMusic.pas @@ -34,10 +34,11 @@ interface {$I switches.inc} uses - UTime, SysUtils, Classes, - UBeatTimer; + UTime, + UBeatTimer, + UPath; type TNoteType = (ntFreestyle, ntNormal, ntGolden); @@ -315,7 +316,7 @@ type // soundcard output-devices information TAudioOutputDevice = class public - Name: string; // soundcard name + Name: UTF8String; // soundcard name end; TAudioOutputDeviceList = array of TAudioOutputDevice; @@ -324,7 +325,7 @@ type ['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}'] function GetName: String; - function Open(const Filename: string): boolean; // true if succeed + function Open(const Filename: IPath): boolean; // true if succeed procedure Close; procedure Play; @@ -376,7 +377,7 @@ type // nil-pointers is not neccessary anymore. // PlaySound/StopSound will be removed then, OpenSound will be renamed to // CreateSound. - function OpenSound(const Filename: String): TAudioPlaybackStream; + function OpenSound(const Filename: IPath): TAudioPlaybackStream; procedure PlaySound(Stream: TAudioPlaybackStream); procedure StopSound(Stream: TAudioPlaybackStream); @@ -391,7 +392,7 @@ type IGenericDecoder = Interface ['{557B0E9A-604D-47E4-B826-13769F3E10B7}'] - function GetName(): String; + function GetName(): string; function InitializeDecoder(): boolean; function FinalizeDecoder(): boolean; //function IsSupported(const Filename: string): boolean; @@ -406,7 +407,7 @@ type IAudioDecoder = Interface( IGenericDecoder ) ['{AB47B1B6-2AA9-4410-BF8C-EC79561B5478}'] - function Open(const Filename: string): TAudioDecodeStream; + function Open(const Filename: IPath): TAudioDecodeStream; end; IAudioInput = Interface @@ -497,7 +498,7 @@ type procedure StartBgMusic(); procedure PauseBgMusic(); // TODO - //function AddSound(Filename: string): integer; + //function AddSound(Filename: IPath): integer; //procedure RemoveSound(ID: integer); //function GetSound(ID: integer): TAudioPlaybackStream; //property Sound[ID: integer]: TAudioPlaybackStream read GetSound; default; @@ -654,7 +655,7 @@ begin FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do begin - CurrentAudioDecoder := IAudioDecoder(InterfaceList[i]); + CurrentAudioDecoder := InterfaceList[i] as IAudioDecoder; if (not CurrentAudioDecoder.InitializeDecoder()) then begin Log.LogError('Initialize failed, Removing - '+ CurrentAudioDecoder.GetName); @@ -671,7 +672,7 @@ begin FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do begin - CurrentAudioPlayback := IAudioPlayback(InterfaceList[i]); + CurrentAudioPlayback := InterfaceList[i] as IAudioPlayback; if (CurrentAudioPlayback.InitializePlayback()) then begin DefaultAudioPlayback := CurrentAudioPlayback; @@ -686,7 +687,7 @@ begin FilterInterfaceList(IAudioInput, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do begin - CurrentAudioInput := IAudioInput(InterfaceList[i]); + CurrentAudioInput := InterfaceList[i] as IAudioInput; if (CurrentAudioInput.InitializeRecord()) then begin DefaultAudioInput := CurrentAudioInput; @@ -719,7 +720,7 @@ begin FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do begin - VideoInterface := IVideoPlayback(InterfaceList[i]); + VideoInterface := InterfaceList[i] as IVideoPlayback; if (VideoInterface.Init()) then begin DefaultVideoPlayback := VideoInterface; @@ -734,7 +735,7 @@ begin FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do begin - VisualInterface := IVideoVisualization(InterfaceList[i]); + VisualInterface := InterfaceList[i] as IVideoVisualization; if (VisualInterface.Init()) then begin DefaultVisualization := VisualInterface; @@ -748,7 +749,7 @@ begin // now that we have all interfaces, we can dump them // TODO: move this to another place - if FindCmdLineSwitch( cMediaInterfaces ) then + if FindCmdLineSwitch(cMediaInterfaces) then begin DumpMediaInterfaces(); halt; @@ -772,27 +773,27 @@ begin // finalize audio playback interfaces (should be done before the decoders) FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do - IAudioPlayback(InterfaceList[i]).FinalizePlayback(); + (InterfaceList[i] as IAudioPlayback).FinalizePlayback(); // finalize audio input interfaces FilterInterfaceList(IAudioInput, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do - IAudioInput(InterfaceList[i]).FinalizeRecord(); + (InterfaceList[i] as IAudioInput).FinalizeRecord(); // finalize audio decoder interfaces FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do - IAudioDecoder(InterfaceList[i]).FinalizeDecoder(); + (InterfaceList[i] as IAudioDecoder).FinalizeDecoder(); // finalize video interfaces FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do - IVideoPlayback(InterfaceList[i]).Finalize(); + (InterfaceList[i] as IVideoPlayback).Finalize(); // finalize audio decoder interfaces FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList); for i := 0 to InterfaceList.Count-1 do - IVideoVisualization(InterfaceList[i]).Finalize(); + (InterfaceList[i] as IVideoVisualization).Finalize(); InterfaceList.Free; @@ -855,14 +856,14 @@ procedure TSoundLibrary.LoadSounds(); begin UnloadSounds(); - Start := AudioPlayback.OpenSound(SoundPath + 'Common start.mp3'); - Back := AudioPlayback.OpenSound(SoundPath + 'Common back.mp3'); - Swoosh := AudioPlayback.OpenSound(SoundPath + 'menu swoosh.mp3'); - Change := AudioPlayback.OpenSound(SoundPath + 'select music change music 50.mp3'); - Option := AudioPlayback.OpenSound(SoundPath + 'option change col.mp3'); - Click := AudioPlayback.OpenSound(SoundPath + 'rimshot022b.mp3'); + Start := AudioPlayback.OpenSound(SoundPath.Append('Common start.mp3')); + Back := AudioPlayback.OpenSound(SoundPath.Append('Common back.mp3')); + Swoosh := AudioPlayback.OpenSound(SoundPath.Append('menu swoosh.mp3')); + Change := AudioPlayback.OpenSound(SoundPath.Append('select music change music 50.mp3')); + Option := AudioPlayback.OpenSound(SoundPath.Append('option change col.mp3')); + Click := AudioPlayback.OpenSound(SoundPath.Append('rimshot022b.mp3')); - BGMusic := AudioPlayback.OpenSound(SoundPath + 'Bebeto_-_Loop010.mp3'); + BGMusic := AudioPlayback.OpenSound(SoundPath.Append('Bebeto_-_Loop010.mp3')); if (BGMusic <> nil) then BGMusic.Loop := True; diff --git a/unicode/src/base/UPath.pas b/unicode/src/base/UPath.pas index 5203e7eb..fc8c028e 100644 --- a/unicode/src/base/UPath.pas +++ b/unicode/src/base/UPath.pas @@ -45,14 +45,17 @@ type IPath = interface; {** - * TUnicodeFileStream + * TBinaryFileStream (inherited from THandleStream) *} {$IFDEF MSWINDOWS} - TUnicodeFileStream = class(TTntFileStream) + TBinaryFileStream = class(TTntFileStream) {$ELSE} - TUnicodeFileStream = class(TFileStream) + TBinaryFileStream = class(TFileStream) {$ENDIF} public + {** + * @seealso TFileStream.Create for valid Mode parameters + *} constructor Create(const FileName: IPath; Mode: Word); end; @@ -65,13 +68,28 @@ type 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) @@ -100,11 +118,6 @@ type *} function Open(Mode: LongWord): THandle; - {** - * Note: Stream must be freed with TUniFileStream.Free after usage - *} - function OpenStream(Mode: Word): TUnicodeFileStream; - {** @seealso SysUtils.ExtractFileDrive() *} function GetDrive(): IPath; @@ -125,7 +138,9 @@ type * The file itself is not changed, use Rename() for this task. * @seealso SysUtils.ChangeFileExt() *} - function SetExtension(const Extension: IPath): IPath; + 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. @@ -143,16 +158,19 @@ type * 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): IPath; + 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. - * Example: C:\test\dir -> ['C:\', 'test\', 'dir'] + * 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 PathNone if none exists. + * Returns the parent directory or PATH_NONE if none exists. *} function GetParent(): IPath; @@ -171,10 +189,10 @@ type function AdjustCase(AdjustAllLevels: boolean): IPath; {** @seealso SysUtils.IncludeTrailingPathDelimiter() *} - function IncludeTrailingPathDelimiter(): IPath; + function AppendPathDelim(): IPath; {** @seealso SysUtils.ExcludeTrailingPathDelimiter() *} - function ExcludeTrailingPathDelimiter(): IPath; + function RemovePathDelim(): IPath; function Exists(): boolean; function IsFile(): boolean; @@ -187,13 +205,29 @@ type 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; + 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 @@ -222,17 +256,17 @@ type * - 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): IPath; overload; +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): IPath; overload; +function Path(const PathName: WideString; DelimOption: TPathDelimOption = pdKeep): IPath; overload; {** - * Returns a reference to a singleton with path simply set to ''. + * Returns a singleton for Path(''). *} -function PathNone(): IPath; +function PATH_NONE(): IPath; implementation @@ -247,7 +281,7 @@ type {** * Unifies the filename. Path-delimiters are replaced by '/'. *} - procedure Unify(); + procedure Unify(DelimOption: TPathDelimOption); {** * Returns a copy of fName with path delimiters changed to '/'. @@ -257,7 +291,7 @@ type procedure AssertRefCount; inline; public - constructor Create(const Name: UTF8String); + constructor Create(const Name: UTF8String; DelimOption: TPathDelimOption); destructor Destroy(); override; function ToUTF8(UseNativeDelim: boolean): UTF8String; @@ -265,27 +299,36 @@ type function ToNative(): RawByteString; function Open(Mode: LongWord): THandle; - function OpenStream(Mode: Word): TUnicodeFileStream; function GetDrive(): IPath; function GetPath(): IPath; function GetDir(): IPath; function GetName(): IPath; function GetExtension(): IPath; - function SetExtension(const Extension: IPath): 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): IPath; - function Equals(const Other: IPath; IgnoreCase: boolean): boolean; + 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 IncludeTrailingPathDelimiter(): IPath; - function ExcludeTrailingPathDelimiter(): IPath; + function AppendPathDelim(): IPath; + function RemovePathDelim(): IPath; function GetFileAge(): integer; overload; function GetFileAge(out FileDateTime: TDateTime): boolean; overload; @@ -297,7 +340,10 @@ type 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; @@ -308,34 +354,36 @@ type function CopyFile(const Target: IPath; FailIfExists: boolean): boolean; end; -function Path(const PathName: RawByteString): IPath; +function Path(const PathName: RawByteString; DelimOption: TPathDelimOption): IPath; begin if (IsUTF8String(PathName)) then - Result := TPathImpl.Create(PathName) + Result := TPathImpl.Create(PathName, DelimOption) else if (IsNativeUTF8()) then - Result := PathNone + Result := PATH_NONE else - Result := TPathImpl.Create(AnsiToUtf8(PathName)); + Result := TPathImpl.Create(AnsiToUtf8(PathName), DelimOption); end; -function Path(const PathName: WideString): IPath; +function Path(const PathName: WideString; DelimOption: TPathDelimOption): IPath; begin - Result := TPathImpl.Create(UTF8Encode(PathName)); + 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); +constructor TPathImpl.Create(const Name: UTF8String; DelimOption: TPathDelimOption); begin inherited Create(); fName := Name; - Unify(); + Unify(DelimOption); end; destructor TPathImpl.Destroy(); @@ -343,7 +391,7 @@ begin inherited; end; -procedure TPathImpl.Unify(); +procedure TPathImpl.Unify(DelimOption: TPathDelimOption); var I: integer; begin @@ -353,6 +401,12 @@ 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; @@ -430,6 +484,16 @@ begin 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; @@ -448,9 +512,9 @@ var begin AssertRefCount; - Result := PathNone; + Result := PATH_NONE; - CurPath := Self.ExcludeTrailingPathDelimiter(); + CurPath := Self.RemovePathDelim(); // check if current path has a parent (no further '/') if (Pos(PathDelim, CurPath.ToUTF8()) = 0) then Exit; @@ -491,7 +555,7 @@ begin // TODO: remove this workaround for FPC bug TmpPath := CurPath; CurPath := TmpPath.GetParent(); - until (CurPath = PathNone); + until (CurPath = PATH_NONE); // reverse list SetLength(Result, Length(Components)); @@ -499,28 +563,56 @@ begin Result[I] := Components[High(Components)-I]; end; -function TPathImpl.Append(const Child: IPath): IPath; +function TPathImpl.Append(const Child: IPath; DelimOption: TPathDelimOption): IPath; +var + TmpResult: IPath; begin AssertRefCount; if (fName = '') then - Result := Child + TmpResult := Child else - Result := Path(Self.IncludeTrailingPathDelimiter().ToUTF8() + Child.ToUTF8()); + 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().ExcludeTrailingPathDelimiter().ToUTF8(); - OtherPath := Other.GetAbsolutePath().ExcludeTrailingPathDelimiter().ToUTF8(); + 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; @@ -533,11 +625,11 @@ begin // TODO: remove workaround for fpc refcount bug TmpPath := Self.GetParent(); TmpPath2 := TmpPath.GetAbsolutePath(); - SelfPath := TmpPath2.IncludeTrailingPathDelimiter().ToUTF8(); + SelfPath := TmpPath2.AppendPathDelim().ToUTF8(); // TODO: remove workaround for fpc refcount bug TmpPath := Parent.GetAbsolutePath(); - ParentPath := TmpPath.IncludeTrailingPathDelimiter().ToUTF8(); + ParentPath := TmpPath.AppendPathDelim().ToUTF8(); // simply check if this paths parent path (SelfPath) equals ParentPath Result := (SelfPath = ParentPath); @@ -546,11 +638,11 @@ begin begin // TODO: remove workaround for fpc refcount bug TmpPath := Self.GetAbsolutePath(); - SelfPath := TmpPath.IncludeTrailingPathDelimiter().ToUTF8(); + SelfPath := TmpPath.AppendPathDelim().ToUTF8(); // TODO: remove workaround for fpc refcount bug TmpPath := Parent.GetAbsolutePath(); - ParentPath := TmpPath.IncludeTrailingPathDelimiter().ToUTF8(); + ParentPath := TmpPath.AppendPathDelim().ToUTF8(); if (Length(SelfPath) <= Length(ParentPath)) then Exit; @@ -583,12 +675,12 @@ begin // extract name component of current path // TODO: remove workaround for fpc refcount bug - TmpPath := CurPath.ExcludeTrailingPathDelimiter(); + TmpPath := CurPath.RemovePathDelim(); LocalName := TmpPath.GetName(); // try to adjust parent OldParent := CurPath.GetParent(); - if (OldParent <> PathNone) then + if (OldParent <> PATH_NONE) then begin if (not AdjustAllLevels) then begin @@ -663,13 +755,13 @@ begin end; end; -function TPathImpl.IncludeTrailingPathDelimiter(): IPath; +function TPathImpl.AppendPathDelim(): IPath; begin AssertRefCount; Result := FileSystem.IncludeTrailingPathDelimiter(Self); end; -function TPathImpl.ExcludeTrailingPathDelimiter(): IPath; +function TPathImpl.RemovePathDelim(): IPath; begin AssertRefCount; Result := FileSystem.ExcludeTrailingPathDelimiter(Self); @@ -693,11 +785,6 @@ begin Result := FileSystem.FileOpen(Self, Mode); end; -function TPathImpl.OpenStream(Mode: Word): TUnicodeFileStream; -begin - Result := TUnicodeFileStream.Create(Self, Mode); -end; - function TPathImpl.GetFileAge(): integer; begin Result := FileSystem.FileAge(Self); @@ -759,6 +846,16 @@ 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; @@ -786,23 +883,38 @@ begin end; -{ TUnicodeFileStream } +{ 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 } -constructor TUnicodeFileStream.Create(const FileName: IPath; Mode: Word); +function TTextFileStream.ReadLine(var Line: UTF8String): boolean; begin - inherited Create(FileName.ToWide(), Mode); + // TODO end; -{$ELSE} +function TTextFileStream.ReadLine(var Line: AnsiString): boolean; +begin + // TODO +end; -constructor TUnicodeFileStream.Create(const FileName: IPath; Mode: Word); +procedure TTextFileStream.WriteLine(Line: RawByteString); begin - inherited Create(FileName.ToNative(), Mode); + Self.WriteBuffer(Line[1], Length(Line)); end; -{$ENDIF} +procedure TTextFileStream.Write(Str: RawByteString); +begin + Self.WriteBuffer(Str[1], Length(Str)); +end; { TUnicodeMemoryStream } @@ -810,7 +922,7 @@ procedure TUnicodeMemoryStream.LoadFromFile(const FileName: IPath); var Stream: TStream; begin - Stream := TUnicodeFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); + Stream := TBinaryFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try LoadFromStream(Stream); finally @@ -822,7 +934,7 @@ procedure TUnicodeMemoryStream.SaveToFile(const FileName: IPath); var Stream: TStream; begin - Stream := TUnicodeFileStream.Create(FileName, fmCreate); + Stream := TBinaryFileStream.Create(FileName, fmCreate); try SaveToStream(Stream); finally @@ -831,17 +943,17 @@ begin end; var - PathNone_Singelton: IPath; + PATH_NONE_Singelton: IPath; -function PathNone(): IPath; +function PATH_NONE(): IPath; begin - Result := PathNone_Singelton; + Result := PATH_NONE_Singelton; end; initialization - PathNone_Singelton := Path(''); + PATH_NONE_Singelton := Path(''); finalization - PathNone_Singelton := nil; + PATH_NONE_Singelton := nil; end. diff --git a/unicode/src/base/UPathUtils.pas b/unicode/src/base/UPathUtils.pas index 4c4d3bc0..c2bcdd4b 100644 --- a/unicode/src/base/UPathUtils.pas +++ b/unicode/src/base/UPathUtils.pas @@ -35,28 +35,29 @@ interface uses SysUtils, - Classes; + Classes, + UPath; var // Absolute Paths - GamePath: string; - SoundPath: string; - SongPaths: TStringList; - LogPath: string; - ThemePath: string; - SkinsPath: string; - ScreenshotsPath: string; - CoverPaths: TStringList; - LanguagesPath: string; - PluginPath: string; - VisualsPath: string; - FontPath: string; - ResourcesPath: string; - PlayListPath: string; - -function FindPath(out PathResult: string; const RequestedPath: string; NeedsWritePermission: boolean): boolean; + GamePath: IPath; + SoundPath: IPath; + SongPaths: IInterfaceList; + LogPath: IPath; + ThemePath: IPath; + SkinsPath: IPath; + ScreenshotsPath: IPath; + CoverPaths: IInterfaceList; + LanguagesPath: IPath; + PluginPath: IPath; + VisualsPath: IPath; + FontPath: IPath; + ResourcesPath: IPath; + PlaylistPath: IPath; + +function FindPath(out PathResult: IPath; const RequestedPath: IPath; NeedsWritePermission: boolean): boolean; procedure InitializePaths; -procedure AddSongPath(const Path: string); +procedure AddSongPath(const Path: IPath); implementation @@ -66,33 +67,38 @@ uses UCommandLine, ULog; -procedure AddSpecialPath(var PathList: TStringList; const Path: string); +procedure AddSpecialPath(var PathList: IInterfaceList; const Path: IPath); var Index: integer; - PathAbs, OldPathAbs: string; + PathAbs, PathTmp: IPath; + OldPath, OldPathAbs, OldPathTmp: IPath; begin if (PathList = nil) then - PathList := TStringList.Create; + PathList := TInterfaceList.Create; - if (Path = '') or not ForceDirectories(Path) then + if Path.Equals(PATH_NONE) or not Path.CreateDirectory(true) then Exit; - PathAbs := IncludeTrailingPathDelimiter(ExpandFileName(Path)); + PathTmp := Path.GetAbsolutePath(); + PathAbs := PathTmp.AppendPathDelim(); // check if path or a part of the path was already added for Index := 0 to PathList.Count-1 do begin - OldPathAbs := IncludeTrailingPathDelimiter(ExpandFileName(PathList[Index])); + OldPath := PathList[Index] as IPath; + OldPathTmp := OldPath.GetAbsolutePath(); + OldPathAbs := OldPathTmp.AppendPathDelim(); + // check if the new directory is a sub-directory of a previously added one. // This is also true, if both paths point to the same directories. - if (AnsiStartsText(OldPathAbs, PathAbs)) then + if (OldPathAbs.IsChildOf(PathAbs, false) or OldPathAbs.Equals(PathAbs)) then begin // ignore the new path Exit; end; // check if a previously added directory is a sub-directory of the new one. - if (AnsiStartsText(PathAbs, OldPathAbs)) then + if (PathAbs.IsChildOf(OldPathAbs, false)) then begin // replace the old with the new one. PathList[Index] := PathAbs; @@ -103,12 +109,12 @@ begin PathList.Add(PathAbs); end; -procedure AddSongPath(const Path: string); +procedure AddSongPath(const Path: IPath); begin AddSpecialPath(SongPaths, Path); end; -procedure AddCoverPath(const Path: string); +procedure AddCoverPath(const Path: IPath); begin AddSpecialPath(CoverPaths, Path); end; @@ -117,30 +123,27 @@ end; * Initialize a path variable * After setting paths, make sure that paths exist *) -function FindPath(out PathResult: string; - const RequestedPath: string; - NeedsWritePermission: boolean) - : boolean; +function FindPath( + out PathResult: IPath; + const RequestedPath: IPath; + NeedsWritePermission: boolean): boolean; begin Result := false; - if (RequestedPath = '') then + if (RequestedPath.Equals(PATH_NONE)) then Exit; // Make sure the directory exists - if (not ForceDirectories(RequestedPath)) then + if (not RequestedPath.CreateDirectory(true)) then begin - PathResult := ''; + PathResult := PATH_NONE; Exit; end; - PathResult := IncludeTrailingPathDelimiter(RequestedPath); + PathResult := RequestedPath.AppendPathDelim(); - if (NeedsWritePermission) and - (FileIsReadOnly(RequestedPath)) then - begin + if (NeedsWritePermission) and RequestedPath.IsReadOnly() then Exit; - end; Result := true; end; @@ -149,40 +152,45 @@ end; * Function sets all absolute paths e.g. song path and makes sure the directorys exist *) procedure InitializePaths; +var + SharedPath, UserPath: IPath; begin // Log directory (must be writable) if (not FindPath(LogPath, Platform.GetLogPath, true)) then begin Log.FileOutputEnabled := false; - Log.LogWarn('Log directory "'+ Platform.GetLogPath +'" not available', 'InitializePaths'); + Log.LogWarn('Log directory "'+ Platform.GetLogPath.ToNative +'" not available', 'InitializePaths'); end; - FindPath(SoundPath, Platform.GetGameSharedPath + 'sounds', false); - FindPath(ThemePath, Platform.GetGameSharedPath + 'themes', false); - FindPath(SkinsPath, Platform.GetGameSharedPath + 'themes', false); - FindPath(LanguagesPath, Platform.GetGameSharedPath + 'languages', false); - FindPath(PluginPath, Platform.GetGameSharedPath + 'plugins', false); - FindPath(VisualsPath, Platform.GetGameSharedPath + 'visuals', false); - FindPath(FontPath, Platform.GetGameSharedPath + 'fonts', false); - FindPath(ResourcesPath, Platform.GetGameSharedPath + 'resources', false); + SharedPath := Platform.GetGameSharedPath; + UserPath := Platform.GetGameUserPath; + + FindPath(SoundPath, SharedPath.Append('sounds'), false); + FindPath(ThemePath, SharedPath.Append('themes'), false); + FindPath(SkinsPath, SharedPath.Append('themes'), false); + FindPath(LanguagesPath, SharedPath.Append('languages'), false); + FindPath(PluginPath, SharedPath.Append('plugins'), false); + FindPath(VisualsPath, SharedPath.Append('visuals'), false); + FindPath(FontPath, SharedPath.Append('fonts'), false); + FindPath(ResourcesPath, SharedPath.Append('resources'), false); // Playlists are not shared as we need one directory to write too - FindPath(PlaylistPath, Platform.GetGameUserPath + 'playlists', true); + FindPath(PlaylistPath, UserPath.Append('playlists'), true); // Screenshot directory (must be writable) - if (not FindPath(ScreenshotsPath, Platform.GetGameUserPath + 'screenshots', true)) then + if (not FindPath(ScreenshotsPath, UserPath.Append('screenshots'), true)) then begin - Log.LogWarn('Screenshot directory "'+ Platform.GetGameUserPath +'" not available', 'InitializePaths'); + Log.LogWarn('Screenshot directory "'+ UserPath.ToNative +'" not available', 'InitializePaths'); end; // Add song paths AddSongPath(Params.SongPath); - AddSongPath(Platform.GetGameSharedPath + 'songs'); - AddSongPath(Platform.GetGameUserPath + 'songs'); + AddSongPath(SharedPath.Append('songs')); + AddSongPath(UserPath.Append('songs')); // Add category cover paths - AddCoverPath(Platform.GetGameSharedPath + 'covers'); - AddCoverPath(Platform.GetGameUserPath + 'covers'); + AddCoverPath(SharedPath.Append('covers')); + AddCoverPath(UserPath.Append('covers')); end; end. diff --git a/unicode/src/base/UPlatform.pas b/unicode/src/base/UPlatform.pas index 6f13481c..11c67fa7 100644 --- a/unicode/src/base/UPlatform.pas +++ b/unicode/src/base/UPlatform.pas @@ -39,28 +39,20 @@ interface {$I switches.inc} uses - Classes; + Classes, + UPath; type - TDirectoryEntry = record - Name: WideString; - IsDirectory: boolean; - IsFile: boolean; - end; - - TDirectoryEntryArray = array of TDirectoryEntry; - TPlatform = class - function GetExecutionDir(): string; + function GetExecutionDir(): IPath; procedure Init; virtual; - function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray; virtual; abstract; + function TerminateIfAlreadyRunning(var WndTitle: string): boolean; virtual; - function FindSongFile(Dir, Mask: WideString): WideString; virtual; procedure Halt; virtual; - function GetLogPath: WideString; virtual; abstract; - function GetGameSharedPath: WideString; virtual; abstract; - function GetGameUserPath: WideString; virtual; abstract; - function CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean; virtual; + + function GetLogPath: IPath; virtual; abstract; + function GetGameSharedPath: IPath; virtual; abstract; + function GetGameUserPath: IPath; virtual; abstract; end; function Platform(): TPlatform; @@ -76,7 +68,9 @@ uses {$ELSEIF Defined(UNIX)} UPlatformLinux, {$IFEND} - ULog; + ULog, + UUnicodeUtils, + UFilesystem; // I modified it to use the Platform_singleton in this location (in the implementation) @@ -109,9 +103,13 @@ end; {** * Returns the directory of the executable *} -function TPlatform.GetExecutionDir(): string; +function TPlatform.GetExecutionDir(): IPath; +var + ExecName, ExecDir: IPath; begin - Result := ExpandFileName(ExtractFilePath(ParamStr(0))); + ExecName := Path(ParamStr(0)); + ExecDir := ExecName.GetPath; + Result := ExecDir.GetAbsolutePath(); end; (** @@ -122,65 +120,6 @@ begin Result := false; end; -(** - * Default FindSongFile() implementation - *) -function TPlatform.FindSongFile(Dir, Mask: WideString): WideString; -var - SR: TSearchRec; // for parsing song directory -begin - Result := ''; - if SysUtils.FindFirst(Dir + Mask, faDirectory, SR) = 0 then - begin - Result := SR.Name; - end; - SysUtils.FindClose(SR); -end; - -function TPlatform.CopyFile(const Source, Target: WideString; 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, fmOpenRead); - TargetFile := TFileStream.Create(Target, 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; - - initialization {$IF Defined(MSWINDOWS)} Platform_singleton := TPlatformWindows.Create; diff --git a/unicode/src/base/UPlatformLinux.pas b/unicode/src/base/UPlatformLinux.pas index 7efeb963..f318d49d 100644 --- a/unicode/src/base/UPlatformLinux.pas +++ b/unicode/src/base/UPlatformLinux.pas @@ -44,15 +44,13 @@ type UseLocalDirs: boolean; procedure DetectLocalExecution(); - function GetHomeDir(): string; + function GetHomeDir(): IPath; public procedure Init; override; - - function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; override; - - function GetLogPath : WideString; override; - function GetGameSharedPath : WideString; override; - function GetGameUserPath : WideString; override; + + function GetLogPath : IPath; override; + function GetGameSharedPath : IPath; override; + function GetGameUserPath : IPath; override; end; implementation @@ -60,9 +58,7 @@ implementation uses UCommandLine, BaseUnix, - {$IF FPC_VERSION_INT >= 2002002} pwd, - {$IFEND} SysUtils, ULog; @@ -88,113 +84,65 @@ end; *} procedure TPlatformLinux.DetectLocalExecution(); var - LocalDir: string; + LocalDir, LanguageDir: IPath; begin - LocalDir := GetExecutionDir(); - // we just check if the 'languages' folder exists in the // directory of the executable. If so -> local execution. - UseLocalDirs := (DirectoryExists(LocalDir + 'languages')); -end; - -function TPlatformLinux.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; -var - i: Integer; - TheDir : pDir; - ADirent : pDirent; - lAttrib : integer; -begin - i := 0; - Filter := LowerCase(Filter); - - TheDir := FpOpenDir(Dir); - if Assigned(TheDir) then - begin - repeat - ADirent := FpReadDir(TheDir^); - - if Assigned(ADirent) and (ADirent^.d_name <> '.') and (ADirent^.d_name <> '..') then - begin - lAttrib := FileGetAttr(Dir + ADirent^.d_name); - if ReturnAllSubDirs and ((lAttrib and faDirectory) <> 0) then - begin - SetLength(Result, i + 1); - Result[i].Name := ADirent^.d_name; - Result[i].IsDirectory := true; - Result[i].IsFile := false; - i := i + 1; - end - else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(ADirent^.d_name)) > 0) then - begin - SetLength(Result, i + 1); - Result[i].Name := ADirent^.d_name; - Result[i].IsDirectory := false; - Result[i].IsFile := true; - i := i + 1; - end; - end; - until (ADirent = nil); - - FpCloseDir(TheDir^); - end; + LocalDir := GetExecutionDir(); + LanguageDir := LocalDir.Append('languages'); + UseLocalDirs := LanguageDir.IsDirectory; end; -function TPlatformLinux.GetLogPath: WideString; +function TPlatformLinux.GetLogPath: IPath; begin if UseLocalDirs then Result := GetExecutionDir() else - Result := GetGameUserPath() + 'logs/'; + Result := GetGameUserPath().Append('logs', pdAppend); // create non-existing directories - ForceDirectories(Result); + Result.CreateDirectory(true); end; -function TPlatformLinux.GetGameSharedPath: WideString; +function TPlatformLinux.GetGameSharedPath: IPath; begin if UseLocalDirs then Result := GetExecutionDir() else - Result := IncludeTrailingPathDelimiter(INSTALL_DATADIR); + Result := Path(INSTALL_DATADIR, pdAppend); end; -function TPlatformLinux.GetGameUserPath: WideString; +function TPlatformLinux.GetGameUserPath: IPath; begin if UseLocalDirs then Result := GetExecutionDir() else - Result := GetHomeDir() + '.ultrastardx/'; + Result := GetHomeDir().Append('.ultrastardx', pdAppend); end; {** * Returns the user's home directory terminated by a path delimiter *} -function TPlatformLinux.GetHomeDir(): string; -{$IF FPC_VERSION_INT >= 2002002} +function TPlatformLinux.GetHomeDir(): IPath; var PasswdEntry: PPasswd; -{$IFEND} begin - Result := ''; + Result := PATH_NONE; - {$IF FPC_VERSION_INT >= 2002002} // try to retrieve the info from passwd PasswdEntry := FpGetpwuid(FpGetuid()); if (PasswdEntry <> nil) then - Result := PasswdEntry.pw_dir; - {$IFEND} + Result := Path(PasswdEntry.pw_dir); // fallback if passwd does not contain the path - if (Result = '') then - Result := GetEnvironmentVariable('HOME'); + if (Result.IsUnset) then + Result := Path(GetEnvironmentVariable('HOME')); // add trailing path delimiter (normally '/') - if (Result <> '') then - Result := IncludeTrailingPathDelimiter(Result); + if (Result.IsSet) then + Result := Result.AppendPathDelim(); - {$IF FPC_VERSION_INT >= 2002002} // GetUserDir() is another function that returns a user path. // It uses env-var HOME or a fallback to a temp-dir. //Result := GetUserDir(); - {$IFEND} end; end. diff --git a/unicode/src/base/UPlatformMacOSX.pas b/unicode/src/base/UPlatformMacOSX.pas index 08ad247d..aa1eb7cf 100644 --- a/unicode/src/base/UPlatformMacOSX.pas +++ b/unicode/src/base/UPlatformMacOSX.pas @@ -36,7 +36,8 @@ interface uses Classes, ULog, - UPlatform; + UPlatform, + UPath; type {** @@ -93,13 +94,13 @@ type * GetBundlePath returns the path to the application bundle * UltraStarDeluxe.app. *} - function GetBundlePath: WideString; + function GetBundlePath: IPath; {** * GetApplicationSupportPath returns the path to * $HOME/Library/Application Support/UltraStarDeluxe. *} - function GetApplicationSupportPath: WideString; + function GetApplicationSupportPath: IPath; {** * see the description of @link(Init). @@ -115,31 +116,25 @@ type *} procedure Init; override; - {** - * DirectoryFindFiles returns all entries of a folder with names and - * booleans about their type, i.e. file or directory. - *} - function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray; override; - {** * GetLogPath returns the path for log messages. Currently it is set to * $HOME/Library/Application Support/UltraStarDeluxe/Log. *} - function GetLogPath : WideString; override; + function GetLogPath : IPath; override; {** * GetGameSharedPath returns the path for shared resources. Currently it * is set to /Library/Application Support/UltraStarDeluxe. * However it is not used. *} - function GetGameSharedPath : WideString; override; + function GetGameSharedPath : IPath; override; {** * GetGameUserPath returns the path for user resources. Currently it is * set to $HOME/Library/Application Support/UltraStarDeluxe. * This is where a user can add songs, themes, .... *} - function GetGameUserPath : WideString; override; + function GetGameUserPath : IPath; override; end; implementation @@ -248,87 +243,38 @@ begin ChDir(OldBaseDir); end; -function TPlatformMacOSX.GetBundlePath: WideString; +function TPlatformMacOSX.GetBundlePath: IPath; var - i, pos : integer; + TmpPath: IPath; begin // Mac applications are packaged in folders. // Cutting the last two folders yields the application folder. - - Result := GetExecutionDir(); - for i := 1 to 2 do - begin - pos := Length(Result); - repeat - Delete(Result, pos, 1); - pos := Length(Result); - until (pos = 0) or (Result[pos] = '/'); - end; + TmpPath := GetExecutionDir().GetParent; + Result := TmpPath.GetParent; end; -function TPlatformMacOSX.GetApplicationSupportPath: WideString; +function TPlatformMacOSX.GetApplicationSupportPath: IPath; const PathName : string = '/Library/Application Support/UltraStarDeluxe'; + HomeDir: IPath; begin - Result := GetEnvironmentVariable('HOME') + PathName + '/'; + HomeDir := GetEnvironmentVariable('HOME'); + Result := HomeDir.Append(PathName, true); end; -function TPlatformMacOSX.GetLogPath: WideString; +function TPlatformMacOSX.GetLogPath: IPath; begin - Result := GetApplicationSupportPath + 'Logs'; + Result := GetApplicationSupportPath.Append('Logs'); end; -function TPlatformMacOSX.GetGameSharedPath: WideString; +function TPlatformMacOSX.GetGameSharedPath: IPath; begin Result := GetApplicationSupportPath; end; -function TPlatformMacOSX.GetGameUserPath: WideString; +function TPlatformMacOSX.GetGameUserPath: IPath; begin Result := GetApplicationSupportPath; end; -{ TODO: REMOVE } -function TPlatformMacOSX.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: boolean): TDirectoryEntryArray; -var - i : integer; - TheDir : pdir; - ADirent : pDirent; - lAttrib : integer; -begin - i := 0; - Filter := LowerCase(Filter); - - TheDir := FpOpenDir(Dir); - if Assigned(TheDir) then - begin - repeat - ADirent := FpReadDir(TheDir^); - - if Assigned(ADirent) and (ADirent^.d_name <> '.') and (ADirent^.d_name <> '..') then - begin - lAttrib := FileGetAttr(Dir + ADirent^.d_name); - if ReturnAllSubDirs and ((lAttrib and faDirectory) <> 0) then - begin - SetLength(Result, i + 1); - Result[i].Name := ADirent^.d_name; - Result[i].IsDirectory := true; - Result[i].IsFile := false; - i := i + 1; - end - else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(ADirent^.d_name)) > 0) then - begin - SetLength(Result, i + 1); - Result[i].Name := ADirent^.d_name; - Result[i].IsDirectory := false; - Result[i].IsFile := true; - i := i + 1; - end; - end; - until (ADirent = nil); - - FpCloseDir(TheDir^); - end; -end; - end. diff --git a/unicode/src/base/UPlatformWindows.pas b/unicode/src/base/UPlatformWindows.pas index e198958a..2d0bd212 100644 --- a/unicode/src/base/UPlatformWindows.pas +++ b/unicode/src/base/UPlatformWindows.pas @@ -38,21 +38,19 @@ interface uses Classes, - UPlatform; + UPlatform, + UPath; type TPlatformWindows = class(TPlatform) private - function GetSpecialPath(CSIDL: integer): WideString; + function GetSpecialPath(CSIDL: integer): IPath; public - function DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; override; function TerminateIfAlreadyRunning(var WndTitle: String): Boolean; override; - function GetLogPath: WideString; override; - function GetGameSharedPath: WideString; override; - function GetGameUserPath: WideString; override; - - function CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean; override; + function GetLogPath: IPath; override; + function GetGameSharedPath: IPath; override; + function GetGameUserPath: IPath; override; end; implementation @@ -63,62 +61,10 @@ uses Windows, UConfig; -type - TSearchRecW = record - Time: Integer; - Size: Integer; - Attr: Integer; - Name: WideString; - ExcludeAttr: Integer; - FindHandle: THandle; - FindData: TWin32FindDataW; - end; - -function FindFirstW(const Path: WideString; Attr: Integer; var F: TSearchRecW): Integer; forward; -function FindNextW(var F: TSearchRecW): Integer; forward; -procedure FindCloseW(var F: TSearchRecW); forward; +(* function FindMatchingFileW(var F: TSearchRecW): Integer; forward; function DirectoryExistsW(const Directory: widestring): Boolean; forward; -function FindFirstW(const Path: widestring; Attr: Integer; var F: TSearchRecW): Integer; -const - faSpecial = faHidden or faSysFile or faVolumeID or faDirectory; -begin - F.ExcludeAttr := not Attr and faSpecial; -{$IFDEF Delphi} - F.FindHandle := FindFirstFileW(PWideChar(Path), F.FindData); -{$ELSE} - F.FindHandle := FindFirstFileW(PWideChar(Path), @F.FindData); -{$ENDIF} - if F.FindHandle <> INVALID_HANDLE_VALUE then - begin - Result := FindMatchingFileW(F); - if Result <> 0 then FindCloseW(F); - end else - Result := GetLastError; -end; - -function FindNextW(var F: TSearchRecW): Integer; -begin -{$IFDEF Delphi} - if FindNextFileW(F.FindHandle, F.FindData) then -{$ELSE} - if FindNextFileW(F.FindHandle, @F.FindData) then -{$ENDIF} - Result := FindMatchingFileW(F) - else - Result := GetLastError; -end; - -procedure FindCloseW(var F: TSearchRecW); -begin - if F.FindHandle <> INVALID_HANDLE_VALUE then - begin - Windows.FindClose(F.FindHandle); - F.FindHandle := INVALID_HANDLE_VALUE; - end; -end; - function FindMatchingFileW(var F: TSearchRecW): Integer; var LocalFileTime: TFileTime; @@ -151,6 +97,7 @@ begin Code := GetFileAttributesW(PWideChar(Directory)); Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0); end; +*) //------------------------------ //Start more than One Time Prevention @@ -180,41 +127,6 @@ begin end; end; -function TPlatformWindows.DirectoryFindFiles(Dir, Filter: WideString; ReturnAllSubDirs: Boolean): TDirectoryEntryArray; -var - i : Integer; - SR : TSearchRecW; - Attrib : Integer; -begin - i := 0; - Filter := LowerCase(Filter); - - if FindFirstW(Dir + '*', faAnyFile or faDirectory, SR) = 0 then - repeat - if (SR.Name <> '.') and (SR.Name <> '..') then - begin - Attrib := FileGetAttr(Dir + SR.name); - if ReturnAllSubDirs and ((Attrib and faDirectory) <> 0) then - begin - SetLength( Result, i + 1); - Result[i].Name := SR.name; - Result[i].IsDirectory := true; - Result[i].IsFile := false; - i := i + 1; - end - else if (Length(Filter) = 0) or (Pos( Filter, LowerCase(SR.Name)) > 0) then - begin - SetLength( Result, i + 1); - Result[i].Name := SR.Name; - Result[i].IsDirectory := false; - Result[i].IsFile := true; - i := i + 1; - end; - end; - until FindNextW(SR) <> 0; - FindCloseW(SR); -end; - (** * Returns the path of a special folder. * @@ -225,37 +137,30 @@ end; * CSIDL_PERSONAL (e.g. C:\Documents and Settings\username\My Documents) * CSIDL_MYMUSIC (e.g. C:\Documents and Settings\username\My Documents\My Music) *) -function TPlatformWindows.GetSpecialPath(CSIDL: integer): WideString; +function TPlatformWindows.GetSpecialPath(CSIDL: integer): IPath; var Buffer: array [0..MAX_PATH-1] of WideChar; begin -{$IF Defined(Delphi) or (FPC_VERSION_INT >= 2002002)} // >= 2.2.2 if (SHGetSpecialFolderPathW(0, @Buffer, CSIDL, false)) then - Result := Buffer + Result := Path(Buffer) else -{$IFEND} - Result := ''; + Result := PATH_NONE; end; -function TPlatformWindows.GetLogPath: WideString; +function TPlatformWindows.GetLogPath: IPath; begin Result := GetExecutionDir(); end; -function TPlatformWindows.GetGameSharedPath: WideString; +function TPlatformWindows.GetGameSharedPath: IPath; begin Result := GetExecutionDir(); end; -function TPlatformWindows.GetGameUserPath: WideString; +function TPlatformWindows.GetGameUserPath: IPath; begin - //Result := GetSpecialPath(CSIDL_APPDATA) + PathDelim + 'UltraStarDX' + PathDelim; + //Result := GetSpecialPath(CSIDL_APPDATA).Append('UltraStarDX', pdAppend); Result := GetExecutionDir(); end; -function TPlatformWindows.CopyFile(const Source, Target: WideString; FailIfExists: boolean): boolean; -begin - Result := Windows.CopyFileW(PWideChar(Source), PWideChar(Target), FailIfExists); -end; - end. diff --git a/unicode/src/base/UPlaylist.pas b/unicode/src/base/UPlaylist.pas index cc983a9e..09c196ef 100644 --- a/unicode/src/base/UPlaylist.pas +++ b/unicode/src/base/UPlaylist.pas @@ -34,7 +34,9 @@ interface {$I switches.inc} uses + Classes, USong, + UPath, UPathUtils; type @@ -47,8 +49,8 @@ type APlaylistItem = array of TPlaylistItem; TPlaylist = record - Name: string; - Filename: String; + Name: UTF8String; + Filename: IPath; Items: APlaylistItem; end; @@ -68,20 +70,20 @@ type Playlists: APlaylist; constructor Create; - Procedure LoadPlayLists; - Function LoadPlayList(Index: Cardinal; const Filename: String): Boolean; - Procedure SavePlayList(Index: Cardinal); + procedure LoadPlayLists; + function LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean; + procedure SavePlayList(Index: Cardinal); - Procedure SetPlayList(Index: Cardinal); + procedure SetPlayList(Index: Cardinal); - Function AddPlaylist(const Name: string): Cardinal; - Procedure DelPlaylist(const Index: Cardinal); + function AddPlaylist(const Name: UTF8String): Cardinal; + procedure DelPlaylist(const Index: Cardinal); - Procedure AddItem(const SongID: Cardinal; const iPlaylist: Integer = -1); - Procedure DelItem(const iItem: Cardinal; const iPlaylist: Integer = -1); + procedure AddItem(const SongID: Cardinal; const iPlaylist: Integer = -1); + procedure DelItem(const iItem: Cardinal; const iPlaylist: Integer = -1); - Procedure GetNames(var PLNames: array of string); - Function GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer = -1): Integer; + procedure GetNames(var PLNames: array of UTF8String); + function GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer = -1): Integer; end; {Modes: @@ -95,13 +97,15 @@ type implementation -uses USongs, - ULog, - UMain, - //UFiles, - UGraphic, - UThemes, - SysUtils; +uses + SysUtils, + USongs, + ULog, + UMain, + UFilesystem, + UGraphic, + UThemes, + UUnicodeUtils; //---------- //Create - Construct Class - Dummy for now @@ -117,90 +121,90 @@ end; //---------- Procedure TPlayListManager.LoadPlayLists; var - SR: TSearchRec; Len: Integer; PlayListBuffer: TPlayList; + Iter: IFileIterator; + FileInfo: TFileInfo; begin SetLength(Playlists, 0); - if FindFirst(PlayListPath + '*.upl', 0, SR) = 0 then + Iter := FileSystem.FileFind(PlayListPath.Append('*.upl'), 0); + while (Iter.HasNext) do begin - repeat - Len := Length(Playlists); - SetLength(Playlists, Len +1); + Len := Length(Playlists); + SetLength(Playlists, Len + 1); + + FileInfo := Iter.Next; - if not LoadPlayList (Len, Sr.Name) then - SetLength(Playlists, Len) - else + if not LoadPlayList(Len, FileInfo.Name) then + SetLength(Playlists, Len) + else + begin + // Sort the Playlists - Insertion Sort + PlayListBuffer := Playlists[Len]; + Dec(Len); + while (Len >= 0) AND (CompareText(Playlists[Len].Name, PlayListBuffer.Name) >= 0) do begin - // Sort the Playlists - Insertion Sort - PlayListBuffer := Playlists[Len]; - Dec(Len); - while (Len >= 0) AND (CompareText(Playlists[Len].Name, PlayListBuffer.Name) >= 0) do - begin - Playlists[Len+1] := Playlists[Len]; - Dec(Len); - end; - Playlists[Len+1] := PlayListBuffer; + Playlists[Len+1] := Playlists[Len]; + Dec(Len); end; - - until FindNext(SR) <> 0; - FindClose(SR); - end; + Playlists[Len+1] := PlayListBuffer; + end; + end; end; //---------- //LoadPlayList - Load a Playlist in the Array //---------- -Function TPlayListManager.LoadPlayList(Index: Cardinal; const Filename: String): Boolean; - var - F: TextFile; - Line: String; - PosDelimiter: Integer; - SongID: Integer; - Len: Integer; +function TPlayListManager.LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean; - Function FindSong(Artist, Title: UTF8String): Integer; + function FindSong(Artist, Title: UTF8String): Integer; var I: Integer; begin Result := -1; For I := low(CatSongs.Song) to high(CatSongs.Song) do begin - if (CatSongs.Song[I].Title = Title) AND (CatSongs.Song[I].Artist = Artist) then + if (CatSongs.Song[I].Title = Title) and (CatSongs.Song[I].Artist = Artist) then begin Result := I; Break; end; end; end; + +var + TextStream: TTextFileStream; + Line: UTF8String; + PosDelimiter: Integer; + SongID: Integer; + Len: Integer; + FilenameAbs: IPath; begin - if not FileExists(PlayListPath + Filename) then - begin - Log.LogError('Could not load Playlist: ' + Filename); - Result := False; - Exit; + //Load File + try + FilenameAbs := PlaylistPath.Append(Filename); + TextStream := TTextFileStream.Create(FilenameAbs, fmOpenRead); + except + begin + Log.LogError('Could not load Playlist: ' + FilenameAbs.ToNative); + Result := False; + Exit; + end; end; Result := True; - //Load File - AssignFile(F, PlayListPath + FileName); - Reset(F); - //Set Filename - PlayLists[Index].Filename := Filename; - PlayLists[Index].Name := ''; + Playlists[Index].Filename := Filename; + Playlists[Index].Name := ''; //Read Until End of File - While not Eof(F) do + while TextStream.ReadLine(Line) do begin - //Read Curent Line - Readln(F, Line); - if (Length(Line) > 0) then begin - PosDelimiter := Pos(':', Line); - if (PosDelimiter <> 0) then + PosDelimiter := UTF8Pos(':', Line); + if (PosDelimiter <> 0) then begin //Comment or Name String if (Line[1] = '#') then @@ -224,7 +228,7 @@ begin PlayLists[Index].Items[Len].Artist := Trim(copy(Line, 1, PosDelimiter - 1)); PlayLists[Index].Items[Len].Title := Trim(copy(Line, PosDelimiter + 1, Length(Line) - PosDelimiter)); end - else Log.LogError('Could not find Song in Playlist: ' + PlayLists[Index].Filename + ', ' + Line); + else Log.LogError('Could not find Song in Playlist: ' + PlayLists[Index].Filename.ToNative + ', ' + Line); end; end; end; @@ -233,71 +237,70 @@ begin //If no special name is given, use Filename if PlayLists[Index].Name = '' then begin - PlayLists[Index].Name := ChangeFileExt(FileName, ''); + PlayLists[Index].Name := FileName.SetExtension('').ToUTF8; end; //Finish (Close File) - CloseFile(F); + TextStream.Free; end; -//---------- -//SavePlayList - Saves the specified Playlist -//---------- -Procedure TPlayListManager.SavePlayList(Index: Cardinal); +{** + * Saves the specified Playlist + *} +procedure TPlayListManager.SavePlayList(Index: Cardinal); var - F: TextFile; + TextStream: TTextFileStream; + PlaylistFile: IPath; I: Integer; begin - if (Not FileExists(PlaylistPath + Playlists[Index].Filename)) OR (Not FileisReadOnly(PlaylistPath + Playlists[Index].Filename)) then - begin + PlaylistFile := PlaylistPath.Append(Playlists[Index].Filename); - //open File for Rewriting - AssignFile(F, PlaylistPath + Playlists[Index].Filename); - try - try - Rewrite(F); + // cannot update read-only file + if PlaylistFile.IsFile() and PlaylistFile.IsReadOnly() then + Exit; - //Write Version (not nessecary but helpful) - WriteLn(F, '######################################'); - WriteLn(F, '#Ultrastar Deluxe Playlist Format v1.0'); - WriteLn(F, '#Playlist "' + Playlists[Index].Name + '" with ' + InttoStr(Length(Playlists[Index].Items)) + ' Songs.'); - WriteLn(F, '######################################'); + // open file for rewriting + TextStream := TTextFileStream.Create(PlaylistFile, fmCreate); + try + // Write version (not nessecary but helpful) + TextStream.WriteLine('######################################'); + TextStream.WriteLine('#Ultrastar Deluxe Playlist Format v1.0'); + TextStream.WriteLine(Format('#Playlist %s with %d Songs.', + [ Playlists[Index].Name, Length(Playlists[Index].Items) ])); + TextStream.WriteLine('######################################'); - //Write Name Information - WriteLn(F, '#Name: ' + Playlists[Index].Name); + // Write name information + TextStream.WriteLine('#Name: ' + Playlists[Index].Name); - //Write Song Information - WriteLn(F, '#Songs:'); + // Write song information + TextStream.WriteLine('#Songs:'); - For I := 0 to high(Playlists[Index].Items) do - begin - WriteLn(F, Playlists[Index].Items[I].Artist + ' : ' + Playlists[Index].Items[I].Title); - end; - except - log.LogError('Could not write Playlistfile "' + Playlists[Index].Name + '"'); - end; - finally - CloseFile(F); + for I := 0 to high(Playlists[Index].Items) do + begin + TextStream.WriteLine(Playlists[Index].Items[I].Artist + ' : ' + Playlists[Index].Items[I].Title); end; + except + Log.LogError('Could not write Playlistfile "' + Playlists[Index].Name + '"'); end; + TextStream.Free; end; -//---------- -//SetPlayList - Display a Playlist in CatSongs -//---------- -Procedure TPlayListManager.SetPlayList(Index: Cardinal); +{** + * Display a Playlist in CatSongs + *} +procedure TPlayListManager.SetPlayList(Index: Cardinal); var I: Integer; begin - If (Int(Index) > High(PlayLists)) then + if (Int(Index) > High(PlayLists)) then exit; //Hide all Songs - For I := 0 to high(CatSongs.Song) do + for I := 0 to high(CatSongs.Song) do CatSongs.Song[I].Visible := False; //Show Songs in PL - For I := 0 to high(PlayLists[Index].Items) do + for I := 0 to high(PlayLists[Index].Items) do begin CatSongs.Song[PlayLists[Index].Items[I].SongID].Visible := True; end; @@ -324,31 +327,30 @@ end; //---------- //AddPlaylist - Adds a Playlist and Returns the Index //---------- -Function TPlayListManager.AddPlaylist(const Name: string): Cardinal; +function TPlayListManager.AddPlaylist(const Name: UTF8String): cardinal; var I: Integer; + PlaylistFile: IPath; begin Result := Length(Playlists); SetLength(Playlists, Result + 1); // Sort the Playlists - Insertion Sort - while (Result > 0) AND (CompareText(Playlists[Result - 1].Name, Name) >= 0) do + while (Result > 0) and (CompareText(Playlists[Result - 1].Name, Name) >= 0) do begin Dec(Result); Playlists[Result+1] := Playlists[Result]; end; - Playlists[Result].Name := Name; + Playlists[Result].Name := Name; I := 1; - if (not FileExists(PlaylistPath + Name + '.upl')) then - Playlists[Result].Filename := Name + '.upl' - else + PlaylistFile := PlaylistPath.Append(Name + '.upl'); + while (PlaylistFile.Exists) do begin - repeat - Inc(I); - until not FileExists(PlaylistPath + Name + InttoStr(I) + '.upl'); - Playlists[Result].Filename := Name + InttoStr(I) + '.upl'; + Inc(I); + PlaylistFile := PlaylistPath.Append(Name + InttoStr(I) + '.upl'); end; + Playlists[Result].Filename := PlaylistFile; //Save new Playlist SavePlayList(Result); @@ -357,28 +359,28 @@ end; //---------- //DelPlaylist - Deletes a Playlist //---------- -Procedure TPlayListManager.DelPlaylist(const Index: Cardinal); +procedure TPlayListManager.DelPlaylist(const Index: Cardinal); var I: Integer; - Filename: String; + Filename: IPath; begin - If Int(Index) > High(Playlists) then + if Int(Index) > High(Playlists) then Exit; - Filename := PlaylistPath + Playlists[Index].Filename; + Filename := PlaylistPath.Append(Playlists[Index].Filename); //If not FileExists or File is not Writeable then exit - If (Not FileExists(Filename)) OR (FileisReadOnly(Filename)) then + if (not Filename.IsFile()) or (Filename.IsReadOnly()) then Exit; //Delete Playlist from FileSystem - if Not DeleteFile(Filename) then + if not Filename.DeleteFile() then Exit; //Delete Playlist from Array //move all PLs to the Hole - For I := Index to High(Playlists)-1 do + for I := Index to High(Playlists)-1 do PlayLists[I] := PlayLists[I+1]; //Delete last Playlist @@ -471,7 +473,7 @@ end; //---------- //GetNames - Writes Playlist Names in a Array //---------- -Procedure TPlayListManager.GetNames(var PLNames: array of String); +procedure TPlayListManager.GetNames(var PLNames: array of UTF8String); var I: Integer; Len: Integer; diff --git a/unicode/src/base/USkins.pas b/unicode/src/base/USkins.pas index a19b7546..6ef5c596 100644 --- a/unicode/src/base/USkins.pas +++ b/unicode/src/base/USkins.pas @@ -33,31 +33,34 @@ interface {$I switches.inc} +uses + UPath; + type TSkinTexture = record Name: string; - FileName: string; + FileName: IPath; end; TSkinEntry = record Theme: string; Name: string; - Path: string; - FileName: string; + Path: IPath; + FileName: IPath; Creator: string; // not used yet end; TSkin = class Skin: array of TSkinEntry; SkinTexture: array of TSkinTexture; - SkinPath: string; + SkinPath: IPath; Color: integer; constructor Create; procedure LoadList; - procedure ParseDir(Dir: string); - procedure LoadHeader(FileName: string); + procedure ParseDir(Dir: IPath); + procedure LoadHeader(FileName: IPath); procedure LoadSkin(Name: string); - function GetTextureFileName(TextureName: string): string; + function GetTextureFileName(TextureName: string): IPath; function GetSkinNumber(Name: string): integer; procedure onThemeChange; end; @@ -74,7 +77,8 @@ uses UIni, ULog, UMain, - UPathUtils; + UPathUtils, + UFileSystem; constructor TSkin.Create; begin @@ -86,45 +90,43 @@ end; procedure TSkin.LoadList; var - SR: TSearchRec; + Iter: IFileIterator; + DirInfo: TFileInfo; begin - if FindFirst(SkinsPath+'*', faDirectory, SR) = 0 then + Iter := FileSystem.FileFind(SkinsPath.Append('*'), faDirectory); + while Iter.HasNext do begin - repeat - if (SR.Name <> '.') and (SR.Name <> '..') then - ParseDir(SkinsPath + SR.Name + PathDelim); - until FindNext(SR) <> 0; - end; // if - FindClose(SR); + DirInfo := Iter.Next(); + if (not DirInfo.Name.Equals('.')) and (not DirInfo.Name.Equals('..')) then + ParseDir(SkinsPath.Append(DirInfo.Name, pdAppend)); + end; end; -procedure TSkin.ParseDir(Dir: string); +procedure TSkin.ParseDir(Dir: IPath); var - SR: TSearchRec; + Iter: IFileIterator; + IniInfo: TFileInfo; begin - if FindFirst(Dir + '*.ini', faAnyFile, SR) = 0 then + Iter := FileSystem.FileFind(Dir.Append('*.ini'), 0); + while Iter.HasNext do begin - repeat - - if (SR.Name <> '.') and (SR.Name <> '..') then - LoadHeader(Dir + SR.Name); - - until FindNext(SR) <> 0; + IniInfo := Iter.Next; + LoadHeader(Dir.Append(IniInfo.Name)); end; end; -procedure TSkin.LoadHeader(FileName: string); +procedure TSkin.LoadHeader(FileName: IPath); var SkinIni: TMemIniFile; S: integer; begin - SkinIni := TMemIniFile.Create(FileName); + SkinIni := TMemIniFile.Create(FileName.ToNative); S := Length(Skin); SetLength(Skin, S+1); - Skin[S].Path := IncludeTrailingPathDelimiter(ExtractFileDir(FileName)); - Skin[S].FileName := ExtractFileName(FileName); + Skin[S].Path := FileName.GetPath; + Skin[S].FileName := FileName.GetName; Skin[S].Theme := SkinIni.ReadString('Skin', 'Theme', ''); Skin[S].Name := SkinIni.ReadString('Skin', 'Name', ''); Skin[S].Creator := SkinIni.ReadString('Skin', 'Creator', ''); @@ -142,7 +144,7 @@ begin S := GetSkinNumber(Name); SkinPath := Skin[S].Path; - SkinIni := TMemIniFile.Create(SkinPath + Skin[S].FileName); + SkinIni := TMemIniFile.Create(SkinPath.Append(Skin[S].FileName).ToNative); SL := TStringList.Create; SkinIni.ReadSection('Textures', SL); @@ -151,30 +153,29 @@ begin for T := 0 to SL.Count-1 do begin SkinTexture[T].Name := SL.Strings[T]; - SkinTexture[T].FileName := SkinIni.ReadString('Textures', SL.Strings[T], ''); + SkinTexture[T].FileName := Path(SkinIni.ReadString('Textures', SL.Strings[T], '')); end; SL.Free; SkinIni.Free; end; -function TSkin.GetTextureFileName(TextureName: string): string; +function TSkin.GetTextureFileName(TextureName: string): IPath; var T: integer; begin - Result := ''; + Result := PATH_NONE; for T := 0 to High(SkinTexture) do begin - if ( SkinTexture[T].Name = TextureName ) and - ( SkinTexture[T].FileName <> '' ) then + if (SkinTexture[T].Name = TextureName) and + (SkinTexture[T].FileName.IsSet) then begin - Result := SkinPath + SkinTexture[T].FileName; + Result := SkinPath.Append(SkinTexture[T].FileName); end; end; - if ( TextureName <> '' ) and - ( Result <> '' ) then + if (TextureName <> '') and (Result.IsSet) then begin //Log.LogError('', '-----------------------------------------'); //Log.LogError(TextureName+' - '+ Result, 'TSkin.GetTextureFileName'); diff --git a/unicode/src/base/USong.pas b/unicode/src/base/USong.pas index 86349359..203ff517 100644 --- a/unicode/src/base/USong.pas +++ b/unicode/src/base/USong.pas @@ -57,7 +57,9 @@ uses {$ENDIF} UCatCovers, UXMLSong, - UTextEncoding; + UTextEncoding, + UFilesystem, + UPath; type @@ -74,6 +76,7 @@ type end; TSong = class + private FileLineNo : integer; // line, which is read last, for error reporting function EncodeFilename(Filename: string): string; @@ -81,19 +84,22 @@ type procedure ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: UTF8String); procedure NewSentence(LineNumberP: integer; Param1, Param2: integer); - function ReadTXTHeader( const aFileName : WideString ): boolean; - function ReadXMLHeader( const aFileName : WideString ): boolean; + function ReadTXTHeader(const aFileName: IPath): boolean; + function ReadXMLHeader(const aFileName: IPath): boolean; + + function GetFolderCategory(const aFileName: IPath): UTF8String; + function FindSongFile(Dir: IPath; Mask: UTF8String): IPath; + public - Path: WideString; - Folder: WideString; // for sorting by folder - fFileName, - FileName: WideString; + Path: IPath; // kust path component of file (only set if file was found) + Folder: UTF8String; // for sorting by folder (only set if file was found) + FileName: IPath; // just name component of file (only set if file was found) // filenames - Cover: WideString; - Mp3: WideString; - Background: WideString; - Video: WideString; + Cover: IPath; + Mp3: IPath; + Background: IPath; + Video: IPath; // sorting methods Genre: UTF8String; @@ -127,7 +133,7 @@ type OrderTyp: integer; // type of sorting for this button (0=name) CatNumber: integer; // Count of Songs in Category for Cats and Number of Song in Category for Songs - SongFile: TextFile; // all procedures in this unit operate on this file + //SongFile: TextFile; // all procedures in this unit operate on this file Base : array[0..1] of integer; Rel : array[0..1] of integer; @@ -140,7 +146,7 @@ type constructor Create(); overload; - constructor Create( const aFileName : WideString ); overload; + constructor Create(const aFileName : IPath); overload; function LoadSong: boolean; function LoadXMLSong: boolean; function Analyse(): boolean; @@ -167,58 +173,61 @@ begin inherited; end; -constructor TSong.Create( const aFileName: WideString ); - // This may be changed, when we rewrite song select code. - // it is some kind of dirty, but imho the best possible - // solution as we do atm not support nested categorys. - // it works like the folder sorting in 1.0.1a - // folder is set to the first folder under the songdir - // so songs ~/.ultrastardx/songs/punk is in the same - // category as songs in shared/ultrastardx/songs are. - // note: folder is just the name of a category it has - // nothing to do with the path used for file loading - function GetFolderCategory: WideString; - var - I: Integer; - P: Integer; //position of next path delimiter - begin - Result := 'Unknown'; //default folder category, if we can't locate the song dir +// This may be changed, when we rewrite song select code. +// it is some kind of dirty, but imho the best possible +// solution as we do atm not support nested categorys. +// it works like the folder sorting in 1.0.1a +// folder is set to the first folder under the songdir +// so songs ~/.ultrastardx/songs/punk is in the same +// category as songs in shared/ultrastardx/songs are. +// note: folder is just the name of a category it has +// nothing to do with the path used for file loading +function TSong.GetFolderCategory(const aFileName: IPath): UTF8String; +var + I: Integer; + CurSongPath: IPath; + CurSongPathRel: IPath; +begin + Result := 'Unknown'; //default folder category, if we can't locate the song dir - for I := 0 to SongPaths.Count-1 do - if (AnsiStartsText(SongPaths.Strings[I], aFilename)) then + for I := 0 to SongPaths.Count-1 do + begin + CurSongPath := SongPaths[I] as IPath; + if (aFileName.IsChildOf(CurSongPath, false)) then + begin + if (aFileName.IsChildOf(CurSongPath, true)) then begin - P := PosEx(PathDelim, aFilename, Length(SongPaths.Strings[I]) + 1); - - If (P > 0) then - begin - // we have found the category name => get it - Result := copy(self.Path, Length(SongPaths.Strings[I]) + 1, P - Length(SongPaths.Strings[I]) - 1); - end - else - begin - // songs are in the "root" of the songdir => use songdir for the categorys name - Result := SongPaths.Strings[I]; - end; - - Exit; + // songs are in the "root" of the songdir => use songdir for the categorys name + Result := CurSongPath.ToUTF8; // TODO: remove trailing path-delim? + end + else + begin + // use the first subdirectory below CurSongPath as the category name + CurSongPathRel := aFileName.GetRelativePath(CurSongPath.AppendPathDelim); + Result := CurSongPathRel.SplitDirs[0].RemovePathDelim.ToUTF8; end; + Exit; + end; end; +end; + +constructor TSong.Create(const aFileName: IPath); begin inherited Create(); Mult := 1; MultBPM := 4; - fFileName := aFileName; LastError := ''; - if fileexists( aFileName ) then + Self.Path := aFileName.GetPath; + Self.FileName := aFileName.GetName; + Self.Folder := GetFolderCategory(aFileName); + + (* + if (aFileName.IsFile) then begin - self.Path := ExtractFilePath( aFileName ); - self.Folder := GetFolderCategory; - self.FileName := ExtractFileName( aFileName ); - (* - if ReadTXTHeader( aFileName ) then + if ReadTXTHeader(aFileName) then begin LoadSong(); end @@ -227,8 +236,21 @@ begin Log.LogError('Error Loading SongHeader, abort Song Loading'); Exit; end; - *) end; + *) +end; + +function TSong.FindSongFile(Dir: IPath; Mask: UTF8String): IPath; +var + Iter: IFileIterator; + FileInfo: TFileInfo; + FileName: IPath; +begin + Iter := FileSystem.FileFind(Dir.Append(Mask), faDirectory); + if (Iter.HasNext) then + Result := Iter.Next.Name + else + Result := PATH_NONE; end; function TSong.EncodeFilename(Filename: string): string; @@ -247,7 +269,7 @@ end; //Load TXT Song function TSong.LoadSong(): boolean; var - TempC: char; + TempC: AnsiChar; Text: UTF8String; Count: integer; Both: boolean; @@ -256,15 +278,19 @@ var Param3: integer; ParamS: UTF8String; I: integer; + NotesFound: boolean; + TextStream: TTextFileStream; + FileNamePath: IPath; begin Result := false; LastError := ''; - if not FileExists(Path + PathDelim + FileName) then + FileNamePath := Path.Append(FileName); + if not FileNamePath.IsFile() then begin LastError := 'ERROR_CORRUPT_SONG_FILE_NOT_FOUND'; - Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'TSong.LoadSong()'); - exit; + Log.LogError('File not found: "' + FileNamePath.ToNative + '"', 'TSong.LoadSong()'); + Exit; end; MultBPM := 4; // multiply beat-count of note by 4 @@ -275,34 +301,42 @@ begin if Length(Player) = 2 then Both := true; + {$MESSAGE warn 'TODO: TSong.LoadSong'} + + (* try // Open song file for reading..... - FileMode := fmOpenRead; - AssignFile(SongFile, fFileName); - Reset(SongFile); + TextStream := nil; + TextStream := TBinaryFileStream.Create(FullFileName, fmOpenRead); //Clear old Song Header - if (self.Path = '') then - self.Path := ExtractFilePath(FileName); + if (Self.Path.IsUnset) then + Self.Path := FullFileName.GetPath(); - if (self.FileName = '') then - self.Filename := ExtractFileName(FileName); + if (Self.FileName.IsUnset) then + Self.Filename := FullFileName.GetName(); - FileLineNo := 0; //Search for Note Begining - repeat - ReadLn(SongFile, Text); + FileLineNo := 0; + NotesFound := false; + while (TextStream.ReadLine(Text)) do + begin Inc(FileLineNo); - - if (Eof(SongFile)) then - begin //Song File Corrupted - No Notes - CloseFile(SongFile); - Log.LogError('Could not load txt File, no Notes found: ' + FileName); - LastError := 'ERROR_CORRUPT_SONG_NO_NOTES'; - Exit; + if (Length(Text) > 0) and (Text[0] in [':', 'F', '*']) then + begin + TempC := Text[0]; + NotesFound := true; + Break; end; - Read(SongFile, TempC); - until ((TempC = ':') or (TempC = 'F') or (TempC = '*')); + end; + + if (not NotesFound) then + begin //Song File Corrupted - No Notes + TextStream.Free; + Log.LogError('Could not load txt File, no Notes found: ' + FileName); + LastError := 'ERROR_CORRUPT_SONG_NO_NOTES'; + Exit; + end; SetLength(Lines, 2); for Count := 0 to High(Lines) do @@ -327,7 +361,7 @@ begin while (TempC <> 'E') and (not EOF(SongFile)) do begin - if (TempC = ':') or (TempC = '*') or (TempC = 'F') then + if (TempC in [':', '*', 'F']) then begin // read notes Read(SongFile, Param1); @@ -398,7 +432,7 @@ begin if (Length(Lines[I].Line) < 2) then begin LastError := 'ERROR_CORRUPT_SONG_NO_BREAKS'; - Log.LogError('Error Loading File, Can''t find any Linebreaks: "' + fFileName + '"'); + Log.LogError('Error Loading File, Can''t find any Linebreaks: "' + FullFileName + '"'); exit; end; @@ -425,10 +459,11 @@ begin end; LastError := 'ERROR_CORRUPT_SONG_ERROR_IN_LINE'; - Log.LogError('Error Loading File: "' + fFileName + '" in Line ' + inttostr(FileLineNo)); + Log.LogError('Error Loading File: "' + FullFileName + '" in Line ' + inttostr(FileLineNo)); exit; end; - + *) + Result := true; end; @@ -447,13 +482,15 @@ var NoteType: char; SentenceEnd, Rest, Time: integer; Parser: TParser; + FileNamePath: IPath; begin Result := false; LastError := ''; - if not FileExists(Path + PathDelim + FileName) then + FileNamePath := Path.Append(FileName); + if not FileNamePath.IsFile() then begin - Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'TSong.LoadSong()'); + Log.LogError('File not found: "' + FileNamePath.ToNative + '"', 'TSong.LoadSong()'); exit; end; @@ -491,7 +528,7 @@ begin //Try to Parse the Song - if Parser.ParseSong(Path + PathDelim + FileName) then + if Parser.ParseSong(FileNamePath) then begin //Writeln('XML Inputfile Parsed succesful'); @@ -558,7 +595,7 @@ begin end else begin - Log.LogError('Could not parse Inputfile: ' + Path + PathDelim + FileName); + Log.LogError('Could not parse Inputfile: ' + FileNamePath.ToNative); exit; end; @@ -570,10 +607,11 @@ begin Result := true; end; -function TSong.ReadXMLHeader(const aFileName : WideString): boolean; +function TSong.ReadXMLHeader(const aFileName : IPath): boolean; var Done : byte; Parser : TParser; + FileNamePath: IPath; begin Result := true; Done := 0; @@ -582,7 +620,8 @@ begin Parser := TParser.Create; Parser.Settings.DashReplacement := '~'; - if Parser.ParseSong(self.Path + self.FileName) then + FileNamePath := Self.Path.Append(Self.FileName); + if Parser.ParseSong(FileNamePath) then begin //----------- //Required Attributes @@ -601,9 +640,9 @@ begin Done := Done or 2; //MP3 File //Test if Exists - self.Mp3 := platform.FindSongFile(Path, '*.mp3'); + Self.Mp3 := FindSongFile(Self.Path, '*.mp3'); //Add Mp3 Flag to Done - if (FileExists(self.Path + self.Mp3)) then + if (Self.Path.Append(Self.Mp3).IsFile()) then Done := Done or 4; //Beats per Minute @@ -624,10 +663,10 @@ begin self.GAP := Parser.SongInfo.Header.Gap; //Cover Picture - self.Cover := platform.FindSongFile(Path, '*[CO].jpg'); + self.Cover := FindSongFile(Path, '*[CO].jpg'); //Background Picture - self.Background := platform.FindSongFile(Path, '*[BG].jpg'); + self.Background := FindSongFile(Path, '*[BG].jpg'); // Video File // self.Video := Value @@ -648,7 +687,7 @@ begin self.Language := Parser.SongInfo.Header.Language; end else - Log.LogError('File Incomplete or not SingStar XML (A): ' + aFileName); + Log.LogError('File Incomplete or not SingStar XML (A): ' + aFileName.ToNative); Parser.Free; @@ -657,36 +696,35 @@ begin begin Result := false; if (Done and 8) = 0 then //No BPM Flag - Log.LogError('BPM Tag Missing: ' + self.FileName) + Log.LogError('BPM Tag Missing: ' + self.FileName.ToNative) else if (Done and 4) = 0 then //No MP3 Flag - Log.LogError('MP3 Tag/File Missing: ' + self.FileName) + Log.LogError('MP3 Tag/File Missing: ' + self.FileName.ToNative) else if (Done and 2) = 0 then //No Artist Flag - Log.LogError('Artist Tag Missing: ' + self.FileName) + Log.LogError('Artist Tag Missing: ' + self.FileName.ToNative) else if (Done and 1) = 0 then //No Title Flag - Log.LogError('Title Tag Missing: ' + self.FileName) + Log.LogError('Title Tag Missing: ' + self.FileName.ToNative) else //unknown Error - Log.LogError('File Incomplete or not SingStar XML (B - '+ inttostr(Done) +'): ' + aFileName); + Log.LogError('File Incomplete or not SingStar XML (B - '+ inttostr(Done) +'): ' + aFileName.ToNative); end; end; -function TSong.ReadTXTHeader(const aFileName : WideString): boolean; - - {** - * "International" StrToFloat variant. Uses either ',' or '.' as decimal - * separator. - *} - function StrToFloatI18n(const Value: string): extended; - var - TempValue : string; - begin - TempValue := Value; - if (Pos(',', TempValue) <> 0) then - TempValue[Pos(',', TempValue)] := '.'; - Result := StrToFloatDef(TempValue, 0); - end; +{** + * "International" StrToFloat variant. Uses either ',' or '.' as decimal + * separator. + *} +function StrToFloatI18n(const Value: string): extended; +var + TempValue : string; +begin + TempValue := Value; + if (Pos(',', TempValue) <> 0) then + TempValue[Pos(',', TempValue)] := '.'; + Result := StrToFloatDef(TempValue, 0); +end; +function TSong.ReadTXTHeader(const aFileName : IPath): boolean; var Line, Identifier: string; Value: string; @@ -697,6 +735,10 @@ begin Result := true; Done := 0; + {$message warn 'TSong.ReadTXTHeader'} + + (* + //Read first Line ReadLn (SongFile, Line); @@ -913,7 +955,7 @@ begin else //unknown Error Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + aFileName); end; - + *) end; function TSong.GetErrorLineNo: integer; @@ -1034,7 +1076,8 @@ begin end else begin //use old line if it there were no notes added since last call of NewSentence - Log.LogError('Error loading Song, sentence w/o note found in line ' + InttoStr(FileLineNo) + ': ' + Filename); + Log.LogError('Error loading Song, sentence w/o note found in line ' + + InttoStr(FileLineNo) + ': ' + Filename.ToNative); end; Lines[LineNumberP].Line[Lines[LineNumberP].High].HighNote := -1; @@ -1076,7 +1119,7 @@ begin Encoding := DEFAULT_ENCODING; //Required Information - Mp3 := ''; + Mp3 := PATH_NONE; SetLength(BPM, 0); GAP := 0; @@ -1084,9 +1127,9 @@ begin Finish := 0; //Additional Information - Background := ''; - Cover := ''; - Video := ''; + Background := PATH_NONE; + Cover := PATH_NONE; + Video := PATH_NONE; VideoGAP := 0; NotesGAP := 0; Resolution := 4; @@ -1096,7 +1139,8 @@ begin end; function TSong.Analyse(): boolean; - +var + SongFile: TTextFileStream; begin Result := false; @@ -1104,20 +1148,17 @@ begin FileLineNo := 0; //Open File and set File Pointer to the beginning - AssignFile(SongFile, self.Path + self.FileName); - + SongFile := TTextFileStream.Create(Self.Path.Append(Self.FileName), fmOpenRead); try - Reset(SongFile); - //Clear old Song Header - self.clear; + Self.clear; //Read Header - Result := self.ReadTxTHeader( FileName ) + Result := self.ReadTxTHeader(FileName) //And Close File finally - CloseFile(SongFile); + SongFile.Free; end; end; diff --git a/unicode/src/base/USongs.pas b/unicode/src/base/USongs.pas index 2b2bcf49..55ddc5e9 100644 --- a/unicode/src/base/USongs.pas +++ b/unicode/src/base/USongs.pas @@ -40,28 +40,26 @@ interface {$ENDIF} uses + SysUtils, + Classes, {$IFDEF MSWINDOWS} Windows, DirWatch, {$ELSE} {$IFNDEF DARWIN} - syscall, + syscall, {$ENDIF} baseunix, UnixType, {$ENDIF} - SysUtils, - Classes, UPlatform, ULog, UTexture, UCommon, - {$IFDEF DARWIN} - cthreads, - {$ENDIF} {$IFDEF USE_PSEUDO_THREAD} - PseudoThread, + PseudoThread, {$ENDIF} + UPath, USong, UCatCovers; @@ -107,11 +105,10 @@ type procedure LoadSongList; // load all songs - procedure BrowseDir(Dir: widestring); // should return number of songs in the future - procedure BrowseTXTFiles(Dir: widestring); - procedure BrowseXMLFiles(Dir: widestring); + procedure BrowseDir(Dir: IPath); // should return number of songs in the future + procedure BrowseTXTFiles(Dir: IPath); + procedure BrowseXMLFiles(Dir: IPath); procedure Sort(Order: integer); - function FindSongFile(Dir, Mask: widestring): widestring; property Processing: boolean read fProcessing; end; @@ -165,6 +162,7 @@ uses UIni, UPathUtils, UNote, + UFilesystem, UUnicodeUtils; constructor TSongs.Create(); @@ -239,7 +237,7 @@ begin // browse directories for I := 0 to SongPaths.Count-1 do - BrowseDir(SongPaths[I]); + BrowseDir(SongPaths[I] as IPath); if assigned(CatSongs) then CatSongs.Refresh; @@ -271,13 +269,13 @@ begin Resume(); end; -procedure TSongs.BrowseDir(Dir: widestring); +procedure TSongs.BrowseDir(Dir: IPath); begin BrowseTXTFiles(Dir); BrowseXMLFiles(Dir); end; -procedure TSongs.BrowseTXTFiles(Dir: widestring); +procedure TSongs.BrowseTXTFiles(Dir: IPath); var i: integer; Files: TDirectoryEntryArray; @@ -285,27 +283,27 @@ var begin try - Files := Platform.DirectoryFindFiles(Dir, '.txt', true) + Files := DirectoryFindFiles(Dir, '.txt', true) except - Log.LogError('Couldn''t deal with directory/file: ' + Dir + ' in TSongs.BrowseTXTFiles') + Log.LogError('Couldn''t deal with directory/file: ' + Dir.ToNative + ' in TSongs.BrowseTXTFiles') end; for i := 0 to Length(Files) - 1 do begin if Files[i].IsDirectory then begin - BrowseTXTFiles(Dir + Files[i].Name + PathDelim); //Recursive Call + BrowseTXTFiles(Dir.Append(Files[i].Name, pdAppend)); //Recursive Call end else begin - lSong := TSong.create(Dir + Files[i].Name); + lSong := TSong.Create(Dir.Append(Files[i].Name)); if lSong.Analyse then SongList.add(lSong) else begin - Log.LogError('AnalyseFile failed for "' + Files[i].Name + '".'); - freeandnil(lSong); + Log.LogError('AnalyseFile failed for "' + Files[i].Name.ToNative + '".'); + FreeAndNil(lSong); end; end; @@ -314,7 +312,7 @@ begin end; -procedure TSongs.BrowseXMLFiles(Dir: widestring); +procedure TSongs.BrowseXMLFiles(Dir: IPath); var i: integer; Files: TDirectoryEntryArray; @@ -322,26 +320,26 @@ var begin try - Files := Platform.DirectoryFindFiles(Dir, '.xml', true) + Files := DirectoryFindFiles(Dir, '.xml', true) except - Log.LogError('Couldn''t deal with directory/file: ' + Dir + ' in TSongs.BrowseXMLFiles') + Log.LogError('Couldn''t deal with directory/file: ' + Dir.ToNative + ' in TSongs.BrowseXMLFiles') end; for i := 0 to Length(Files) - 1 do begin if Files[i].IsDirectory then begin - BrowseXMLFiles(Dir + Files[i].Name + PathDelim); // Recursive Call + BrowseXMLFiles(Dir.Append(Files[i].Name, pdAppend)); // Recursive Call end else begin - lSong := TSong.create(Dir + Files[i].Name); + lSong := TSong.Create(Dir.Append(Files[i].Name)); if lSong.AnalyseXML then SongList.add(lSong) else begin - Log.LogError('AnalyseFile failed for "' + Files[i].Name + '".'); + Log.LogError('AnalyseFile failed for "' + Files[i].Name.ToNative + '".'); freeandnil(lSong); end; @@ -377,7 +375,7 @@ end; function CompareByFolder(Song1, Song2: Pointer): integer; begin - Result := CompareText(TSong(Song1).Folder, TSong(Song2).Folder); + Result := UTF8CompareText(TSong(Song1).Folder, TSong(Song2).Folder); end; function CompareByLanguage(Song1, Song2: Pointer): integer; @@ -419,18 +417,6 @@ begin MergeSort(SongList, CompareFunc); end; -function TSongs.FindSongFile(Dir, Mask: widestring): widestring; -var - SR: TSearchRec; // for parsing song directory -begin - Result := ''; - if FindFirst(Dir + Mask, faDirectory, SR) = 0 then - begin - Result := SR.Name; - end; // if - FindClose(SR); -end; - procedure TCatSongs.SortSongs(); begin case Ini.Sorting of @@ -603,7 +589,7 @@ begin end; sFolder: begin - if (CompareText(CurCategory, CurSong.Folder) <> 0) then + if (UTF8CompareText(CurCategory, CurSong.Folder) <> 0) then begin CurCategory := CurSong.Folder; // add folder tab @@ -831,7 +817,7 @@ begin begin case Filter of fltAll: - TmpString := Song[I].Artist + ' ' + Song[i].Title + ' ' + UTF8Encode(Song[i].Folder); + TmpString := Song[I].Artist + ' ' + Song[i].Title + ' ' + Song[i].Folder; fltTitle: TmpString := Song[I].Title; fltArtist: diff --git a/unicode/src/base/UTextEncoding.pas b/unicode/src/base/UTextEncoding.pas index 966064c0..a0d455dd 100644 --- a/unicode/src/base/UTextEncoding.pas +++ b/unicode/src/base/UTextEncoding.pas @@ -1,239 +1,239 @@ -{* 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: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/menu/UMenuText.pas $ - * $Id: UMenuText.pas 1485 2008-10-28 20:16:05Z tobigun $ - *} - -unit UTextEncoding; - -interface - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - -uses - SysUtils; - -type - TEncoding = ( - encLocale, // current locale (needs cwstring on linux) - encUTF8, // UTF-8 - encCP1250, // Windows-1250 Central/Eastern Europe (used by Ultrastar) - encCP1252 // Windows-1252 Western Europe (used by UltraStar Deluxe < 1.1) - ); - -const - UTF8_BOM: UTF8String = #$EF#$BB#$BF; - -{** - * Decodes Src encoded in SrcEncoding to a UTF-16 or UTF-8 encoded Dst string. - * Returns true if the conversion was successful. - *} -function DecodeString(const Src: AnsiString; out Dst: WideString; SrcEncoding: TEncoding): boolean; overload; -function DecodeString(const Src: AnsiString; SrcEncoding: TEncoding): WideString; overload; -function DecodeStringUTF8(const Src: AnsiString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean; overload; -function DecodeStringUTF8(const Src: AnsiString; SrcEncoding: TEncoding): UTF8String; overload; - -{** - * Encodes the UTF-16 or UTF-8 encoded Src string to Dst using DstEncoding - * Returns true if the conversion was successful. - *} -function EncodeString(const Src: WideString; out Dst: AnsiString; DstEncoding: TEncoding): boolean; overload; -function EncodeString(const Src: WideString; DstEncoding: TEncoding): AnsiString; overload; -function EncodeStringUTF8(const Src: UTF8String; out Dst: AnsiString; DstEncoding: TEncoding): boolean; overload; -function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): AnsiString; overload; - -{** - * If Text starts with an UTF-8 BOM, the BOM is removed and true will - * be returned. - *} -function CheckReplaceUTF8BOM(var Text: AnsiString): boolean; - -{** - * Parses an encoding string to its TEncoding equivalent. - * Surrounding whitespace and dashes ('-') are removed, the upper-cased - * resulting value is then compared with TEncodingNames. - * If the encoding was not found, the result is set to the Default encoding. - *} -function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding; - -{** - * Returns the name of an encoding. - *} -function EncodingName(Encoding: TEncoding): AnsiString; - -implementation - -uses - StrUtils, - UUnicodeUtils; - -type - IEncoder = interface - function GetName(): AnsiString; - function Encode(const InStr: UCS4String; out OutStr: AnsiString): boolean; - function Decode(const InStr: AnsiString; out OutStr: UCS4String): boolean; - end; - - TEncoder = class(TInterfacedObject, IEncoder) - public - function GetName(): AnsiString; virtual; abstract; - function Encode(const InStr: UCS4String; out OutStr: AnsiString): boolean; virtual; abstract; - function Decode(const InStr: AnsiString; out OutStr: UCS4String): boolean; virtual; abstract; - end; - - TSingleByteEncoder = class(TEncoder) - public - function Encode(const InStr: UCS4String; out OutStr: AnsiString): boolean; override; - function Decode(const InStr: AnsiString; out OutStr: UCS4String): boolean; override; - function DecodeChar(InChr: AnsiChar; out OutChr: UCS4Char): boolean; virtual; abstract; - function EncodeChar(InChr: UCS4Char; out OutChr: AnsiChar): boolean; virtual; abstract; - end; - -const - ERROR_CHAR = '?'; - -var - Encoders: array[TEncoding] of IEncoder; - -function TSingleByteEncoder.Encode(const InStr: UCS4String; out OutStr: AnsiString): boolean; -var - I: integer; -begin - SetLength(OutStr, LengthUCS4(InStr)); - Result := true; - for I := 1 to Length(OutStr) do - begin - if (not EncodeChar(InStr[I-1], OutStr[I])) then - Result := false; - end; -end; - -function TSingleByteEncoder.Decode(const InStr: AnsiString; out OutStr: UCS4String): boolean; -var - I: integer; -begin - SetLength(OutStr, Length(InStr)+1); - Result := true; - for I := 1 to Length(InStr) do - begin - if (not DecodeChar(InStr[I], OutStr[I-1])) then - Result := false; - end; - OutStr[High(OutStr)] := 0; -end; - -function DecodeString(const Src: AnsiString; out Dst: WideString; SrcEncoding: TEncoding): boolean; -var - DstUCS4: UCS4String; -begin - Result := Encoders[SrcEncoding].Decode(Src, DstUCS4); - Dst := UCS4StringToWideString(DstUCS4); -end; - -function DecodeString(const Src: AnsiString; SrcEncoding: TEncoding): WideString; -begin - DecodeString(Src, Result, SrcEncoding); -end; - -function DecodeStringUTF8(const Src: AnsiString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean; -var - DstUCS4: UCS4String; -begin - Result := Encoders[SrcEncoding].Decode(Src, DstUCS4); - Dst := UCS4ToUTF8String(DstUCS4); -end; - -function DecodeStringUTF8(const Src: AnsiString; SrcEncoding: TEncoding): UTF8String; -begin - DecodeStringUTF8(Src, Result, SrcEncoding); -end; - -function EncodeString(const Src: WideString; out Dst: AnsiString; DstEncoding: TEncoding): boolean; -begin - Result := Encoders[DstEncoding].Encode(WideStringToUCS4String(Src), Dst); -end; - -function EncodeString(const Src: WideString; DstEncoding: TEncoding): AnsiString; -begin - EncodeString(Src, Result, DstEncoding); -end; - -function EncodeStringUTF8(const Src: UTF8String; out Dst: AnsiString; DstEncoding: TEncoding): boolean; -begin - Result := Encoders[DstEncoding].Encode(UTF8ToUCS4String(Src), Dst); -end; - -function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): AnsiString; -begin - EncodeStringUTF8(Src, Result, DstEncoding); -end; - -function CheckReplaceUTF8BOM(var Text: AnsiString): boolean; -begin - if AnsiStartsStr(UTF8_BOM, Text) then - begin - Text := Copy(Text, Length(UTF8_BOM)+1, Length(Text)-Length(UTF8_BOM)); - Result := true; - Exit; - end; - Result := false; -end; - -function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding; -var - PrepStr: AnsiString; // prepared encoding string - Encoding: TEncoding; -begin - // remove surrounding whitespace, replace dashes, to upper case - PrepStr := UpperCase(AnsiReplaceStr(Trim(EncodingStr), '-', '')); - for Encoding := Low(TEncoding) to High(TEncoding) do - begin - if (Encoders[Encoding].GetName() = PrepStr) then - begin - Result := Encoding; - Exit; +{* 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: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/trunk/src/menu/UMenuText.pas $ + * $Id: UMenuText.pas 1485 2008-10-28 20:16:05Z tobigun $ + *} + +unit UTextEncoding; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + SysUtils, + UUnicodeUtils; + +type + TEncoding = ( + encLocale, // current locale (needs cwstring on linux) + encUTF8, // UTF-8 + encCP1250, // Windows-1250 Central/Eastern Europe (used by Ultrastar) + encCP1252 // Windows-1252 Western Europe (used by UltraStar Deluxe < 1.1) + ); + +const + UTF8_BOM: UTF8String = #$EF#$BB#$BF; + +{** + * Decodes Src encoded in SrcEncoding to a UTF-16 or UTF-8 encoded Dst string. + * Returns true if the conversion was successful. + *} +function DecodeString(const Src: RawByteString; out Dst: WideString; SrcEncoding: TEncoding): boolean; overload; +function DecodeString(const Src: RawByteString; SrcEncoding: TEncoding): WideString; overload; +function DecodeStringUTF8(const Src: RawByteString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean; overload; +function DecodeStringUTF8(const Src: RawByteString; SrcEncoding: TEncoding): UTF8String; overload; + +{** + * Encodes the UTF-16 or UTF-8 encoded Src string to Dst using DstEncoding + * Returns true if the conversion was successful. + *} +function EncodeString(const Src: WideString; out Dst: RawByteString; DstEncoding: TEncoding): boolean; overload; +function EncodeString(const Src: WideString; DstEncoding: TEncoding): RawByteString; overload; +function EncodeStringUTF8(const Src: UTF8String; out Dst: RawByteString; DstEncoding: TEncoding): boolean; overload; +function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): RawByteString; overload; + +{** + * If Text starts with an UTF-8 BOM, the BOM is removed and true will + * be returned. + *} +function CheckReplaceUTF8BOM(var Text: RawByteString): boolean; + +{** + * Parses an encoding string to its TEncoding equivalent. + * Surrounding whitespace and dashes ('-') are removed, the upper-cased + * resulting value is then compared with TEncodingNames. + * If the encoding was not found, the result is set to the Default encoding. + *} +function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding; + +{** + * Returns the name of an encoding. + *} +function EncodingName(Encoding: TEncoding): AnsiString; + +implementation + +uses + StrUtils; + +type + IEncoder = interface + function GetName(): AnsiString; + function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; + function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; + end; + + TEncoder = class(TInterfacedObject, IEncoder) + public + function GetName(): AnsiString; virtual; abstract; + function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; virtual; abstract; + function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; virtual; abstract; + end; + + TSingleByteEncoder = class(TEncoder) + public + function Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; override; + function Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; override; + function DecodeChar(InChr: AnsiChar; out OutChr: UCS4Char): boolean; virtual; abstract; + function EncodeChar(InChr: UCS4Char; out OutChr: AnsiChar): boolean; virtual; abstract; + end; + +const + ERROR_CHAR = '?'; + +var + Encoders: array[TEncoding] of IEncoder; + +function TSingleByteEncoder.Encode(const InStr: UCS4String; out OutStr: RawByteString): boolean; +var + I: integer; +begin + SetLength(OutStr, LengthUCS4(InStr)); + Result := true; + for I := 1 to Length(OutStr) do + begin + if (not EncodeChar(InStr[I-1], OutStr[I])) then + Result := false; + end; +end; + +function TSingleByteEncoder.Decode(const InStr: RawByteString; out OutStr: UCS4String): boolean; +var + I: integer; +begin + SetLength(OutStr, Length(InStr)+1); + Result := true; + for I := 1 to Length(InStr) do + begin + if (not DecodeChar(InStr[I], OutStr[I-1])) then + Result := false; + end; + OutStr[High(OutStr)] := 0; +end; + +function DecodeString(const Src: RawByteString; out Dst: WideString; SrcEncoding: TEncoding): boolean; +var + DstUCS4: UCS4String; +begin + Result := Encoders[SrcEncoding].Decode(Src, DstUCS4); + Dst := UCS4StringToWideString(DstUCS4); +end; + +function DecodeString(const Src: RawByteString; SrcEncoding: TEncoding): WideString; +begin + DecodeString(Src, Result, SrcEncoding); +end; + +function DecodeStringUTF8(const Src: RawByteString; out Dst: UTF8String; SrcEncoding: TEncoding): boolean; +var + DstUCS4: UCS4String; +begin + Result := Encoders[SrcEncoding].Decode(Src, DstUCS4); + Dst := UCS4ToUTF8String(DstUCS4); +end; + +function DecodeStringUTF8(const Src: RawByteString; SrcEncoding: TEncoding): UTF8String; +begin + DecodeStringUTF8(Src, Result, SrcEncoding); +end; + +function EncodeString(const Src: WideString; out Dst: RawByteString; DstEncoding: TEncoding): boolean; +begin + Result := Encoders[DstEncoding].Encode(WideStringToUCS4String(Src), Dst); +end; + +function EncodeString(const Src: WideString; DstEncoding: TEncoding): RawByteString; +begin + EncodeString(Src, Result, DstEncoding); +end; + +function EncodeStringUTF8(const Src: UTF8String; out Dst: RawByteString; DstEncoding: TEncoding): boolean; +begin + Result := Encoders[DstEncoding].Encode(UTF8ToUCS4String(Src), Dst); +end; + +function EncodeStringUTF8(const Src: UTF8String; DstEncoding: TEncoding): RawByteString; +begin + EncodeStringUTF8(Src, Result, DstEncoding); +end; + +function CheckReplaceUTF8BOM(var Text: RawByteString): boolean; +begin + if AnsiStartsStr(UTF8_BOM, Text) then + begin + Text := Copy(Text, Length(UTF8_BOM)+1, Length(Text)-Length(UTF8_BOM)); + Result := true; + Exit; + end; + Result := false; +end; + +function ParseEncoding(const EncodingStr: AnsiString; Default: TEncoding): TEncoding; +var + PrepStr: AnsiString; // prepared encoding string + Encoding: TEncoding; +begin + // remove surrounding whitespace, replace dashes, to upper case + PrepStr := UpperCase(AnsiReplaceStr(Trim(EncodingStr), '-', '')); + for Encoding := Low(TEncoding) to High(TEncoding) do + begin + if (Encoders[Encoding].GetName() = PrepStr) then + begin + Result := Encoding; + Exit; end; end; - Result := Default; -end; - -function EncodingName(Encoding: TEncoding): AnsiString; -begin - Result := Encoders[Encoding].GetName(); -end; - -{$I ../encoding/Locale.inc} -{$I ../encoding/UTF8.inc} -{$I ../encoding/CP1250.inc} -{$I ../encoding/CP1252.inc} - -initialization - Encoders[encLocale] := TEncoderLocale.Create; - Encoders[encUTF8] := TEncoderUTF8.Create; - Encoders[encCP1250] := TEncoderCP1250.Create; - Encoders[encCP1252] := TEncoderCP1252.Create; - -end. + Result := Default; +end; + +function EncodingName(Encoding: TEncoding): AnsiString; +begin + Result := Encoders[Encoding].GetName(); +end; + +{$I ../encoding/Locale.inc} +{$I ../encoding/UTF8.inc} +{$I ../encoding/CP1250.inc} +{$I ../encoding/CP1252.inc} + +initialization + Encoders[encLocale] := TEncoderLocale.Create; + Encoders[encUTF8] := TEncoderUTF8.Create; + Encoders[encCP1250] := TEncoderCP1250.Create; + Encoders[encCP1252] := TEncoderCP1252.Create; + +end. diff --git a/unicode/src/base/UTexture.pas b/unicode/src/base/UTexture.pas index 97f244fe..04badae4 100644 --- a/unicode/src/base/UTexture.pas +++ b/unicode/src/base/UTexture.pas @@ -40,6 +40,7 @@ uses Classes, SysUtils, UCommon, + UPath, SDL, SDL_Image; @@ -66,7 +67,7 @@ type TexX2: real; TexY2: real; Alpha: real; - Name: string; // experimental for handling cache images. maybe it's useful for dynamic skins + Name: IPath; // experimental for handling cache images. maybe it's useful for dynamic skins end; type @@ -91,7 +92,7 @@ procedure AdjustPixelFormat(var TexSurface: PSDL_Surface; Typ: TTextureType); type PTextureEntry = ^TTextureEntry; TTextureEntry = record - Name: string; + Name: IPath; Typ: TTextureType; Color: cardinal; @@ -105,7 +106,7 @@ type Texture: array of TTextureEntry; public procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean); - function FindTexture(const Name: string; Typ: TTextureType; Color: cardinal): integer; + function FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer; end; TTextureUnit = class @@ -116,14 +117,14 @@ type procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Cache: boolean = false); overload; procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean = false); overload; - function GetTexture(const Name: string; Typ: TTextureType; FromCache: boolean = false): TTexture; overload; - function GetTexture(const Name: string; Typ: TTextureType; Col: LongWord; FromCache: boolean = false): TTexture; overload; - function LoadTexture(FromRegistry: boolean; const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; overload; - function LoadTexture(const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; overload; - function LoadTexture(const Identifier: string): TTexture; overload; - function CreateTexture(Data: PChar; const Name: string; Width, Height: word; BitsPerPixel: byte): TTexture; - procedure UnloadTexture(const Name: string; Typ: TTextureType; FromCache: boolean); overload; - procedure UnloadTexture(const Name: string; Typ: TTextureType; Col: cardinal; FromCache: boolean); overload; + function GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean = false): TTexture; overload; + function GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean = false): TTexture; overload; + function LoadTexture(FromRegistry: boolean; const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; overload; + function LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; overload; + function LoadTexture(const Identifier: IPath): TTexture; overload; + function CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture; + procedure UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean); overload; + procedure UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean); overload; //procedure FlushTextureDatabase(); constructor Create; @@ -188,7 +189,7 @@ begin Texture[TextureIndex].Texture := Tex; end; -function TTextureDatabase.FindTexture(const Name: string; Typ: TTextureType; Color: cardinal): integer; +function TTextureDatabase.FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer; var TextureIndex: integer; CurrentTexture: PTextureEntry; @@ -235,18 +236,18 @@ begin TextureDatabase.AddTexture(Tex, Typ, Color, Cache); end; -function TTextureUnit.LoadTexture(FromRegistry: boolean; const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; +function TTextureUnit.LoadTexture(FromRegistry: boolean; const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; begin // FIXME: what is the FromRegistry parameter supposed to do? Result := LoadTexture(Identifier, Typ, Col); end; -function TTextureUnit.LoadTexture(const Identifier: string): TTexture; +function TTextureUnit.LoadTexture(const Identifier: IPath): TTexture; begin Result := LoadTexture(Identifier, TEXTURE_TYPE_PLAIN, 0); end; -function TTextureUnit.LoadTexture(const Identifier: string; Typ: TTextureType; Col: LongWord): TTexture; +function TTextureUnit.LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; var TexSurface: PSDL_Surface; newWidth, newHeight: integer; @@ -260,7 +261,7 @@ begin TexSurface := LoadImage(Identifier); if not assigned(TexSurface) then begin - Log.LogError('Could not load texture: "' + Identifier +'" with type "'+ TextureTypeToStr(Typ) +'"', + Log.LogError('Could not load texture: "' + Identifier.ToNative +'" with type "'+ TextureTypeToStr(Typ) +'"', 'TTextureUnit.LoadTexture'); Exit; end; @@ -363,16 +364,16 @@ begin SDL_FreeSurface(TexSurface); end; -function TTextureUnit.GetTexture(const Name: string; Typ: TTextureType; FromCache: boolean): TTexture; +function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean): TTexture; begin Result := GetTexture(Name, Typ, 0, FromCache); end; -function TTextureUnit.GetTexture(const Name: string; Typ: TTextureType; Col: LongWord; FromCache: boolean): TTexture; +function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean): TTexture; var TextureIndex: integer; begin - if (Name = '') then + if (Name.IsUnset) then begin // zero texture data FillChar(Result, SizeOf(Result), 0); @@ -413,7 +414,7 @@ begin Result := TextureDatabase.Texture[TextureIndex].Texture; end; -function TTextureUnit.CreateTexture(Data: PChar; const Name: string; Width, Height: word; BitsPerPixel: byte): TTexture; +function TTextureUnit.CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture; var //Error: integer; ActTex: GLuint; @@ -467,12 +468,12 @@ begin Result.Name := Name; end; -procedure TTextureUnit.UnloadTexture(const Name: string; Typ: TTextureType; FromCache: boolean); +procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean); begin UnloadTexture(Name, Typ, 0, FromCache); end; -procedure TTextureUnit.UnloadTexture(const Name: string; Typ: TTextureType; Col: cardinal; FromCache: boolean); +procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean); var T: integer; TexNum: GLuint; diff --git a/unicode/src/base/UThemes.pas b/unicode/src/base/UThemes.pas index 3fd77853..11ec746c 100644 --- a/unicode/src/base/UThemes.pas +++ b/unicode/src/base/UThemes.pas @@ -34,11 +34,12 @@ interface {$I switches.inc} uses - ULog, IniFiles, SysUtils, Classes, - UTexture; + ULog, + UTexture, + UPath; type TRGB = record @@ -531,10 +532,10 @@ type TextFound: TThemeText; //Translated Texts - Songsfound: string; - NoSongsfound: string; - CatText: string; - IType: array [0..2] of string; + Songsfound: UTF8String; + NoSongsfound: UTF8String; + CatText: UTF8String; + IType: array [0..2] of UTF8String; end; //Party Screens @@ -761,11 +762,11 @@ type Playlist: TThemePlaylist; - ILevel: array[0..2] of string; + ILevel: array[0..2] of UTF8String; - constructor Create(const FileName: string); overload; // Initialize theme system - constructor Create(const FileName: string; Color: integer); overload; // Initialize theme system with color - function LoadTheme(FileName: string; sColor: integer): boolean; // Load some theme settings from file + constructor Create(const FileName: IPath); overload; // Initialize theme system + constructor Create(const FileName: IPath; Color: integer); overload; // Initialize theme system with color + function LoadTheme(const FileName: IPath; sColor: integer): boolean; // Load some theme settings from file procedure LoadColors; @@ -845,12 +846,12 @@ begin glColor4f(Color.R, Color.G, Color.B, Min(Color.A, Alpha)); end; -constructor TTheme.Create(const FileName: string); +constructor TTheme.Create(const FileName: IPath); begin Create(FileName, 0); end; -constructor TTheme.Create(const FileName: string; Color: integer); +constructor TTheme.Create(const FileName: IPath; Color: integer); begin inherited Create(); @@ -893,7 +894,7 @@ begin end; -function TTheme.LoadTheme(FileName: string; sColor: integer): boolean; +function TTheme.LoadTheme(const FileName: IPath; sColor: integer): boolean; var I: integer; begin @@ -901,23 +902,21 @@ begin CreateThemeObjects(); - Log.LogStatus('Loading: '+ FileName, 'TTheme.LoadTheme'); - - FileName := AdaptFilePaths(FileName); + Log.LogStatus('Loading: '+ FileName.ToNative, 'TTheme.LoadTheme'); - if not FileExists(FileName) then + if not FileName.IsFile() then begin - Log.LogError('Theme does not exist ('+ FileName +')', 'TTheme.LoadTheme'); + Log.LogError('Theme does not exist ('+ FileName.ToNative +')', 'TTheme.LoadTheme'); end; - if FileExists(FileName) then + if FileName.IsFile() then begin Result := true; {$IFDEF THEMESAVE} - ThemeIni := TIniFile.Create(FileName); + ThemeIni := TIniFile.Create(FileName.ToNative); {$ELSE} - ThemeIni := TMemIniFile.Create(FileName); + ThemeIni := TMemIniFile.Create(FileName.ToNative); {$ENDIF} if ThemeIni.ReadString('Theme', 'Name', '') <> '' then diff --git a/unicode/src/base/UUnicodeUtils.pas b/unicode/src/base/UUnicodeUtils.pas index ce1731b6..29be778a 100644 --- a/unicode/src/base/UUnicodeUtils.pas +++ b/unicode/src/base/UUnicodeUtils.pas @@ -148,6 +148,11 @@ function UCS4UpperCase(const str: UCS4String): UCS4String; overload; *} function UCS4CharToString(ch: UCS4Char): UCS4String; +{** + * @seealso System.Pos() + *} +function UTF8Pos(const substr: UTF8String; const str: UTF8String): Integer; + {** * Copies a segment of str starting with Index (1-based) with Count characters (not bytes). *} @@ -520,6 +525,11 @@ begin Result[1] := 0; end; +function UTF8Pos(const substr: UTF8String; const str: UTF8String): Integer; +begin + Result := Pos(substr, str); +end; + function UTF8Copy(const str: UTF8String; Index: Integer; Count: Integer): UTF8String; begin Result := UCS4ToUTF8String(UCS4Copy(UTF8ToUCS4String(str), Index-1, Count)); diff --git a/unicode/src/base/UXMLSong.pas b/unicode/src/base/UXMLSong.pas index 58b48789..e8962539 100644 --- a/unicode/src/base/UXMLSong.pas +++ b/unicode/src/base/UXMLSong.pas @@ -34,7 +34,9 @@ interface {$I switches.inc} uses - Classes; + Classes, + UPath, + UUnicodeUtils; type TNote = record @@ -42,30 +44,30 @@ type Duration: Cardinal; Tone: Integer; NoteTyp: Byte; - Lyric: String; + Lyric: UTF8String; end; - ANote = Array of TNote; + ANote = array of TNote; TSentence = record Singer: Byte; Duration: Cardinal; Notes: ANote; end; - ASentence = Array of TSentence; + ASentence = array of TSentence; - TSongInfo = Record + TSongInfo = record ID: Cardinal; DualChannel: Boolean; - Header: Record - Artist: String; - Title: String; + Header: record + Artist: UTF8String; + Title: UTF8String; Gap: Cardinal; BPM: Real; Resolution: Byte; - Edition: String; - Genre: String; - Year: String; - Language: String; + Edition: UTF8String; + Genre: UTF8String; + Year: UTF8String; + Language: UTF8String; end; CountSentences: Cardinal; Sentences: ASentence; @@ -81,23 +83,23 @@ type BindLyrics: Boolean; //Should the Lyrics be bind to the last Word (no Space) FirstNote: Boolean; //Is this the First Note found? For Gap calculating - Function ParseLine(Line: String): Boolean; + function ParseLine(Line: RawByteString): Boolean; public SongInfo: TSongInfo; - ErrorMessage: String; - Edition: String; - SingstarVersion: String; + ErrorMessage: string; + Edition: UTF8String; + SingstarVersion: string; - Settings: Record + Settings: record DashReplacement: Char; end; - Constructor Create; + constructor Create; - Function ParseConfigforEdition(const Filename: String): String; + function ParseConfigForEdition(const Filename: IPath): String; - Function ParseSongHeader(const Filename: String): Boolean; //Parse Song Header only - Function ParseSong (const Filename: String): Boolean; //Parse whole Song + function ParseSongHeader(const Filename: IPath): Boolean; //Parse Song Header only + function ParseSong (const Filename: IPath): Boolean; //Parse whole Song end; const @@ -114,9 +116,12 @@ const DS_Both = 3; implementation -uses SysUtils, StrUtils; -Constructor TParser.Create; +uses + SysUtils, + StrUtils; + +constructor TParser.Create; begin inherited Create; ErrorMessage := ''; @@ -124,19 +129,28 @@ begin DecimalSeparator := '.'; end; -Function TParser.ParseSong (const Filename: String): Boolean; -var I: Integer; +function TParser.ParseSong(const Filename: IPath): Boolean; +var + I: Integer; + FileStream: TBinaryFileStream; begin + {$message warn 'TODO: TParser.ParseSong'} + +(* Result := False; - if FileExists(Filename) then + if Filename.IsFile() then begin SSFile := TStringList.Create; + ErrorMessage := 'Can''t open melody.xml file'; + try - ErrorMessage := 'Can''t open melody.xml file'; - SSFile.LoadFromFile(Filename); + FileStream := TBinaryFileStream.Create(Filename); + SSFile.LoadFromStream(FileStream); + ErrorMessage := ''; Result := True; + I := 0; SongInfo.CountSentences := 0; @@ -153,7 +167,7 @@ begin SetLength(SongInfo.Sentences, 0); - While Result And (I < SSFile.Count) do + while Result and (I < SSFile.Count) do begin Result := ParseLine(SSFile.Strings[I]); @@ -164,12 +178,16 @@ begin SSFile.Free; end; end; + *) end; -Function TParser.ParseSongHeader (const Filename: String): Boolean; +function TParser.ParseSongHeader (const Filename: IPath): Boolean; var I: Integer; begin Result := False; + + {$message warn 'TODO: TParser.ParseSong'} + (* if FileExists(Filename) then begin SSFile := TStringList.Create; @@ -211,6 +229,7 @@ begin end else ErrorMessage := 'Can''t find melody.xml file'; + *) end; Function TParser.ParseLine(Line: String): Boolean; @@ -569,7 +588,7 @@ begin Result := true; end; -Function TParser.ParseConfigforEdition(const Filename: String): String; +Function TParser.ParseConfigForEdition(const Filename: IPath): String; var txt: TStringlist; I: Integer; @@ -578,6 +597,10 @@ var begin Result := ''; txt := TStringlist.Create; + + {$message warn 'TODO: TParser.ParseConfigForEdition'} + + (* try txt.LoadFromFile(Filename); @@ -601,6 +624,7 @@ begin finally txt.Free; end; + *) end; end. -- cgit v1.2.3