aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/UCovers.pas
blob: 7a192e661ffefe252c151f195485b94e3c60e681 (plain) (tree)
1
2

             

















                                                                   

















                   
             
 







                                                  





                        
                                                                


                                



















                                                                                                  
                                                              

















                                                                                      
                 
           




                                    
 
                                   
 

                                                                                  
 
                                                                        

                                   
                                                                        
                                                           






                                  



                                                 

                                                                                                                        

                                              
                                                                                                        

                                                  



                      
                                 




               
              

               
                                                   
     

                                  
                   

    






                                                             
   
                            
     









                            
 


                                                
         




                               
 
 



                    
 


                                                               
                                                                  







                            
                                                          











                       
 







                                                           
 
                                   

                 

            
 




                                                       












                                                               
                                                                                                             




                                             
                                  


                                                   



                                                                                                        
                                                                       
                                                                                                                          







                                                                      






                                                                                  


                                           



                                                                                                   


                     
                         















                                                                               
 


                                                
                                                                  



                                   
 
                                    
                   
       





                             

      
                                      
 
























                                                                   

                                        

            

                                  






                      

    



                                                             
   
                  
     





                                                    
                                                    
 










                                                                    

    
                                               
   
                   
     

                                                                      


                                
       



                                              
           
                        
          

        
 

                                           
         
                            

                                               
    
 





                                                     
                                                                

                                             
                                                        

                                                  
                               



                      
 

                                
                                                       


                                      
        



                                           
                                                                  

      





                                                     

    
                                                        
   
                
     





                                    
       

                                              
                                                

                                     
                 



                  


      







                                                                                                                                                                                                                                    
       

                                                              
      


                           

                                             








                                                                                        



                                    


                    

    
                                                








                                               
                               
                                   

                                                               





                                         
                               

             



                              

                              















                                                                 
 















































                                                                                                                                      
      
     

                                               


                             

                                                                      
 
                                                                                             
      










                                                    

    
    
unit UCovers;

{********************
  UCover
  Contains Class managing Covers.Cache File
  File Structure:
  TCC_FileHeader

  TextureData
    * Array of TCC_TextureData

  Indexes
    * TCC_FileIndex Header Block
    * TCC_FileIndex
    * String containing Filename of Last IndexEntry. Ending with #0
         .
         .
    * TCC_FileIndex Footer Block
*********************}

interface

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

{$I switches.inc}

uses OpenGL12,
     {$IFDEF win32}
     windows,
     {$ENDIF}
     Math,
     Classes,
     SysUtils,
     {$IFNDEF FPC}
     Graphics,
     {$ENDIF}
     UThemes;

const
  cCC_CoverW        = 128;
  cCC_CoverH        = 128;
  cCC_CoverSize     = cCC_CoverW * cCC_CoverH * 3;
  cCC_HeaderText    = 'USDxCo' + #0 + #1;
  cCC_HeaderVersion = 1000;
  cCC_IndexIndicator= 'I' + 'N' + 'D' + #0;

type
  TCover = record
    Name:       string;
    W:          word;
    H:          word;
    Size:       integer;
    Position:   integer; //position of picture in the cache file
