aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/UDataBase.pas
blob: 12a2555e19411e6c59301acce48e834b85eb61f2 (plain) (tree)
1
2
3
4
5
6
7
8
9



               



                
                 
 
            
           
                  























                                                 





                                     
                         







                             



























                                                           

                             



















                                                                



                               
                              
 



                                             
 


                                           
                                                                            













                                                                                           




                                                                                                                               













                                                   




                            



           
 


                           


                                     
 





                                                        
                                                                                
  
                 


                                                       

     


















                                                                                                                                               
                                                   










                                                                                                                                                                                                                                      

                                                 

                                                                                                                                                                                       
                                                                 
        
 

                                                

























                                                                                                                                                                                                         
                                                                
        


         

                                                                                                     









                                
                                         

                        




                                        
                                                                                        

                          
             
     

                                                    
 
                                                   
       























                                                                                                                                                                                                                           
          
 

                     
        
      




                                    
                                                                                                



                        
                             

       





















                                                                                                                                                                                                                                                                                                       
 


                   
        
      




                                                                              
                                                    
     





                                                                                                                                    
 

        
















                                                                                                                                          


                                 






                                                                                         

                                                                                                                                                                  

                                                                                                                          











                                                                                     
     
                                         



                                                                                                                                    


















































                                                                                                         

              

                                 
 













                                                                             
                                                               















                                                                                                                                                     
           
        

      

    















































                                                                                                       
    
unit UDataBase;

interface

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

{$I switches.inc}

uses USongs,
     USong,
     SQLiteTable3;

//--------------------
//DataBaseSystem - Class including all DB Methods
//--------------------
type
  TStatResult = record
    Case Typ: Byte of
      0: (Singer:     ShortString;
          Score:      Word;
          Difficulty: Byte;
          SongArtist: ShortString;
          SongTitle:  ShortString);

      1: (Player:     ShortString;
          AverageScore: Word);

      2: (Artist: ShortString;
          Title:  ShortString;
          TimesSung:  Word);

      3: (ArtistName:   ShortString;
          TimesSungtot: Word);
  end;
  AStatResult = Array of TStatResult;

  TDBSongInfo = record
    FileName:    String;
    FolderID:    String;
    FullPath:    String;
    LastChanged: Integer; //TimeStamp
    TimesPlayed: Integer;

    //Required Information
      Title:      widestring;
      Artist:     widestring;

      Mp3:        widestring;

      Text:       widestring;

      Resolution: integer;
      BPM:        array of TBPM;
      GAP:        real; // in miliseconds

      Base    : array[0..1] of integer;
      Rel     : array[0..1] of integer;
      Mult    : integer;
      MultBPM : integer;

    //Some Files
      Cover:      widestring; //Path to Cover
      CoverID:    Integer;    //ID of Cover in Covers Cache
      Background: widestring;
      Video:      widestring;
      VideoGAP:   real;


    //Additional Information
      NotesGAP:   integer;
      Start:      real; // in seconds
      Finish:     integer; // in miliseconds
      Relative:   boolean;

    //Sorting
      Genre:      widestring;
      Edition:    widestring;
      Language:   widestring;
      Year:       widestring;
      Creator:    widestring;
  end;

  TSongListInfo = record //SongInfo used by Songscreen
    ID:         Integer;
    Title:      widestring;
    Artist:     widestring;

    Mp3:        widestring;
    Video:      widestring;
    CoverID:    Integer; //Cover ID in CoversCache
  end;
  ASongList = Array of TSongListInfo;

  TSongListFilter = record
    Field:    Byte;    //FieldID //See constants below
    isSearch: Boolean; //Search Mask(true) or exact Match(False)
    Filter:   String;
  end;
  ASongListFilter = Array of TSongListFilter;

  TDataBaseSystem = class
    private
      ScoreDB: TSqliteDatabase;
      sFilename: string;
      UpdateFromVer1: Boolean;

    public


    property Filename: String read sFilename;

    Destructor Free;

    Procedure Init(const Filename: string);
    Function ReadScore(const Song: Integer; const Difficulty: Byte): AScore;
    procedure AddScore(Song: Integer; Level: integer; Name: string; Score: integer);
    procedure WriteScore(Song: Integer);

    Function  GetIDbyPath(const Path: String): Integer;
    Function  GetIDbyFileName(const Filename: String; FolderID: Integer): Integer;
    Procedure GetSongData(ID: Integer; var Info: TDBSongInfo);
    Procedure SetSongData(ID: Integer; var Info: TDBSongInfo);
    Function  GetLastChangedbyID(const Song: Integer): Integer;
    Function  GetLastChangedbyFileName(const Filename: String; FolderID: Integer): Integer;

    Function  GetFolderIDbyPath(Path: String): Integer;
    Function  GetFolderIDbyName(const Name: String; ParentFolder: Integer): Integer;

    Function  GetSongList(var List: ASongList; const Filter: ASongListFilter): Integer;

    Function  GetStats(var Stats: AStatResult; const Typ, Count: Byte; const Page: Cardinal; const Reversed: Boolean): Boolean;
    Function  GetTotalEntrys(const Typ: Byte): Cardinal;
  end;

