You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1458 lines
41 KiB
Plaintext
1458 lines
41 KiB
Plaintext
{
|
|
This file is part of R&Q.
|
|
Under same license
|
|
}
|
|
unit SQLiteDB;
|
|
{$I RnQConfig.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, Classes, Forms, Dialogs, Themes, Types, SysUtils, StrUtils, DateUtils, Generics.Collections, System.Threading,
|
|
Variants, Data.DB, FireDAC.Comp.Client, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef,
|
|
FireDAC.Phys.SQLiteWrapper, FireDAC.DApt, FireDAC.Stan.Option, FireDAC.Stan.Def, FireDAC.Stan.Async, FireDAC.Stan.Intf,
|
|
FireDAC.ConsoleUI.Wait{, FireDAC.Moni.FlatFile},
|
|
Events, ICQCommon, Stickers;
|
|
|
|
type
|
|
TMsgID = UInt64;
|
|
TSecurityAction = (SA_CHECK, SA_ENCRYPT, SA_DECRYPT, SA_CHANGEPASS);
|
|
|
|
TSrvHist = record
|
|
LastMsgId, LastRead: TMsgID;
|
|
PatchVersion: String;
|
|
end;
|
|
|
|
TSQLDatabase = class
|
|
private
|
|
sql: TFDConnection;
|
|
sqlcontains: TFDSQLiteFunction;
|
|
sqlsec: TFDSQLiteSecurity;
|
|
sqlvalid: TFDSQLiteValidate;
|
|
procedure ContainsFunc(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
|
|
function OpenTable(const Table: String): Boolean;
|
|
|
|
// Conversations
|
|
procedure CreateConvTable;
|
|
procedure MigrateConvTable;
|
|
procedure OpenConvTable;
|
|
function RecordToEvent(var qry: TFDQuery): Thevent;
|
|
function QueryToEventArray(var qry: TFDQuery; var events: Thevents; inverse: Boolean = False): Boolean;
|
|
|
|
|
|
// Stickers
|
|
|
|
procedure CreateStickersTable;
|
|
|
|
procedure OpenStickersTable;
|
|
|
|
function RecordToSticker(var qry: TFDQuery): TStickerPack;
|
|
function QueryToStickersArray(var qry: TFDQuery; var stickers: TStickerPacks): Boolean;
|
|
public
|
|
function Connect: Boolean;
|
|
procedure Disconnect;
|
|
function CheckIntegrity: Boolean;
|
|
function ManageSecurity(action: TSecurityAction; const passwd: String = ''): Boolean;
|
|
|
|
// Conversations
|
|
procedure ConvertHistory;
|
|
procedure UpdateProgress(Sender: TObject; TickCount: Cardinal; var Reset: Boolean);
|
|
procedure DeleteChat(const chat: TUID);
|
|
procedure WriteEvent(const chat: TUID; ev: Thevent);
|
|
procedure WriteMsgIDs(const chat: TUID; ReqID: Integer; MsgID: TMsgID; const WID: RawByteString);
|
|
procedure WriteMsgFlags(const chat: TUID; MsgID: TMsgID; Flags: Integer);
|
|
procedure DeleteByTimeRange(const chat: TUID; fromTime, toTime: TDateTime);
|
|
procedure DeleteBySender(const chat, sender: TUID);
|
|
function GetExistingChats: TUIDS;
|
|
function ChatExists(const chat: TUID): Boolean;
|
|
function GetAll(const chat: TUID): Thevents;
|
|
function GetLastMulti(const chat: TUID; offset, cnt: Integer; var noMoreMessages: Boolean): Thevents;
|
|
function GetLastSingle(const chat: TUID; cnt: Integer): Thevent;
|
|
function GetByTimeRange(const chat: TUID; fromTime, toTime: TDateTime; var noMoreMessages: Boolean; toInclusive: Boolean = True): Thevents;
|
|
function GetByTime(const chat: TUID; thetime: TDateTime): Thevent;
|
|
function GetByRowID(const chat: TUID; RowID: Integer): Thevent;
|
|
function GetByMsgID(const chat: TUID; MsgID: TMsgID; Recent: Boolean = True): Thevent;
|
|
function GetByWID(const chat: TUID; const wid: String): Thevent;
|
|
function GetBySender(const chat, sender: TUID): Thevents;
|
|
function GetEventCount(const chat: TUID): Integer;
|
|
function DataSearch(const chat: TUID; const txt: String; caseSensitive: Boolean = False): Thevents;
|
|
|
|
// Stickers
|
|
procedure ClearStickerPacks;
|
|
procedure AddStickerPack(const StickerPack: TStickerPack);
|
|
procedure ChangeStickerPackStatus(const PackId: String; Status: Boolean);
|
|
function GetStickerPacksCount: Integer;
|
|
function GetStickerPacks(ActiveOnly: Boolean = False): TStickerPacks;
|
|
|
|
// Server history
|
|
procedure OpenSrvHistTable;
|
|
procedure CreateSrvHistTable;
|
|
procedure MigrateSrvHistTable;
|
|
procedure UpdateLastMsg(const UID: TUID; LastMsgId: TMsgID; PatchVersion: String);
|
|
procedure UpdateLastRead(const UID: TUID; LastRead: TMsgID);
|
|
function GetHistDlg(const UID: TUID): TSrvHist;
|
|
end;
|
|
|
|
TCUID = record
|
|
UID: TUID;
|
|
Pos: Integer;
|
|
end;
|
|
|
|
const
|
|
dbConv: String = 'Conversations';
|
|
dbSrvHist: String = 'ServerHistory';
|
|
dbStickers: String = 'Stickers';
|
|
|
|
var
|
|
SQLDB: TSQLDatabase;
|
|
userts: TDateTime;
|
|
sqldrv: TFDPhysSQLiteDriverLink;
|
|
// trace: TFDMoniFlatFileClientLink;
|
|
taskFakeFrm: TForm;
|
|
taskDlg: TTaskDialog;
|
|
cUID: TCUID;
|
|
|
|
implementation
|
|
|
|
uses
|
|
globalLib, history, utilLib, IOUtils,
|
|
RDGlobal, RDFileUtil, RDUtils, RnQGlobal, RnQDialogs, RnQLangs, RQLog, ICQContacts;
|
|
|
|
procedure TSQLDatabase.ContainsFunc(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
|
|
var
|
|
CaseSensitive: Boolean;
|
|
Str1, Str2: String;
|
|
begin
|
|
if AInputs.Count < 2 then
|
|
AOutput.AsBoolean := False;
|
|
|
|
if AInputs.Count < 3 then
|
|
CaseSensitive := False
|
|
else
|
|
CaseSensitive := AInputs[2].AsBoolean;
|
|
|
|
Str1 := AInputs[0].AsString;
|
|
Str2 := AInputs[1].AsString;
|
|
|
|
if CaseSensitive then
|
|
AOutput.AsBoolean := ContainsStr(Str1, Str2)
|
|
else
|
|
AOutput.AsBoolean := ContainsText(Str1, Str2);
|
|
end;
|
|
|
|
function TSQLDatabase.Connect: Boolean;
|
|
var
|
|
params: TFDPhysSQLiteConnectionDefParams;
|
|
begin
|
|
Result := False;
|
|
if not CheckHistPass then
|
|
Exit;
|
|
|
|
userts := Now;
|
|
|
|
// FDManager.Active := True;
|
|
|
|
sqlcontains := TFDSQLiteFunction.Create(nil);
|
|
sqlcontains.DriverLink := sqldrv;
|
|
sqlcontains.FunctionName := 'contains';
|
|
sqlcontains.ArgumentsCount := 3;
|
|
sqlcontains.OnCalculate := ContainsFunc;
|
|
sqlcontains.Active := True;
|
|
|
|
// trace := TFDMoniFlatFileClientLink.Create(nil);
|
|
// trace.FileName := 'D:\Site\trace';
|
|
// trace.FileEncoding := ecUTF8;
|
|
// trace.Tracing := True;
|
|
|
|
try
|
|
sql := TFDConnection.Create(nil);
|
|
sql.DriverName := 'SQLite';
|
|
params := TFDPhysSQLiteConnectionDefParams(sql.Params);
|
|
params.Clear;
|
|
params.LockingMode := lmExclusive;
|
|
params.DriverID := 'SQLite';
|
|
params.Database := AccPath + accountFilename;
|
|
params.OpenMode := omCreateUTF8;
|
|
params.JournalMode := jmWAL;
|
|
params.LockingMode := lmNormal;
|
|
params.Synchronous := snFull;
|
|
params.GUIDFormat := guiBinary;
|
|
params.DateTimeFormat := dtfBinary;
|
|
if histcrypt.enabled then
|
|
params.Password := histcrypt.pwd;
|
|
// params.MonitorBy := mbFlatFile;
|
|
// params.SQLiteAdvanced := 'page_size = 4096';
|
|
sql.FormatOptions.QuoteIdentifiers := True;
|
|
sql.FormatOptions.StrsTrim := False;
|
|
sql.FetchOptions.Mode := fmAll;
|
|
sql.FetchOptions.Items := [fiBlobs, fiDetails, fiMeta];
|
|
sql.UpdateOptions.LockWait := True;
|
|
sql.TxOptions.AutoCommit := True;
|
|
sql.TxOptions.Isolation := xiReadCommitted;
|
|
sql.ResourceOptions.CmdExecMode := amNonBlocking;
|
|
sql.ResourceOptions.SilentMode := True;
|
|
sql.Connected := True;
|
|
except
|
|
Exit;
|
|
end;
|
|
|
|
// WAL checkpoint at 512KB (128 * 4096 bytes page)
|
|
if Assigned(sql.ConnectionIntf.CliObj) then
|
|
TSQLiteDatabase(sql.ConnectionIntf.CliObj).AutoCheckpoint := 128;
|
|
// OutputDebugString(PChar(TSQLiteDatabase(sql.ConnectionIntf.CliObj).CharacterSet));
|
|
|
|
OpenConvTable;
|
|
OpenSrvHistTable;
|
|
OpenStickersTable;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TSQLDatabase.Disconnect;
|
|
begin
|
|
if Assigned(sql) then
|
|
begin
|
|
|
|
if sql.Connected then
|
|
|
|
try
|
|
|
|
sql.Connected := False;
|
|
|
|
if optimizeDB then
|
|
|
|
begin
|
|
|
|
sqlvalid := TFDSQLiteValidate.Create(nil);
|
|
|
|
sqlvalid.DriverLink := sqldrv;
|
|
|
|
sqlvalid.Password := histcrypt.pwd;
|
|
|
|
sqlvalid.Sweep; // VACUUM
|
|
|
|
sqlvalid.Free;
|
|
|
|
end;
|
|
|
|
except end;
|
|
|
|
FreeAndNil(sql);
|
|
|
|
end;
|
|
|
|
if Assigned(sqlcontains) then
|
|
FreeAndNil(sqlcontains);
|
|
end;
|
|
|
|
function TSQLDatabase.CheckIntegrity: Boolean;
|
|
begin
|
|
try
|
|
sqlvalid := TFDSQLiteValidate.Create(nil);
|
|
sqlvalid.DriverLink := sqldrv;
|
|
sqlvalid.Database := AccPath + accountFilename;
|
|
sqlvalid.Password := histcrypt.pwd;
|
|
sqlvalid.Options := []; // Quick check, do not validate indexes
|
|
Result := sqlvalid.CheckOnly;
|
|
sqlvalid.Free;
|
|
|
|
if not Result then
|
|
messageDlg(GetTranslation('History database integrity validation failed, so it connot be encrypted or decrypted right now'), mtError, [mbOK])
|
|
except
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.ManageSecurity(action: TSecurityAction; const passwd: String = ''): Boolean;
|
|
|
|
function IsEncrypted: Boolean;
|
|
begin
|
|
try
|
|
Result := not (sqlsec.CheckEncryption = ' |
|
except
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := False;
|
|
sqlsec := TFDSQLiteSecurity.Create(nil);
|
|
sqlsec.DriverLink := sqldrv;
|
|
sqlsec.Options := [soSetLargeCache];
|
|
sqlsec.Database := AccPath + accountFilename;
|
|
|
|
if action = SA_CHECK then
|
|
begin
|
|
sqlsec.Password := histcrypt.pwd;
|
|
Result := IsEncrypted;
|
|
sqlsec.Free;
|
|
Exit;
|
|
end;
|
|
|
|
if not CheckIntegrity then
|
|
Exit;
|
|
Disconnect;
|
|
|
|
try case action of
|
|
SA_ENCRYPT:
|
|
begin
|
|
sqlsec.Password := passwd;
|
|
sqlsec.SetPassword;
|
|
Result := True;
|
|
end;
|
|
SA_DECRYPT:
|
|
begin
|
|
sqlsec.Password := passwd;
|
|
sqlsec.RemovePassword;
|
|
Result := True;
|
|
end;
|
|
SA_CHANGEPASS:
|
|
begin
|
|
sqlsec.Password := histcrypt.pwd;
|
|
sqlsec.ToPassword := passwd;
|
|
sqlsec.ChangePassword;
|
|
Result := True;
|
|
end;
|
|
end except end;
|
|
|
|
histcrypt.enabled := IsEncrypted;
|
|
if histcrypt.enabled then
|
|
histcrypt.pwd := passwd
|
|
else
|
|
histcrypt.pwd := '';
|
|
Connect;
|
|
|
|
sqlsec.Free;
|
|
end;
|
|
|
|
function TSQLDatabase.OpenTable(const Table: String): Boolean;
|
|
var
|
|
MIQ: TFDMetaInfoQuery;
|
|
begin
|
|
Result := False;
|
|
MIQ := TFDMetaInfoQuery.Create(sql);
|
|
MIQ.Connection := sql;
|
|
MIQ.MetaInfoKind := mkTables;
|
|
if MIQ.OpenOrExecute and (MIQ.RecordCount > 0) then
|
|
begin
|
|
MIQ.First;
|
|
while not MIQ.Eof do
|
|
begin
|
|
if MIQ.FieldByName('TABLE_NAME').AsString = Table then
|
|
begin
|
|
Result := True;
|
|
Break;
|
|
end;
|
|
MIQ.Next;
|
|
end;
|
|
end;
|
|
MIQ.Free;
|
|
end;
|
|
|
|
|
|
procedure TSQLDatabase.OpenConvTable;
|
|
|
|
begin
|
|
|
|
if not OpenTable(dbConv) then
|
|
|
|
CreateConvTable
|
|
|
|
else
|
|
|
|
MigrateConvTable;
|
|
|
|
end;
|
|
|
|
function GetConvSchema: String;
|
|
begin
|
|
Result := 'CREATE TABLE IF NOT EXISTS "' + dbConv + '" ('
|
|
+ '"When" DATETIME NOT NULL,'
|
|
+ '"Kind" INTEGER NOT NULL,'
|
|
+ '"Chat" TEXT NOT NULL,'
|
|
+ '"Who" TEXT NOT NULL,'
|
|
+ '"Text" TEXT,'
|
|
+ '"Binary" BLOB,'
|
|
+ '"Flags" INTEGER DEFAULT 0,'
|
|
+ '"Out" INTEGER DEFAULT 0,'
|
|
+ '"WID" GUID,'
|
|
+ '"MsgID" UINT64);';
|
|
end;
|
|
|
|
function GetConvIndex: String;
|
|
begin
|
|
Result := 'CREATE INDEX IF NOT EXISTS "mainindex" ON "' + dbConv + '" ("Chat", "When");';
|
|
end;
|
|
|
|
function GetSrvHistSchema: String;
|
|
begin
|
|
Result := 'CREATE TABLE IF NOT EXISTS "' + dbSrvHist + '" ('
|
|
+ '"UID" TEXT NOT NULL,'
|
|
+ '"LastMsgId" UINT64 DEFAULT 0,'
|
|
+ '"LastRead" UINT64 DEFAULT 0,'
|
|
+ '"PatchVersion" TEXT);';
|
|
end;
|
|
|
|
function GetSrvHistIndex: String;
|
|
begin
|
|
Result := 'CREATE UNIQUE INDEX IF NOT EXISTS "userid" ON "' + dbSrvHist + '" ("UID");';
|
|
end;
|
|
|
|
procedure TSQLDatabase.CreateConvTable;
|
|
var
|
|
qry: TFDQuery;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
sql.StartTransaction;
|
|
try
|
|
qry.ExecSQL(GetConvSchema);
|
|
qry.ExecSQL(GetConvIndex);
|
|
sql.Commit;
|
|
except
|
|
sql.Rollback;
|
|
end;
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.MigrateConvTable;
|
|
var
|
|
qry: TFDQuery;
|
|
MIQ: TFDMetaInfoQuery;
|
|
MigrateMsgID: Boolean;
|
|
begin
|
|
MigrateMsgID := false;
|
|
MIQ := TFDMetaInfoQuery.Create(sql);
|
|
MIQ.Connection := sql;
|
|
MIQ.MetaInfoKind := mkTableFields;
|
|
MIQ.ObjectName := dbConv;
|
|
if MIQ.OpenOrExecute and (MIQ.RecordCount > 0) then
|
|
begin
|
|
MIQ.First;
|
|
while not MIQ.Eof do
|
|
begin
|
|
if MIQ.Fields.FieldByName('COLUMN_NAME').AsWideString = 'MsgID' then
|
|
if MIQ.Fields.FieldByName('COLUMN_DATATYPE').AsInteger = Integer(dtInt32) then
|
|
MigrateMsgID := true;
|
|
MIQ.Next;
|
|
end;
|
|
end;
|
|
MIQ.Free;
|
|
|
|
if MigrateMsgID then
|
|
begin
|
|
OutputDebugString(PChar('Changing MsgID type from INTEGER to UINT64'));
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
sql.StartTransaction;
|
|
try
|
|
qry.ExecSQL('ALTER TABLE "' + dbConv + '" RENAME TO "Temp";');
|
|
qry.ExecSQL('DROP INDEX IF EXISTS "mainindex";');
|
|
qry.ExecSQL(GetConvSchema);
|
|
qry.ExecSQL(GetConvIndex);
|
|
qry.ExecSQL('INSERT INTO "' + dbConv + '" SELECT * FROM "Temp";');
|
|
qry.ExecSQL('DROP TABLE "Temp";');
|
|
sql.Commit;
|
|
except
|
|
sql.Rollback;
|
|
end;
|
|
qry.ExecSQL('VACUUM;');
|
|
qry.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDatabase.DeleteChat(const chat: TUID);
|
|
begin
|
|
sql.ExecSQL('DELETE FROM "' + dbConv + '" WHERE "Chat" = ' + AnsiQuotedStr(chat, ''''), True);
|
|
end;
|
|
|
|
|
|
procedure TSQLDatabase.WriteEvent(const chat: TUID; ev: Thevent);
|
|
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
txt: String;
|
|
bin: TBytes;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" = :when; LIMIT 1';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('when').AsDateTime := ev.when;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
ev.when := IncMillisecond(ev.when);
|
|
qry.Free;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
|
|
qry.SQL.Add('INSERT INTO "' + dbConv + '" VALUES (:when, :kind, :chat, :who, :text, :binary, :flags, :out, :wid, :msgid)');
|
|
|
|
qry.Params[0].DataType := ftDateTime;
|
|
qry.Params[1].DataType := ftInteger;
|
|
qry.Params[2].DataType := ftWideString;
|
|
qry.Params[3].DataType := ftWideString;
|
|
qry.Params[4].DataType := ftWideString;
|
|
qry.Params[5].DataType := ftBlob;
|
|
qry.Params[6].DataType := ftInteger;
|
|
qry.Params[7].DataType := ftInteger;
|
|
qry.Params[8].DataType := ftGuid;
|
|
qry.Params[9].DataType := ftLargeint;
|
|
|
|
qry.ParamByName('when').AsDateTime := ev.when;
|
|
qry.ParamByName('kind').AsInteger := ev.kind;
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('who').AsString := ev.who.UID;
|
|
txt := ev.textData;
|
|
if txt = '' then
|
|
qry.ParamByName('text').Value := Null
|
|
else
|
|
qry.ParamByName('text').AsWideString := txt;
|
|
bin := ev.binaryData;
|
|
if not (bin = nil) and (Length(bin) > 0) then
|
|
qry.ParamByName('binary').Value := bin
|
|
else
|
|
qry.ParamByName('binary').Value := Null;
|
|
qry.ParamByName('flags').AsInteger := ev.flags;
|
|
qry.ParamByName('out').AsInteger := IfThen(ev.outgoing, 1, 0);
|
|
if ev.WID = '' then
|
|
qry.ParamByName('wid').Value := Null
|
|
else
|
|
qry.ParamByName('wid').AsGUID := StringToGUID('{' + ev.wid + '}');
|
|
if ev.ID > 0 then
|
|
qry.ParamByName('msgid').AsLargeInt := ev.ID
|
|
else
|
|
qry.ParamByName('msgid').Value := Null;
|
|
qry.ExecSQL;
|
|
qry.Free;
|
|
end;
|
|
|
|
|
|
procedure TSQLDatabase.WriteMsgIDs(const chat: TUID; ReqID: Integer; MsgID: TMsgID; const WID: RawByteString);
|
|
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
if wid = '' then
|
|
Exit;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'UPDATE OR IGNORE "' + dbConv + '" SET "WID" = :wid, "MsgID" = :msgid WHERE "Chat" = :chat AND "When" >= :userts AND "MsgID" = :reqid';
|
|
qry.ParamByName('wid').AsGUID := StringToGUID('{' + wid + '}');
|
|
qry.ParamByName('msgid').AsLargeInt := MsgID;
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('userts').AsDateTime := userts;
|
|
qry.ParamByName('reqid').AsInteger := ReqID;
|
|
qry.ExecSQL;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDatabase.WriteMsgFlags(const chat: TUID; MsgID: TMsgID; Flags: Integer);
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
if MsgID = 0 then
|
|
Exit;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'UPDATE OR IGNORE "' + dbConv + '" SET "Flags" = :flags WHERE "Chat" = :chat AND "When" >= :userts AND "MsgID" = :msgid';
|
|
qry.ParamByName('flags').AsInteger := Flags;
|
|
qry.ParamByName('msgid').AsLargeInt := MsgID;
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('userts').AsDateTime := userts;
|
|
qry.ExecSQL;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.RecordToEvent(var qry: TFDQuery): Thevent;
|
|
|
|
var
|
|
|
|
schat, swho: String;
|
|
|
|
chat, who: TICQContact;
|
|
|
|
begin
|
|
|
|
schat := qry.FieldByName('Chat').AsString;
|
|
|
|
if Account.AccProto.getMyInfo.equals(schat) then
|
|
chat := Account.AccProto.getMyInfo
|
|
else
|
|
chat := Account.AccProto.getContact(schat);
|
|
|
|
|
|
swho := qry.FieldByName('Who').AsString;
|
|
|
|
if Account.AccProto.getMyInfo.equals(swho) then
|
|
who := Account.AccProto.getMyInfo
|
|
else
|
|
who := Account.AccProto.getContact(swho);
|
|
|
|
Result := Thevent.new(
|
|
qry.FieldByName('Kind').AsInteger,
|
|
chat,
|
|
who,
|
|
qry.FieldByName('When').AsDateTime,
|
|
qry.FieldByName('Text').AsWideString,
|
|
[],
|
|
qry.FieldByName('Flags').AsInteger,
|
|
0,
|
|
Copy(qry.FieldByName('WID').AsString, 2, 36)
|
|
);
|
|
Result.outgoing := qry.FieldByName('Out').AsInteger = 1;
|
|
Result.ID := qry.FieldByName('MsgID').AsLargeInt;
|
|
Result.rawBin := qry.FieldByName('Binary').AsBytes;
|
|
if Assigned(qry.FieldList.Find('rowid')) then
|
|
Result.rowID := qry.FieldByName('rowid').AsInteger;
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.QueryToEventArray(var qry: TFDQuery; var events: Thevents; inverse: Boolean = False): Boolean;
|
|
|
|
var
|
|
|
|
c: Integer;
|
|
|
|
begin
|
|
|
|
Result := False;
|
|
|
|
if not qry.OpenOrExecute or (qry.RecordCount = 0) then
|
|
|
|
Exit;
|
|
Result := True;
|
|
|
|
SetLength(events, qry.RecordCount);
|
|
c := 0;
|
|
|
|
if inverse then
|
|
begin
|
|
qry.Last;
|
|
while (not qry.Bof) do
|
|
begin
|
|
events[c] := RecordToEvent(qry);
|
|
Inc(c);
|
|
qry.Prior;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
qry.First;
|
|
while not qry.Eof do
|
|
begin
|
|
events[c] := RecordToEvent(qry);
|
|
Inc(c);
|
|
qry.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.RecordToSticker(var qry: TFDQuery): TStickerPack;
|
|
|
|
begin
|
|
|
|
Result := Default(TStickerPack);
|
|
|
|
Result.Id := qry.FieldByName('Id').AsInteger;
|
|
|
|
Result.Name := qry.FieldByName('Name').AsWideString;
|
|
|
|
Result.Purchased := qry.FieldByName('Purchased').AsInteger = 1;
|
|
|
|
Result.UserSticker := qry.FieldByName('Usersticker').AsInteger = 1;
|
|
|
|
Result.Priority := qry.FieldByName('Priority').AsInteger;
|
|
|
|
Result.Count := qry.FieldByName('Count').AsInteger;
|
|
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.QueryToStickersArray(var qry: TFDQuery; var stickers: TStickerPacks): Boolean;
|
|
|
|
var
|
|
|
|
c: Integer;
|
|
|
|
begin
|
|
|
|
Result := False;
|
|
|
|
if not qry.OpenOrExecute or (qry.RecordCount = 0) then
|
|
|
|
Exit;
|
|
Result := True;
|
|
|
|
SetLength(stickers, qry.RecordCount);
|
|
c := 0;
|
|
|
|
qry.First;
|
|
while not qry.Eof do
|
|
begin
|
|
stickers[c] := RecordToSticker(qry);
|
|
Inc(c);
|
|
qry.Next;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.GetExistingChats: TUIDS;
|
|
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
SetLength(Result, 0);
|
|
try
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT DISTINCT "Chat" FROM "' + dbConv + '" ORDER BY "Chat" ASC';
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
SetLength(Result, qry.RecordCount);
|
|
i := 0;
|
|
qry.First;
|
|
while not qry.Eof do
|
|
begin
|
|
Result[i] := qry.FieldByName('Chat').AsString;
|
|
qry.Next;
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
|
|
end;
|
|
|
|
function TSQLDatabase.ChatExists(const chat: TUID): Boolean;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := False;
|
|
try
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT "Chat" FROM "' + dbConv + '" WHERE "Chat" = :chat LIMIT 1';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
Result := qry.OpenOrExecute and (qry.RecordCount > 0);
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.GetAll(const chat: TUID): Thevents;
|
|
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
SetLength(Result, 0);
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT *, "rowid" FROM "' + dbConv + '" WHERE "Chat" = :chat ORDER BY "When" ASC';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
QueryToEventArray(qry, Result);
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
function TSQLDatabase.GetLastMulti(const chat: TUID; offset, cnt: Integer; var noMoreMessages: Boolean): Thevents;
|
|
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
c: Integer;
|
|
|
|
begin
|
|
SetLength(Result, 0);
|
|
noMoreMessages := False;
|
|
qry := nil;
|
|
try
|
|
c := GetEventCount(chat);
|
|
noMoreMessages := (offset + cnt) >= c;
|
|
if c > 0 then
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat ORDER BY "When" DESC LIMIT ' + IntToStr(cnt) + ' OFFSET ' + IntToStr(offset) + ';';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
QueryToEventArray(qry, Result, True);
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetLastSingle(const chat: TUID; cnt: Integer): Thevent;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
c: Integer;
|
|
|
|
begin
|
|
|
|
Result := nil;
|
|
qry := nil;
|
|
try
|
|
c := GetEventCount(chat);
|
|
if (c > 0) and (cnt <= c) then
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat ORDER BY "When" DESC LIMIT 1 OFFSET ' + IntToStr(cnt-1) + ';';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := RecordToEvent(qry);
|
|
end;
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetByTime(const chat: TUID; thetime: TDateTime): Thevent;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := nil;
|
|
|
|
if thetime <= 0 then
|
|
Exit;
|
|
|
|
try
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" = :time;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('time').AsDateTime := thetime;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := RecordToEvent(qry);
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetByRowID(const chat: TUID; rowID: Integer): Thevent;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := nil;
|
|
|
|
if rowID <= 0 then
|
|
Exit;
|
|
|
|
try
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "rowid" = :rowid LIMIT 1;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('rowid').AsInteger := rowID;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := RecordToEvent(qry);
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetByMsgID(const chat: TUID; MsgID: TMsgID; Recent: Boolean = True): Thevent;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := nil;
|
|
|
|
if msgID < 0 then
|
|
Exit;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" >= :userts AND "MsgID" = :msgid LIMIT 1;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
if Recent then
|
|
qry.ParamByName('userts').AsDateTime := userts
|
|
else
|
|
qry.ParamByName('userts').AsFloat := 0;
|
|
qry.ParamByName('msgid').AsLargeInt := MsgID;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := RecordToEvent(qry);
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetByWID(const chat: TUID; const wid: String): Thevent;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := nil;
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "WID" = :wid;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('wid').AsGUID := StringToGUID('{' + wid + '}');
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := RecordToEvent(qry);
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetBySender(const chat, sender: TUID): Thevents;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
SetLength(Result, 0);
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbConv + '" WHERE "Chat" = :chat AND "Who" = :sender ORDER BY "When" ASC;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('sender').AsString := sender;
|
|
QueryToEventArray(qry, Result, True);
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetByTimeRange(const chat: TUID; fromTime, toTime: TDateTime; var noMoreMessages: Boolean; toInclusive: Boolean = True): Thevents;
|
|
var
|
|
qry, qry2: TFDQuery;
|
|
|
|
begin
|
|
|
|
SetLength(Result, 0);
|
|
noMoreMessages := False;
|
|
qry := nil;
|
|
qry2 := nil;
|
|
|
|
if fromTime < 0 then
|
|
fromTime := 0;
|
|
if toTime <= 0 then
|
|
Exit;
|
|
|
|
try
|
|
if GetEventCount(chat) > 0 then
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
if toInclusive then
|
|
qry.SQL.Text := 'SELECT *, "rowid" FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" BETWEEN :fromTime AND :toTime ORDER BY "When" ASC;'
|
|
else
|
|
qry.SQL.Text := 'SELECT *, "rowid" FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" >= :fromTime AND "When" < :toTime ORDER BY "When" ASC;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('fromTime').AsDateTime := fromTime;
|
|
qry.ParamByName('toTime').AsDateTime := toTime;
|
|
if QueryToEventArray(qry, Result) then
|
|
try
|
|
qry2 := TFDQuery.Create(sql);
|
|
qry2.Connection := sql;
|
|
qry2.SQL.Text := 'SELECT "rowid" FROM "' + dbConv + '" WHERE "Chat" = :chat ORDER BY "When" ASC LIMIT 1;';
|
|
qry2.ParamByName('chat').AsString := chat;
|
|
if qry2.OpenOrExecute then
|
|
begin
|
|
qry.First; qry2.First;
|
|
noMoreMessages := qry.FieldByName('rowid').AsInteger = qry2.FieldByName('rowid').AsInteger;
|
|
end;
|
|
finally
|
|
if Assigned(qry2) then
|
|
FreeAndNil(qry2);
|
|
end else
|
|
noMoreMessages := (fromTime = 0);
|
|
end else
|
|
noMoreMessages := True;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDatabase.DeleteByTimeRange(const chat: TUID; fromTime, toTime: TDateTime);
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
if toTime <= 0 then
|
|
Exit;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'DELETE FROM "' + dbConv + '" WHERE "Chat" = :chat AND "When" BETWEEN :fromTime AND :toTime;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('fromTime').AsDateTime := fromTime;
|
|
qry.ParamByName('toTime').AsDateTime := toTime;
|
|
qry.ExecSQL;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TSQLDatabase.DeleteBySender(const chat, sender: TUID);
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
if sender = '' then
|
|
Exit;
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'DELETE FROM "' + dbConv + '" WHERE "Chat" = :chat AND "Who" = :sender';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('sender').AsString := sender;
|
|
qry.ExecSQL;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.DataSearch(const chat: TUID; const txt: String; caseSensitive: Boolean = False): Thevents;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
|
|
SetLength(Result, 0);
|
|
Result := nil;
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT *, "rowid" FROM "' + dbConv + '" WHERE "Chat" = :chat AND CONTAINS("Text", :txt, :case) ORDER BY "When" ASC;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
qry.ParamByName('txt').AsWideString := txt;
|
|
qry.ParamByName('case').AsBoolean := caseSensitive;
|
|
QueryToEventArray(qry, Result);
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetEventCount(const chat: TUID): Integer;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
|
|
Result := 0;
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT COUNT(*) FROM "' + dbConv + '" WHERE "Chat" = :chat;';
|
|
qry.ParamByName('chat').AsString := chat;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := qry.Fields[0].AsInteger;
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDatabase.UpdateProgress(Sender: TObject; TickCount: Cardinal; var Reset: Boolean);
|
|
begin
|
|
if Assigned(taskDlg) then
|
|
if cUID.Pos >= taskDlg.ProgressBar.Max then
|
|
begin
|
|
taskDlg.Text := GetTranslation('History was successfully converted to SQLite format.\nYou can now proceed by pressing Continue.');
|
|
taskDlg.FooterText := GetTranslation('Contacts processed') + ': ' + IntToStr(taskDlg.ProgressBar.Max);
|
|
taskDlg.ProgressBar.Position := taskDlg.ProgressBar.Max;
|
|
taskDlg.Buttons[0].Enabled := True;
|
|
end
|
|
else
|
|
begin
|
|
taskDlg.FooterText := GetTranslation('Processing contact') + ' ' + cUID.UID;
|
|
taskDlg.ProgressBar.Position := cUID.Pos;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDatabase.ConvertHistory;
|
|
var
|
|
uins: TStringDynArray;
|
|
task: ITask;
|
|
begin
|
|
LogEvent('History: converting to SQLite');
|
|
|
|
if TDirectory.Exists(AccPath + historyPath) then
|
|
uins := TDirectory.GetFiles(AccPath + historyPath);
|
|
|
|
if Length(uins) = 0 then
|
|
Exit;
|
|
|
|
task := TTask.Create(procedure()
|
|
var
|
|
hist: THistory;
|
|
qry: TFDQuery;
|
|
ev: Pointer;
|
|
i, k: Integer;
|
|
uin, txt: String;
|
|
bin: TBytes;
|
|
begin
|
|
sql.StartTransaction;
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.FetchOptions.AutoClose := False;
|
|
qry.SQL.Add('INSERT INTO "' + dbConv + '" VALUES (:when, :kind, :chat, :who, :text, :binary, :flags, :out, :wid, :msgid);');
|
|
try
|
|
for i := 0 to Length(uins) - 1 do
|
|
try
|
|
uin := ExtractFileName(uins[i]);
|
|
cUID.UID := uin;
|
|
cUID.Pos := i;
|
|
|
|
hist := Thistory.Create('');
|
|
OutputDebugString(PChar('Start ' + uin));
|
|
hist.old_Load(Account.AccProto.GetICQContact(uin));
|
|
|
|
k := 0;
|
|
qry.Params.ArraySize := hist.old_eventList.Count;
|
|
qry.Params[0].DataType := ftDateTime;
|
|
qry.Params[1].DataType := ftInteger;
|
|
qry.Params[2].DataType := ftWideString;
|
|
qry.Params[3].DataType := ftWideString;
|
|
qry.Params[4].DataType := ftWideString;
|
|
qry.Params[5].DataType := ftBlob;
|
|
qry.Params[6].DataType := ftInteger;
|
|
qry.Params[7].DataType := ftInteger;
|
|
qry.Params[8].DataType := ftGuid;
|
|
qry.Params[9].DataType := ftLargeint;
|
|
|
|
for ev in hist.old_eventList do
|
|
with Thevent(ev) do
|
|
begin
|
|
qry.Params[0].Values[k] := when;
|
|
qry.Params[1].Values[k] := kind;
|
|
qry.Params[2].Values[k] := uin;
|
|
qry.Params[3].Values[k] := who.UID;
|
|
txt := old_getTextPart;
|
|
if txt = '' then
|
|
qry.Params[4].Values[k] := Null
|
|
else
|
|
qry.Params[4].Values[k] := txt;
|
|
bin := old_getBinPart;
|
|
if not (bin = nil) and (Length(bin) > 0) then
|
|
qry.Params[5].Values[k] := bin
|
|
else
|
|
qry.Params[5].Values[k] := Null;
|
|
qry.Params[6].Values[k] := flags;
|
|
qry.Params[7].Values[k] := IfThen(old_isMyEvent, 1, 0);
|
|
if WID = '' then
|
|
qry.Params[8].Values[k] := Null
|
|
else
|
|
qry.Params[8].AsGUIDs[k] := StringToGUID('{' + WID + '}');
|
|
if ID > 0 then
|
|
qry.Params[9].Values[k] := ID
|
|
else
|
|
qry.Params[9].Values[k] := Null;
|
|
Inc(k);
|
|
end;
|
|
qry.Execute(hist.old_eventList.Count);
|
|
hist.Free;
|
|
except
|
|
OutputDebugString(PChar('Failed ' + uin));
|
|
end;
|
|
|
|
qry.Free;
|
|
sql.Commit;
|
|
LogEvent('History: conversion completed');
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
OutputDebugString(PChar(E.Message));
|
|
sql.Rollback;
|
|
end;
|
|
end;
|
|
end);
|
|
task.Start;
|
|
|
|
if (Win32MajorVersion >= 6) and ThemeServices.ThemesEnabled then
|
|
begin
|
|
taskDlg := TTaskDialog.Create(nil);
|
|
taskDlg.Caption := GetTranslation('History management');
|
|
taskDlg.Text := GetTranslation('Converting history to SQLite format.\nThis could take a while...');
|
|
taskDlg.CustomMainIcon.LoadFromResourceName(hInstance, 'SQLITE');
|
|
taskDlg.CustomFooterIcon.LoadFromResourceName(hInstance, 'USER');
|
|
taskDlg.ProgressBar.Min := 0;
|
|
taskDlg.ProgressBar.Max := Length(uins) - 1;
|
|
taskDlg.ProgressBar.Position := 0;
|
|
taskDlg.OnTimer := UpdateProgress;
|
|
taskDlg.FooterText := GetTranslation('Processing contact') + '...';
|
|
taskDlg.Flags := [tfUseHiconMain, tfUseHiconFooter, tfShowProgressBar, tfCallbackTimer, tfExpandFooterArea];
|
|
taskDlg.CommonButtons := [];
|
|
taskDlg.Buttons.Clear;
|
|
with taskDlg.Buttons.Add do
|
|
begin
|
|
Caption := GetTranslation('Continue');
|
|
Enabled := False;
|
|
end;
|
|
taskDlg.ExpandButtonCaption := '';
|
|
taskDlg.Execute;
|
|
FreeAndNil(taskDlg);
|
|
end;
|
|
|
|
while not task.Wait(100) do
|
|
Application.ProcessMessages;
|
|
end;
|
|
|
|
// Stickers
|
|
|
|
procedure TSQLDatabase.OpenStickersTable;
|
|
begin
|
|
|
|
if not OpenTable(dbStickers) then
|
|
CreateStickersTable;
|
|
end;
|
|
|
|
procedure TSQLDatabase.CreateStickersTable;
|
|
var
|
|
qry: TFDQuery;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
sql.StartTransaction;
|
|
try
|
|
qry.ExecSQL('CREATE TABLE IF NOT EXISTS "' + dbStickers + '" ('
|
|
+ '"Id" INTEGER NOT NULL,'
|
|
+ '"Name" TEXT NOT NULL,'
|
|
+ '"Purchased" INTEGER DEFAULT 0,'
|
|
+ '"Usersticker" INTEGER DEFAULT 0,'
|
|
+ '"Priority" INTEGER DEFAULT 0,'
|
|
+ '"Count" INTEGER DEFAULT 0);');
|
|
qry.ExecSQL('CREATE UNIQUE INDEX IF NOT EXISTS "stickerid" ON "' + dbStickers + '" ("Id");');
|
|
sql.Commit;
|
|
except
|
|
sql.Rollback;
|
|
end;
|
|
qry.Free;
|
|
end;
|
|
|
|
//procedure TSQLDatabase.MigrateStickersTable;
|
|
//var
|
|
// qry: TFDQuery;
|
|
|
|
// MIQ: TFDMetaInfoQuery;
|
|
|
|
//begin
|
|
// MIQ := TFDMetaInfoQuery.Create(sql);
|
|
// MIQ.Connection := sql;
|
|
// MIQ.MetaInfoKind := mkTableFields;
|
|
// MIQ.ObjectName := dbStickers;
|
|
// if MIQ.OpenOrExecute and (MIQ.RecordCount > 0) then
|
|
// begin
|
|
// MIQ.First;
|
|
// while not MIQ.Eof do
|
|
// begin
|
|
// if MIQ.Fields.FieldByName('COLUMN_NAME').AsWideString = 'StoreId' then
|
|
// begin
|
|
// MIQ.Free;
|
|
// Exit;
|
|
// end;
|
|
// MIQ.Next;
|
|
// end;
|
|
// end;
|
|
// MIQ.Free;
|
|
|
|
//
|
|
|
|
// qry := TFDQuery.Create(sql);
|
|
// qry.Connection := sql;
|
|
// sql.StartTransaction;
|
|
// try
|
|
// qry.ExecSQL('DROP TABLE "' + dbStickers + '";');
|
|
// sql.Commit;
|
|
// except
|
|
// sql.Rollback;
|
|
// end;
|
|
// qry.Free;
|
|
//
|
|
// CreateStickersTable;
|
|
//end;
|
|
|
|
procedure TSQLDatabase.ClearStickerPacks;
|
|
var
|
|
qry: TFDQuery;
|
|
txt: String;
|
|
bin: TBytes;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.ExecSQL('DELETE FROM "' + dbStickers + '"');
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.AddStickerPack(const StickerPack: TStickerPack);
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
txt: String;
|
|
bin: TBytes;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
|
|
qry.SQL.Add('REPLACE INTO "' + dbStickers + '" VALUES (:id, :name, :purchased, :user, :priority, :count)');
|
|
|
|
qry.ParamByName('id').AsInteger := StickerPack.Id;
|
|
qry.ParamByName('name').AsWideString := StickerPack.Name;
|
|
qry.ParamByName('purchased').AsInteger := IfThen(StickerPack.Purchased, 1, 0);
|
|
qry.ParamByName('user').AsInteger := IfThen(StickerPack.UserSticker, 1, 0);
|
|
qry.ParamByName('priority').AsInteger := StickerPack.Priority;
|
|
qry.ParamByName('count').AsInteger := StickerPack.Count;
|
|
qry.ExecSQL;
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.ChangeStickerPackStatus(const PackId: String; Status: Boolean);
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
txt: String;
|
|
bin: TBytes;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Add('UPDATE "' + dbStickers + '" SET "purchased" = :purchased WHERE "id" = :packid');
|
|
qry.ParamByName('purchased').AsInteger := IfThen(Status, 1, 0);
|
|
qry.ParamByName('packid').AsInteger := StrToInt(PackId);
|
|
qry.ExecSQL;
|
|
qry.Free;
|
|
end;
|
|
|
|
function TSQLDatabase.GetStickerPacksCount: Integer;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
|
|
Result := 0;
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT COUNT(*) FROM "' + dbStickers + '"';
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result := qry.Fields[0].AsInteger;
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDatabase.GetStickerPacks(ActiveOnly: Boolean = False): TStickerPacks;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbStickers + '"' + IfThen(ActiveOnly, ' WHERE "purchased" = 1', '') + ' ORDER BY "Id"';
|
|
if not QueryToStickersArray(qry, Result) then
|
|
SetLength(Result, 0);
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
// Server history
|
|
|
|
procedure TSQLDatabase.OpenSrvHistTable;
|
|
begin
|
|
|
|
if not OpenTable(dbSrvHist) then
|
|
|
|
CreateSrvHistTable
|
|
|
|
else
|
|
|
|
MigrateSrvHistTable;
|
|
|
|
end;
|
|
|
|
|
|
procedure TSQLDatabase.CreateSrvHistTable;
|
|
var
|
|
qry: TFDQuery;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
sql.StartTransaction;
|
|
try
|
|
qry.ExecSQL(GetSrvHistSchema);
|
|
qry.ExecSQL(GetSrvHistIndex);
|
|
sql.Commit;
|
|
except
|
|
sql.Rollback;
|
|
end;
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.MigrateSrvHistTable;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
MIQ: TFDMetaInfoQuery;
|
|
|
|
begin
|
|
MIQ := TFDMetaInfoQuery.Create(sql);
|
|
MIQ.Connection := sql;
|
|
MIQ.MetaInfoKind := mkTableFields;
|
|
MIQ.ObjectName := dbSrvHist;
|
|
if MIQ.OpenOrExecute and (MIQ.RecordCount > 0) then
|
|
begin
|
|
MIQ.First;
|
|
while not MIQ.Eof do
|
|
begin
|
|
if MIQ.Fields.FieldByName('COLUMN_NAME').AsWideString = 'LastRead' then
|
|
begin
|
|
MIQ.Free;
|
|
Exit;
|
|
end;
|
|
MIQ.Next;
|
|
end;
|
|
end;
|
|
MIQ.Free;
|
|
|
|
|
|
OutputDebugString(PChar('Adding LastRead column to ServerHistory table'));
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
sql.StartTransaction;
|
|
try
|
|
qry.ExecSQL('ALTER TABLE "' + dbSrvHist + '" RENAME TO "Temp";');
|
|
qry.ExecSQL('DROP INDEX IF EXISTS "userid";');
|
|
qry.ExecSQL(GetSrvHistSchema);
|
|
qry.ExecSQL(GetSrvHistIndex);
|
|
qry.ExecSQL('INSERT INTO "' + dbSrvHist + '" ("UID", "LastMsgId", "PatchVersion") SELECT * FROM "Temp";');
|
|
qry.ExecSQL('DROP TABLE "Temp";');
|
|
sql.Commit;
|
|
except
|
|
sql.Rollback;
|
|
end;
|
|
qry.ExecSQL('VACUUM;');
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.UpdateLastMsg(const UID: TUID; LastMsgId: TMsgID; PatchVersion: String);
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
|
|
txt: String;
|
|
bin: TBytes;
|
|
tmp: TMsgId;
|
|
begin
|
|
if not TryStrToUInt64(PatchVersion, tmp) then
|
|
PatchVersion := '1';
|
|
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
|
|
qry.SQL.Add('INSERT INTO "' + dbSrvHist + '" VALUES (:uid, :last, 0, :patch) ON CONFLICT("UID") DO UPDATE SET "LastMsgId" = :last, "PatchVersion" = :patch;');
|
|
qry.ParamByName('uid').AsString := UID;
|
|
qry.ParamByName('last').AsLargeInt := LastMsgId;
|
|
qry.ParamByName('patch').AsString := PatchVersion;
|
|
qry.ExecSQL;
|
|
qry.Free;
|
|
end;
|
|
|
|
procedure TSQLDatabase.UpdateLastRead(const UID: TUID; LastRead: TMsgID);
|
|
var
|
|
|
|
qry: TFDQuery;
|
|
|
|
txt: String;
|
|
bin: TBytes;
|
|
tmp: TMsgId;
|
|
begin
|
|
qry := TFDQuery.Create(sql);
|
|
qry.Connection := sql;
|
|
qry.SQL.Add('INSERT INTO "' + dbSrvHist + '" VALUES (:uid, 0, :lastread, "1") ON CONFLICT("UID") DO UPDATE SET "LastRead" = :lastread;');
|
|
qry.ParamByName('uid').AsString := UID;
|
|
qry.ParamByName('lastread').AsLargeInt := LastRead;
|
|
qry.ExecSQL;
|
|
qry.Free;
|
|
end;
|
|
|
|
function TSQLDatabase.GetHistDlg(const UID: TUID): TSrvHist;
|
|
var
|
|
qry: TFDQuery;
|
|
|
|
begin
|
|
Result := Default(TSrvHist);
|
|
qry := TFDQuery.Create(sql);
|
|
try
|
|
qry.Connection := sql;
|
|
qry.SQL.Text := 'SELECT * FROM "' + dbSrvHist + '" WHERE "UID" = :uid;';
|
|
qry.ParamByName('uid').AsString := UID;
|
|
if qry.OpenOrExecute and (qry.RecordCount > 0) then
|
|
begin
|
|
qry.First;
|
|
Result.LastMsgId := qry.FieldByName('LastMsgId').AsLargeInt;
|
|
Result.LastRead := qry.FieldByName('LastRead').AsLargeInt;
|
|
Result.PatchVersion := qry.FieldByName('PatchVersion').AsString;
|
|
end;
|
|
finally
|
|
if Assigned(qry) then
|
|
FreeAndNil(qry);
|
|
end;
|
|
end;
|
|
|
|
|
|
initialization
|
|
|
|
FFDGUIxSilentMode := True;
|
|
|
|
FFDGUIxProvider := 'Console';
|
|
|
|
sqldrv := TFDPhysSQLiteDriverLink.Create(nil);
|
|
sqldrv.VendorHome := ModulesPath;
|
|
sqldrv.VendorLib := '..\sqlite3.dll';
|
|
|
|
finalization
|
|
if Assigned(sqldrv) then
|
|
FreeAndNil(sqldrv);
|
|
|
|
end.
|