//    Data:       array of byte;
  end;

  //-------------------------------------------------------------------
  //Covers.Cache File Parts

  TCC_Hash = Array [1..32] of Char;
  TCC_FileHeader = record
    FileTyp:    Array [1..8] of Char;  //Some String to detect if this is the file we want to open
    Version:    DWord;      //Version of Covers.Cache File
    Hash:       TCC_Hash;   //Some Randomly Created Alphanumeric String to Identify w/ SongDB
    CoverW:     Word;       //Width of all Covers in Cache
    CoverH:     Word;       //Height of all Covers in Cache
    DataStart:  Cardinal;   //Start Position in Bytes of Data Part
    DataLength: Cardinal;   //Start Position in Bytes of Data Part
    IndexStart: Cardinal;   //Start of Index Block in Bytes
  end;

  PCC_TextureData = ^TCC_TextureData;
  TCC_TextureData = Array [0..cCC_CoverSize - 1] of Byte;

  TCC_FileIndexHeader = record
    Indicator: Array [1..4] of Char;    //Contains INDE
    HighestID: Integer;                //Highest ID of a Cover
  end;

  TCC_FileIndex = record
    LastUpdated: Integer;   //Time of LastFilechange
    DataStart: Cardinal;    //Position of TextureData of this Cover in Bytes.
                            //0 if this is empty slot(Deleted Cover)(Id not available)
                            //1 if the Texture Data is not already loaded to Cache
                            //High(Cardinal) if this is IndexFooter
    //Filename: String;
  end;

  TCC_IndexListItem = record
    Filename: String;
    TexID:    Integer;
    FileIndex: TCC_FileIndex;
  end;
  TCC_IndexList = Array of TCC_IndexListItem;

  TCovers = class
    private
      Filename:      String;

      Header:        TCC_FileHeader;
      HighestID:     Integer;
      Count:         Cardinal;

      Index:         TCC_IndexList;

      IndexNeedRewrite: Boolean; //Index in CacheFile is overwritten by other Data
      CacheReadOnly: Boolean;    //Cache File is read only

      Function WriteHeader(const ReWriteCache: Boolean = false):Boolean;
      Function ReadHeader: Boolean;
      Function ReadIndex:  Boolean;
      Function WriteIndex(const ReWriteCache: Boolean = false): Boolean;
      Function AddTexData(Data: PCC_TextureData): Cardinal;
    public
      W:          word;
      H:          word;
      Size:       integer;
      Data:       array of byte;
      Cover:      array of TCover;

      property Hash: TCC_Hash read Header.Hash;

      constructor Create(const Filename: String);
      procedure Load(const Filename: String);
      Function  AddCover(FileName: string): Integer;    //Returns ID, Checks Cover for Change, Updates Cover if required
      function  CoverExists(FileName: string): Integer; //Returns ID by FilePath
      procedure PrepareData(FileName: string);
      Procedure LoadTextures;
      Function  ReWriteCache: Boolean;                 //Deletes old cover.cache file and writes new one

      Function  GetTexbyID(ID: Cardinal): Integer;
  end;

var
  Covers:     TCovers;
 // to - do : new Song management
implementation

uses UMain,
     // UFiles,
     ULog,
     UTexture,
     DateUtils;

constructor TCovers.Create(const Filename: String);
begin
  HighestID := -1;
  SetLength(Index, HighestID + 2);
  //Load(Filename);
end;

//----------------------------------------------
// Some File handling helpers

//--------
// Reads and Checks Header. Returns True if Header is correct
//--------
Function TCovers.ReadHeader:   Boolean;
var
  F: File of TCC_FileHeader;
begin
  try
    //Read Header
    AssignFile(F, Filename);

    try
      Reset(F);
      Read(F, Header);
    finally
      CloseFile(F);
    end;

    //Check Header
    If (Header.FileTyp = cCC_HeaderText) AND
       (Header.Version = cCC_HeaderVersion) then
    begin
      Result := True;
      IndexNeedRewrite := True;
    end
    Else
      Result := False;


  except
    Result := False;
  end;
end;

//--------
// Writes Header(Resets File). Returns True if Writing succeed 
//--------
Function TCovers.WriteHeader(const ReWriteCache: Boolean):Boolean;
var
  F: File of TCC_FileHeader;
begin
  try
    Result := True;
    //Read Header
    AssignFile(F, Filename);
    try
      If (not FileExists(Filename)) OR (ReWriteCache) then
        ReWrite(F)
      else
        Reset(F);
      
      Write(F, Header);
    finally
      CloseFile(F);
    end;
  except
    Result := False;
  end;
end;