const
  //Filter FieldIDs
  SLF_Edition   = 0;
  SLF_Genre     = 1;
  SLF_Language  = 2;
  SLF_Folder    = 3;
  SLF_Title     = 4;
  SLF_Artist    = 5;
  SLF_Year      = 6;
  SLF_Creator   = 7;
  SLF_Video     = 8; //Songs w/ Video are returned
  SLF_NoVideo   = 9; //Songs w/o Video are returned
  Max_FilterFieldID = SLF_NoVideo;

var
  DataBase: TDataBaseSystem;

implementation

uses
  IniFiles,
  ULog,
  SysUtils;

const
  cUS_Scores = 'us_scores';
  cUS_Songs  = 'us_songs';
  cUS_Info   = 'us_info';
  cUS_Directories = 'us_directories';
  cDB_Version = '2.0.0';

//--------------------
//Create - Opens Database and Create Tables if not Exist
//--------------------

Procedure TDataBaseSystem.Init(const Filename: string);
begin
  debugWriteln( 'TDataBaseSystem.Init ('+Filename+') @ '+ floattostr( now() ) );
  
  //Open Database
  ScoreDB        := TSqliteDatabase.Create( Filename );
  sFilename      := Filename;
  UpdateFromVer1 := False;

  try
    //Check for Database Version
    if not ScoreDB.TableExists( cUS_Info ) then
    begin
      If (ScoreDB.TableExists( cUS_Scores ) And ScoreDB.TableExists( cUS_Songs )) And (Not ScoreDB.TableExists( cUS_Directories )) then
      begin //Update from a Ver. 1.0 - 1.0.1a Database
        UpdateFromVer1 := True; //This table should be Updated from before Database ver 2.0
        ScoreDb.ExecSQL('ALTER TABLE `' + cUS_Songs + '` RENAME TO ''' + cUS_Songs + '_old'''); //Rename old Song Table, to achieve old SongIDs
        debugWriteln( 'TDataBaseSystem.Init - Switched to "Update from DB Version 1" Mode' );
      end;

      //Create Info Table
      ScoreDB.ExecSQL('CREATE TABLE `' + cUS_Info + '` ( `Ident` varchar(32) PRIMARY KEY, `Value` varchar(64) NOT NULL default '''');');

      //Write Database Version to Table
      ScoreDB.ExecSQL('INSERT INTO `' + cUS_Info + '` VALUES (''version'', ''2.0.0'');');

      debugWriteln( 'TDataBaseSystem.Init - CREATED US_Info' );
    end;

    //Look for Tables => When not exist Create them

    if not ScoreDB.TableExists( cUS_Directories ) then
    begin
      ScoreDB.execsql('CREATE TABLE `' + cUS_Directories + '` ( `ID` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `Name` varchar(255) NOT NULL default '''', `FullPath` text NOT NULL, `LastChange` int(10) NOT NULL default ''0'');');

      //Add Root Directory
      ScoreDB.ExecSQL('INSERT INTO `us_directories` VALUES (1, ''root'', '''', ''0'');');

      debugWriteln( 'TDataBaseSystem.Init - CREATED US_Directories' );
    end;

    if not ScoreDB.TableExists( cUS_Scores ) then
    begin
      ScoreDB.execsql('CREATE TABLE `'+cUS_Scores+'` (`SongID` INT( 10 ) NOT NULL , `Difficulty` INT( 1 ) NOT NULL , `Player` VARCHAR( 150 ) NOT NULL , `Score` INT( 5 ) NOT NULL );');

      debugWriteln( 'TDataBaseSystem.Init - CREATED US_Scores' );
    end;

    if not ScoreDB.TableExists( cUS_Songs ) then
    begin
      // Old 1.0 Style: ScoreDB.execsql('CREATE TABLE `'+cUS_Songs+'` (`ID` INTEGER PRIMARY KEY, `Artist` VARCHAR( 255 ) NOT NULL , `Title` VARCHAR( 255 ) NOT NULL , `TimesPlayed` int(5) NOT NULL );');
      //New: Dataabse 2.0 Style
      ScoreDb.ExecSQL('CREATE TABLE `' + cUS_Songs + '` ( ' +
                      '`ID` integer PRIMARY KEY AUTOINCREMENT, ' +
                      '`Filename` varchar(255) NOT NULL default '''', ' +
                      '`FolderID` int(10) NOT NULL default ''0'', ' +
                      '`FullPath` text NOT NULL, ' +
                      '`LastChanged` int(10) NOT NULL default ''0'', ' +
                      '`Artist` varchar(255) NOT NULL default '''', ' +
                      '`Title` varchar(255) NOT NULL default '''', ' +
                      '`Mp3` text NOT NULL, ' +
                      '`Cover` text NOT NULL, ' +
                      '`CoverID` int(10) NOT NULL default ''0'', ' +
                      '`Video` text NOT NULL, ' +
                      '`VideoGap` float NOT NULL default ''0'', ' +
                      '`Start` float NOT NULL default ''0'', ' +
                      '`Genre` varchar(255) NOT NULL default '''', ' +
                      '`Edition` varchar(255) NOT NULL default '''', ' +
                      '`Language` varchar(255) NOT NULL default '''', ' +
                      '`Year` varchar(255) NOT NULL default '''', ' +
                      '`Creator` varchar(255) NOT NULL default '''', ' +
                      '`TimesPlayed` int(5) NOT NULL default ''0'');');

      //Delete Score Table to avoid wrong IDS
      ScoreDb.ExecSQL('DELETE FROM `' + cUS_Scores + '`');

      debugWriteln( 'TDataBaseSystem.Init - CREATED US_Songs' );
    end;


  finally
    debugWriteln( cUS_Songs +' Exist : ' + inttostr( integer(ScoreDB.TableExists( cUS_Songs )) ) );
    debugWriteln( cUS_Scores +' Exist : ' + inttostr( integer(ScoreDB.TableExists( cUS_Scores )) ) );
  //ScoreDB.Free;
  end;

end;

//--------------------
//Free - Frees Database
//--------------------
Destructor TDataBaseSystem.Free;
begin
  debugWriteln( 'TDataBaseSystem.Free' );

  freeandnil( ScoreDB );
end;

//--------------------
//ReadScore - Read Scores into SongArray
//--------------------
Function TDataBaseSystem.ReadScore(const Song: Integer; const Difficulty: Byte): AScore;
var
  TableData: TSqliteTable;
  I: Integer;
begin
  if (assigned( ScoreDB )) AND (Difficulty < 2) then
  begin

    //ScoreDB := TSqliteDatabase.Create(sFilename);
    try
      try
        //Search Song in DB
        TableData := ScoreDB.GetTable('SELECT `Player`, `Score` FROM `'+cUS_Scores+'` WHERE (`SongID` = ''' + InttoStr(Song) + ''') AND (`Difficulty` = ''' + InttoStr(Difficulty) + ''') ORDER BY `Score` DESC  LIMIT 5');


        I := 0;
        while not TableData.Eof do //Go through all Entrys
        begin //Add one Entry to Array
            Song.Score[Difficulty, high(Song.Score[Difficulty])].Name  :=
                TableData.FieldAsString(TableData.FieldIndex['Player']);
            Song.Score[Difficulty, high(Song.Score[Difficulty])].Score :=
                StrtoInt(TableData.FieldAsString(TableData.FieldIndex['Score']));

          TableData.Next;
          Inc(I);
        end; // While not TableData.EOF

        If (I < 5) then
          Result[1].Score := 0; //Place ending Zero

      except
        Result[0].Name  := 'Error Reading ScoreDB';
        Result[0].Score := -1;
        Result[1].Score := 0;
      end;

    finally
      //ScoreDb.Free;
    end;
  end;
end;

//--------------------
//AddScore - Add one new Score to DB
//--------------------
procedure TDataBaseSystem.AddScore(Song: Integer; Level: integer; Name: string; Score: integer);
var
ID: Integer;
TableData: TSqliteTable;
begin
  if assigned( ScoreDB ) then
  begin

    //ScoreDB := TSqliteDatabase.Create(sFilename);
    try
      //Prevent 0 Scores from being added
      if (Score > 0) then
      begin
        {ID := ScoreDB.GetTableValue('SELECT `ID` FROM `'+cUS_Songs+'` WHERE `Artist` = "' + Song.Artist + '" AND `Title` = "' + Song.Title + '"');
        if ID = 0 then //Song doesn't exist -> Create
        begin
          ScoreDB.ExecSQL ('INSERT INTO `'+cUS_Songs+'` ( `ID` , `Artist` , `Title` , `TimesPlayed` ) VALUES (NULL , "' + Song.Artist + '", "' + Song.Title + '", "0");');
          ID := ScoreDB.GetTableValue('SELECT `ID` FROM `US_Songs` WHERE `Artist` = "' + Song.Artist + '" AND `Title` = "' + Song.Title + '"');
          if ID = 0 then //Could not Create Table
            exit;
        end; }
        //Create new Entry
        ScoreDB.ExecSQL('INSERT INTO `'+cUS_Scores+'` ( `SongID` , `Difficulty` , `Player` , `Score` ) VALUES ("' + InttoStr(Song) + '", "' + InttoStr(Level) + '", "' + Name + '", "' + InttoStr(Score) + '");');

        //Delete Last Position when there are more than 5 Entrys
        if ScoreDB.GetTableValue('SELECT COUNT(`SongID`) FROM `'+cUS_Scores+'` WHERE `SongID` = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'"') > 5 then
        begin
          TableData := ScoreDB.GetTable('SELECT `Player`, `Score` FROM `'+cUS_Scores+'` WHERE SongID = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'" ORDER BY `Score` ASC LIMIT 1');
          ScoreDB.ExecSQL('DELETE FROM `US_Scores` WHERE SongID = "' + InttoStr(ID) + '" AND `Difficulty` = "' + InttoStr(Level) +'" AND `Player` = "' + TableData.FieldAsString(TableData.FieldIndex['Player']) + '" AND `Score` = "' + TableData.FieldAsString(TableData.FieldIndex['Score']) + '"');
        end;

      end;
    finally
    //ScoreDB.Free;
    end;
  end;
end;

//--------------------
//WriteScore - Not needed with new System; But used for Increment Played Count
//--------------------
procedure TDataBaseSystem.WriteScore(Song: Integer);
begin
  if not assigned( ScoreDB ) then
  begin
    try
      //Increase TimesPlayed
      ScoreDB.ExecSQL ('UPDATE `'+cUS_Songs+'` SET `TimesPlayed` = `TimesPlayed` + ''1'' WHERE `ID` = ''' + InttoStr(Song) + ''';');
    except

    end;
  end;
end;

//--------------------
//GetStats - Write some Stats to Array, Returns True if Chossen Page has Entrys
//Case Typ of
//0 - Best Scores
//1 - Best Singers
//2 - Most sung Songs
//3 - Most popular Band
//--------------------
Function TDataBaseSystem.GetStats(var Stats: AStatResult; const Typ, Count: Byte; const Page: Cardinal; const Reversed: Boolean): Boolean;
var
  Query: String;
  TableData: TSqliteTable;
begin
  Result := False;

  if not assigned( ScoreDB ) then
    exit;

  if (Length(Stats) < Count) then
    Exit;

  {Todo:  Add Prevention that only Players with more than 5 Scores are Selected at Typ 2}

  //Create Query
  Case Typ of
    0: Query := 'SELECT `Player` , `Difficulty` , `Score` , `Artist` , `Title` FROM `'+cUS_Scores+'` INNER JOIN `US_Songs` ON (`SongID` = `ID`) ORDER BY `Score`';
    1: Query := 'SELECT `Player` , ROUND (Sum(`Score`) / COUNT(`Score`)) FROM `'+cUS_Scores+'` GROUP BY `Player` ORDER BY (Sum(`Score`) / COUNT(`Score`))';
    2: Query := 'SELECT `Artist` , `Title` , `TimesPlayed` FROM `'+cUS_Songs+'` ORDER BY `TimesPlayed`';
    3: Query := 'SELECT `Artist` , Sum(`TimesPlayed`) FROM `'+cUS_Songs+'` GROUP BY `Artist` ORDER BY Sum(`TimesPlayed`)';
  end;

  //Add Order Direction
  If Reversed then
    Query := Query + ' ASC'
  else
    Query := Query + ' DESC';

  //Add Limit
  Query := Query + ' LIMIT ' + InttoStr(Count * Page) + ', ' + InttoStr(Count) + ';';

  //Execute Query
  try
    TableData := ScoreDB.GetTable(Query);
  except
    exit;  // this has a try except, because ( on linux at least ) it seems that doing a GetTable, that returns nothing
           // causes an exception.   and in the case of a new Database file, with no scores stored yet... this seems to except here.
  end;

  //if Result empty -> Exit
  if (TableData.RowCount < 1) then
    exit;

  //Copy Result to Stats Array
  while not TableData.Eof do
  begin
    Stats[TableData.Row].Typ := Typ;

    Case Typ of
      0:begin
          Stats[TableData.Row].Singer := TableData.Fields[0];

          Stats[TableData.Row].Difficulty := StrtoIntDef(TableData.Fields[1], 0);

          Stats[TableData.Row].Score := StrtoIntDef(TableData.Fields[2], 0){TableData.FieldAsInteger(2)};
          Stats[TableData.Row].SongArtist := TableData.Fields[3];
          Stats[TableData.Row].SongTitle := TableData.Fields[4];
        end;

        1:begin
          Stats[TableData.Row].Player := TableData.Fields[0];
          Stats[TableData.Row].AverageScore := StrtoIntDef(TableData.Fields[1], 0);
        end;

        2:begin
          Stats[TableData.Row].Artist := TableData.Fields[0];
          Stats[TableData.Row].Title  := TableData.Fields[1];
          Stats[TableData.Row].TimesSung  := StrtoIntDef(TableData.Fields[2], 0);
        end;

        3:begin
          Stats[TableData.Row].ArtistName := TableData.Fields[0];
          Stats[TableData.Row].TimesSungtot := StrtoIntDef(TableData.Fields[1], 0);
        end;

    end;

    TableData.Next;
  end;

  Result := True;
end;

//--------------------
//GetTotalEntrys - Get Total Num of entrys for a Stats Query
//--------------------
Function  TDataBaseSystem.GetTotalEntrys(const Typ: Byte): Cardinal;
var Query: String;
begin
  Result := 0;

  if not assigned( ScoreDB ) then
    exit;

  try
    //Create Query
    Case Typ of
      0: begin
           Query := 'SELECT COUNT(`SongID`) FROM `'+cUS_Scores+'`;';
           if not ScoreDB.TableExists( cUS_Scores ) then
             exit;
         end;
      1: begin
           Query := 'SELECT COUNT(DISTINCT `Player`) FROM `'+cUS_Scores+'`;';
           if not ScoreDB.TableExists( cUS_Scores ) then
             exit;
         end;
      2: begin
           Query := 'SELECT COUNT(`ID`) FROM `'+cUS_Songs+'`;';
           if not ScoreDB.TableExists( cUS_Songs ) then
             exit;
         end;
      3: begin
           Query := 'SELECT COUNT(DISTINCT `Artist`) FROM `'+cUS_Songs+'`;';
           if not ScoreDB.TableExists( cUS_Songs ) then
             exit;
         end;
    end;
  
    Result := ScoreDB.GetTableValue(Query);
  except
    // TODO : JB_Linux - Why do we get these exceptions on linux !!
    on E:ESQLiteException DO  // used to handle : Could not retrieve data "SELECT COUNT(`ID`) FROM `US_Songs`;" : SQL logic error or missing database
                              // however, we should pre-empt this error... and make sure the database DOES exist.
    begin
      exit;
    end;
  end;

end;

Function  TDataBaseSystem.GetIDbyPath(const Path: String): Integer;
begin

end;

Function  TDataBaseSystem.GetIDbyFileName(const Filename: String; FolderID: Integer): Integer;
begin

end;

Procedure TDataBaseSystem.GetSongData(ID: Integer; var Info: TDBSongInfo);
begin

end;

Procedure TDataBaseSystem.SetSongData(ID: Integer; var Info: TDBSongInfo);
begin

end;

Function  TDataBaseSystem.GetLastChangedbyID(const Song: Integer): Integer;
begin

end;

Function  TDataBaseSystem.GetLastChangedbyFileName(const Filename: String; FolderID: Integer): Integer;
begin

end;


Function  TDataBaseSystem.GetFolderIDbyPath(Path: String): Integer;
begin

end;

Function  TDataBaseSystem.GetFolderIDbyName(const Name: String; ParentFolder: Integer): Integer;
begin

end;


Function  TDataBaseSystem.GetSongList(var List: ASongList; const Filter: ASongListFilter): Integer;
begin

end;


end.