{* 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 UPlaylist; interface {$IFDEF FPC} {$MODE Delphi} {$ENDIF} {$I switches.inc} uses Classes, USong, UPath, UPathUtils; type TPlaylistItem = record Artist: UTF8String; Title: UTF8String; SongID: Integer; end; APlaylistItem = array of TPlaylistItem; TPlaylist = record Name: UTF8String; Filename: IPath; Items: APlaylistItem; end; APlaylist = array of TPlaylist; //---------- //TPlaylistManager - Class for Managing Playlists (Loading, Displaying, Saving) //---------- TPlaylistManager = class private public Mode: TSingMode; //Current Playlist Mode for SongScreen CurPlayList: Cardinal; CurItem: Cardinal; Playlists: APlaylist; constructor Create; procedure LoadPlayLists; function LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean; procedure SavePlayList(Index: Cardinal); procedure SetPlayList(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 GetNames(var PLNames: array of UTF8String); function GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer = -1): Integer; end; {Modes: 0: Standard Mode 1: Category Mode 2: PlayList Mode} var PlayListMan: TPlaylistManager; implementation uses SysUtils, USongs, ULog, UMain, UFilesystem, UGraphic, UThemes, UUnicodeUtils; //---------- //Create - Construct Class - Dummy for now //---------- constructor TPlayListManager.Create; begin inherited; LoadPlayLists; end; //---------- //LoadPlayLists - Load list of Playlists from PlayList Folder //---------- Procedure TPlayListManager.LoadPlayLists; var Len: Integer; PlayListBuffer: TPlayList; Iter: IFileIterator; FileInfo: TFileInfo; begin SetLength(Playlists, 0); Iter := FileSystem.FileFind(PlayListPath.Append('*.upl'), 0); while (Iter.HasNext) do begin Len := Length(Playlists); SetLength(Playlists, Len + 1); FileInfo := Iter.Next; 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 Playlists[Len+1] := Playlists[Len]; Dec(Len); end; Playlists[Len+1] := PlayListBuffer; end; end; end; //---------- //LoadPlayList - Load a Playlist in the Array //---------- function TPlayListManager.LoadPlayList(Index: Cardinal; const Filename: IPath): Boolean; 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 begin Result := I; Break; end; end; end; var TextStream: TTextFileStream; Line: UTF8String; PosDelimiter: Integer; SongID: Integer; Len: Integer; FilenameAbs: IPath; begin //Load File try FilenameAbs := PlaylistPath.Append(Filename); TextStream := TMemTextFileStream.Create(FilenameAbs, fmOpenRead); except begin Log.LogError('Could not load Playlist: ' + FilenameAbs.ToNative); Result := False; Exit; end; end; Result := True; //Set Filename Playlists[Index].Filename := Filename; Playlists[Index].Name := ''; //Read Until End of File while TextStream.ReadLine(Line) do begin if (Length(Line) > 0) then begin PosDelimiter := UTF8Pos(':', Line); if (PosDelimiter <> 0) then begin //Comment or Name String if (Line[1] = '#') then begin //Found Name Value if (Uppercase(Trim(copy(Line, 2, PosDelimiter - 2))) = 'NAME') then PlayLists[Index].Name := Trim(copy(Line, PosDelimiter + 1,Length(Line) - PosDelimiter)) end //Song Entry else begin SongID := FindSong(Trim(copy(Line, 1, PosDelimiter - 1)), Trim(copy(Line, PosDelimiter + 1, Length(Line) - PosDelimiter))); if (SongID <> -1) then begin Len := Length(PlayLists[Index].Items); SetLength(PlayLists[Index].Items, Len + 1); PlayLists[Index].Items[Len].SongID := SongID; 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.ToNative + ', ' + Line); end; end; end; end; //If no special name is given, use Filename if PlayLists[Index].Name = '' then begin PlayLists[Index].Name := FileName.SetExtension('').ToUTF8; end; //Finish (Close File) TextStream.Free; end; {** * Saves the specified Playlist *} procedure TPlayListManager.SavePlayList(Index: Cardinal); var TextStream: TTextFileStream; PlaylistFile: IPath; I: Integer; begin PlaylistFile := PlaylistPath.Append(Playlists[Index].Filename); // cannot update read-only file if PlaylistFile.IsFile() and PlaylistFile.IsReadOnly() then Exit; // open file for rewriting TextStream := TMemTextFileStream.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 TextStream.WriteLine('#Name: ' + Playlists[Index].Name); // Write song information TextStream.WriteLine('#Songs:'); 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; {** * Display a Playlist in CatSongs *} procedure TPlayListManager.SetPlayList(Index: Cardinal); var I: Integer; begin if (Int(Index) > High(PlayLists)) then exit; //Hide all Songs 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 begin CatSongs.Song[PlayLists[Index].Items[I].SongID].Visible := True; end; //Set CatSongsMode + Playlist Mode CatSongs.CatNumShow := -3; Mode := smPlayListRandom; //Set CurPlaylist CurPlaylist := Index; //Show Cat in Topleft: ScreenSong.ShowCatTLCustom(Format(Theme.Playlist.CatText,[Playlists[Index].Name])); //Fix SongSelection ScreenSong.Interaction := 0; ScreenSong.SelectNext; ScreenSong.FixSelected; //Play correct Music ScreenSong.ChangeMusic; end; //---------- //AddPlaylist - Adds a Playlist and Returns the Index //---------- 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 begin Dec(Result); Playlists[Result+1] := Playlists[Result]; end; Playlists[Result].Name := Name; // clear playlist items SetLength(Playlists[Result].Items, 0); I := 1; PlaylistFile := PlaylistPath.Append(Name + '.upl'); while (PlaylistFile.Exists) do begin Inc(I); PlaylistFile := PlaylistPath.Append(Name + InttoStr(I) + '.upl'); end; Playlists[Result].Filename := PlaylistFile.GetName; //Save new Playlist SavePlayList(Result); end; //---------- //DelPlaylist - Deletes a Playlist //---------- procedure TPlayListManager.DelPlaylist(const Index: Cardinal); var I: Integer; Filename: IPath; begin if Int(Index) > High(Playlists) then Exit; Filename := PlaylistPath.Append(Playlists[Index].Filename); //If not FileExists or File is not Writeable then exit if (not Filename.IsFile()) or (Filename.IsReadOnly()) then Exit; //Delete Playlist from FileSystem if not Filename.DeleteFile() then Exit; //Delete Playlist from Array //move all PLs to the Hole for I := Index to High(Playlists)-1 do PlayLists[I] := PlayLists[I+1]; //Delete last Playlist SetLength (Playlists, High(Playlists)); //If Playlist is Displayed atm //-> Display Songs if (CatSongs.CatNumShow = -3) and (Index = CurPlaylist) then begin ScreenSong.UnLoadDetailedCover; ScreenSong.HideCatTL; CatSongs.SetFilter('', fltAll); ScreenSong.Interaction := 0; ScreenSong.FixSelected; ScreenSong.ChangeMusic; end; end; //---------- //AddItem - Adds an Item to a specific Playlist //---------- Procedure TPlayListManager.AddItem(const SongID: Cardinal; const iPlaylist: Integer); var P: Cardinal; Len: Cardinal; begin if iPlaylist = -1 then P := CurPlaylist else if (iPlaylist >= 0) AND (iPlaylist <= high(Playlists)) then P := iPlaylist else exit; if (Int(SongID) <= High(CatSongs.Song)) AND (NOT CatSongs.Song[SongID].Main) then begin Len := Length(Playlists[P].Items); SetLength(Playlists[P].Items, Len + 1); Playlists[P].Items[Len].SongID := SongID; Playlists[P].Items[Len].Title := CatSongs.Song[SongID].Title; Playlists[P].Items[Len].Artist := CatSongs.Song[SongID].Artist; //Save Changes SavePlayList(P); //Correct Display when Editing current Playlist if (CatSongs.CatNumShow = -3) and (P = CurPlaylist) then SetPlaylist(P); end; end; //---------- //DelItem - Deletes an Item from a specific Playlist //---------- Procedure TPlayListManager.DelItem(const iItem: Cardinal; const iPlaylist: Integer); var I: Integer; P: Cardinal; begin if iPlaylist = -1 then P := CurPlaylist else if (iPlaylist >= 0) AND (iPlaylist <= high(Playlists)) then P := iPlaylist else exit; if (Int(iItem) <= high(Playlists[P].Items)) then begin //Move all entrys behind deleted one to Front For I := iItem to High(Playlists[P].Items) - 1 do Playlists[P].Items[I] := Playlists[P].Items[I + 1]; //Delete Last Entry SetLength(PlayLists[P].Items, Length(PlayLists[P].Items) - 1); //Save Changes SavePlayList(P); end; //Delete Playlist if Last Song is deleted if (Length(PlayLists[P].Items) = 0) then begin DelPlaylist(P); end //Correct Display when Editing current Playlist else if (CatSongs.CatNumShow = -3) and (P = CurPlaylist) then SetPlaylist(P); end; //---------- //GetNames - Writes Playlist Names in a Array //---------- procedure TPlayListManager.GetNames(var PLNames: array of UTF8String); var I: Integer; Len: Integer; begin Len := High(Playlists); if (Length(PLNames) <> Len + 1) then exit; For I := 0 to Len do PLNames[I] := Playlists[I].Name; end; //---------- //GetIndexbySongID - Returns Index in the specified Playlist of the given Song //---------- Function TPlayListManager.GetIndexbySongID(const SongID: Cardinal; const iPlaylist: Integer): Integer; var P: Integer; I: Integer; begin Result := -1; if iPlaylist = -1 then P := CurPlaylist else if (iPlaylist >= 0) AND (iPlaylist <= high(Playlists)) then P := iPlaylist else exit; For I := 0 to high(Playlists[P].Items) do begin if (Playlists[P].Items[I].SongID = Int(SongID)) then begin Result := I; Break; end; end; end; end.