//--------
// Reads and Checks Index. Returns True if Index is correct
//--------
Function TCovers.ReadIndex:  Boolean;
var
  F: File of Byte;
  IndexHeader: TCC_FileIndexHeader;
  I: Integer;

  Procedure mReadLn(var S: String);
  var
    Len: Integer;
  begin
    S := '';

    BlockRead(F, Len, 4); //Read Len of Filename String

    //Read Filename String
    SetLength(S, Len);
    BlockRead(F, S[1], Len);
  end;
begin
  try
    //Read Header
    AssignFile(F, Filename);
      try
        Reset(F);
        Seek(F, Header.IndexStart);

        BlockRead(F, IndexHeader, SizeOf(TCC_FileIndexHeader));

        If (IndexHeader.Indicator = cCC_IndexIndicator) then
        begin
          Log.LogError('TCovers: loading Cover Index Header. HighestID: ' + InttoStr(IndexHeader.HighestID));
          HighestID := IndexHeader.HighestID;
          SetLength(Index, HighestID + 2);

          Count := 0;
          Result := True;
          If (HighestID >= 0) then
          begin
            //Read File Infos until (Eof or Footer)
            I := 0;
            //While (Not Eof(F)) AND ((I <= 0) OR (Index[I-1].FileIndex.DataStart <> High(Cardinal))) do
            Repeat
              Log.LogError('TCovers: loading Cover Index. Position #' + InttoStr(I));
              If (I > HighestID + 1) then
              begin //Header IndexCOunt was wrong, running out of array
                Log.LogError('TCovers: Wrong HighestID in Index Header. Running out of Array at Postion #' + InttoStr(I));
                Inc(HighestID);
                IndexNeedReWrite := True;
                SetLength(Index, HighestID + 2);
              end;

              BlockRead(F, Index[I].FileIndex, SizeOf(TCC_FileIndex));
              Index[I].TexID    := -1;

              If (Index[I].FileIndex.DataStart = High(Cardinal)) then
              begin //Found Footer
                Log.LogError('TCovers: Found footer at Position #' + InttoStr(I));
                Break;
              end;

              If (Not Eof(F)) then
              begin
                //Read Filename
                mReadLn(Index[I].Filename);

                Log.LogError('TCovers: Cover loaded: ' + Index[I].Filename);
                If (Index[I].FileIndex.DataStart <> 0) AND (Index[I].FileIndex.DataStart <> 1) then
                  Inc(Count);
              end;

              Inc(I);
            Until Eof(F);

            If (Index[HighestID + 1].FileIndex.DataStart = High(Cardinal)) then
            begin //No Footer found
              IndexNeedReWrite := True;
            end;
          end;

        end;

      finally
        CloseFile(F);
      end;
  except
    Result := False;
  end;
end;

//--------
// Writes Index. Returns True if Writing succeed
//--------
Function TCovers.WriteIndex(const ReWriteCache: Boolean): Boolean;
var
  F: File of Byte;
  IndexHeader: TCC_FileIndexHeader;
  I: Integer;

  Procedure mWriteLn(var S: String);
  var Len: Integer;
  begin
    //Write Length of String
    Len := Length(S);
    BlockWrite(F, Len, 4);

    //Write String
    BlockWrite(F, S[1], Len);
  end;
begin
  Result := WriteHeader(ReWriteCache);

  If (Result) then
  begin
    try
      //Read Header
      AssignFile(F, Filename);
      try
        Reset(F);

        Seek(F, Header.IndexStart);
        //Write Header
        IndexHeader.Indicator := cCC_IndexIndicator;
        IndexHeader.HighestID := HighestID;

        BlockWrite(F, IndexHeader, SizeOf(TCC_FileIndexHeader));

        Count := 0;
        Result := True;

        //Prepare Footer
        Index[HighestID + 1].FileIndex.DataStart := High(Cardinal);

        // Write Fileinfo
        For I := 0 to HighestID+1 do
        begin
          BlockWrite(F, Index[I].FileIndex, SizeOf(TCC_FileIndex));
          If (I <= HighestID) then
            mWriteLn(Index[I].Filename);
        end;

        IndexNeedRewrite := False;

      finally
        CloseFile(F);
      end;
    except
      Result := False;
    end;
  end;
end;

//--------
// Writes some Texture Data to the End of TextureData Block
//--------
Function TCovers.AddTexData(Data: PCC_TextureData): Cardinal;
var
  F: File of Byte;
begin
  try
    AssignFile(F, Filename);
    try
      Reset(F);
      Seek(F, Header.DataStart + Header.DataLength);

      BlockWrite(F, Data^, SizeOf(TCC_TextureData));

      Result := Header.DataStart + Header.DataLength;
      Inc(Header.DataLength, SizeOf(TCC_TextureData));

      Header.IndexStart := Header.DataStart + Header.DataLength + 1;
      IndexNeedReWrite := True;
    finally
      CloseFile(F);
    end;
  except
    Result := 0;
  end;
end;

procedure TCovers.Load(const Filename: String);
var
  Succeed: Boolean;
begin
  Log.LogError('TCovers: Load cache from file: ''' + Filename + '''');

  Self.Filename := Filename;
  Succeed := False;
  If (FileExists(Filename)) then
  begin
    CacheReadOnly := FileisReadOnly(Filename);
    If (ReadHeader) then
    begin //Header successful read
      If (ReadIndex) then
      begin
        Succeed := True;
      end;
    end;
  end;

  If not Succeed and not CacheReadOnly then
    If not (ReWriteCache) then
    begin
      CacheReadOnly := True;
      Log.LogError('TCovers: Cache readonly!');
    end;
end;

Function TCovers.AddCover(FileName: string): Integer;
var I: Integer;
begin
  Result := CoverExists(Filename);
  If (Result = -1) then
  begin //Add Cover(Does not exist)
    Log.LogError('TCovers: Adding cover: ''' + Filename + '''');
    If (Count <= HighestID) then
    begin //There is an empty slot, Search It
      Log.LogError('TCovers: Searching for Empty Slot');
      For I := 0 to HighestID do
        If (Index[I].FileIndex.DataStart = 0) then
        begin //Found that Slot
          Result := I;
          Break;
        end;
    end;

    If (Result = -1) then
    begin //Attach it to the End
      Log.LogError('TCovers: Attach Cover to the end');
      Inc(HighestID);
      SetLength(Index, HighestID + 2);
      Result := HighestID;
    end;

    Index[Result].Filename := Filename;
    Index[Result].TexID    := -1;
    Index[Result].FileIndex.DataStart := 1;
    Log.LogError('TCovers: Cover Added, ID: ' + InttoStr(Result));
  end
  else
  begin //Check if File has Changed
    If (Index[Result].FileIndex.LastUpdated < 0) then
    begin

    end;
  end;
end;

Function TCovers.CoverExists(FileName: string): integer;
var
  I:    integer;
begin
  Result := -1;
  {$IFDEF MSWINDOWS}
    Filename := Uppercase(Filename);
  {$ENDIF}

  For I := 0 to HighestID do
  begin
    If (Index[I].FileIndex.DataStart <> 0) AND
   {$IFDEF MSWINDOWS}
       (Uppercase(Index[I].Filename) = Filename)
   {$ELSE}
       (Index[I].Filename = Filename)
   {$ENDIF}  then
    begin
      Result := I;
      Exit;
    end;
  end;
end;

//--------
// Deletes old cover.cache file and writes new one
//--------
Function  TCovers.ReWriteCache: Boolean;

  Function MakeHash: TCC_Hash;
    const AlphaNumeric: Array[0..35] of Char = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
    var I: Integer;
  begin
    For I := Low(Result) to High(Result) do
      Result[I] := AlphaNumeric[Random(Length(AlphaNumeric))];
  end;
begin
  If not CacheReadOnly then
  begin
    Log.LogError('TCovers: Rewriting Cache');
    
    Header.FileTyp    := cCC_HeaderText;
    Header.Version    := cCC_HeaderVersion;
    Header.Hash       := MakeHash;
    Header.CoverW     := cCC_CoverW;
    Header.CoverH     := cCC_CoverH;
    Header.DataStart  := SizeOf(TCC_FileHeader) + 4; //Total of 64 Bytes (4 Bytes Space)
    Header.DataLength := 0;
    Header.IndexStart := Header.DataStart + Header.DataLength + 4;

    HighestID := -1;
    SetLength(Index, HighestID + 2);

    Result := WriteIndex(True);
  end
  else
    Result := False;
end;

procedure TCovers.PrepareData(FileName: string);
var
  F:  File;
  C:  integer;
begin
  if FileExists(GamePath + 'covers.cache') then
  begin
    AssignFile(F, GamePath + 'covers.cache');
    Reset(F, 1);

    C := CoverExists(FileName);
    SetLength(Data, Cover[C].Size);
    if Length(Data) < 6 then
      Log.LogStatus('Length(Data) < 6', 'TCovers.PrepareData');
    Seek(F, Cover[C].Position);
    BlockRead(F, Data[0], Cover[C].Size);
    CloseFile(F);
  end;
end;

Procedure TCovers.LoadTextures;
var
  I: Integer;
  TexData: PCC_TextureData;
  CachedData: TCC_TextureData;
  F: File of Byte;

  Function LoadCover: Integer;
  begin
    Result := -1;
    
    If (Index[I].FileIndex.DataStart = 1) then
    begin //This Texture is new and has to be loaded
      TexData := Texture.GetCoverThumbnail(Index[I].Filename);
      If (TexData <> nil) then
      begin
        If not (CacheReadonly) then
        begin //Save this Tex to Cache
          Index[I].FileIndex.DataStart :=  AddTexData(TexData);
          If (Index[I].FileIndex.DataStart = 0) then
          begin
            CacheReadOnly := True; //Failed to write Data
            Log.LogError('Failed to Write TextureData to Cache');
          end;
        end;

        //Create Texture
        glGenTextures(1, @Result);

        glBindTexture(GL_TEXTURE_2D, Result);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        //glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, TexData);
      end
      else
        Log.LogError('Couldn''t get Thumbnail Data');
    end
    Else If (Index[I].FileIndex.DataStart > 1) then
    begin //This texture is already in Cache, Load it from there
      try
        Log.LogError('TCovers: Loading Cover #' + InttoStr(I) + ' from Cache at Position: ' + InttoStr(Index[I].FileIndex.DataStart));
        Assign(F, Filename);
        try
          Reset(F);
          Seek(F, Index[I].FileIndex.DataStart);
          BlockRead(F, CachedData, SizeOf(TCC_TextureData));
        finally
          CloseFile(F);
        end;


        //Create Texture
        glGenTextures(1, @Result);

        if (Result > 0) then
        begin

          glBindTexture(GL_TEXTURE_2D, Result);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

          //glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
          glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, @CachedData[0]);
        end
        else
          Log.LogError('TCovers: Error Generating Texture');
      except
        Log.LogError('TCovers: Error during loading');
      end;
    end;
  end;
begin
  Texture.SetCoverSize(cCC_CoverW, cCC_CoverH);
  Log.LogError('TCovers: LoadingTextures');

  For I := 0 to HighestID do
  begin //Load all the Covers
    If (Index[I].FileIndex.DataStart > 0) then
      Index[I].TexID := LoadCover; //No empty SLot -> Load the Texture

    Log.LogError('TCovers: Texture for ID#' + InttoStr(I) + ': ' + InttoStr(Index[I].TexID));
  end;

  If IndexNeedRewrite then
    WriteIndex;
end;

Function  TCovers.GetTexbyID(ID: Cardinal): Integer;
begin
  If (ID <= HighestID) then
    Result := Index[ID].TexID
  else
    Result := -1;
end;

end.