diff --git a/.gitignore b/.gitignore index e32cb3801..af427fc96 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ *.ppu *.rst *.cgi -*.exe +# *.exe *.log *.bak* fp.ini @@ -20,13 +20,19 @@ fp.dsk *.lps *.res mangadownloader/lib +mangadownloader/languages/ updater/lib +updater/languages/ updaterslim/lib bin/ Release/ +3rd/ # compiled languages files *.mo # lazarus auto generated resources *.lrt + +# auto generated by git2revision +baseunits/revision.inc diff --git a/3rd/internettools b/3rd/internettools deleted file mode 160000 index 4f7490dd0..000000000 --- a/3rd/internettools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4f7490dd05ed6f2eeffa157f7921f46ae835ce53 diff --git a/README.md b/README.md index 2948a7ed5..e9d60d19b 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,59 @@ -# The Free Manga Downloader (FMD) +# Free Manga Downloader (FMD) -(Forked from https://sf.net/p/fmd) +(Forked from https://github.com/riderkick/FMD) -## Download the latest release +# DISCONTINUED, PLEASE MOVE ON TO FMD2 -[![Latest release](https://img.shields.io/github/release/riderkick/FMD.svg)](https://github.com/riderkick/FMD/releases/latest) [![Download latest release (Win32)](https://img.shields.io/github/downloads/riderkick/FMD/latest/fmd_0.9.158.0.7z.svg?label=Win32)](https://github.com/riderkick/FMD/releases/download/0.9.158.0/fmd_0.9.158.0.7z) [![Download latest release (Win64)](https://img.shields.io/github/downloads/riderkick/FMD/latest/fmd_0.9.158.0_Win64.7z.svg?label=Win64)](https://github.com/riderkick/FMD/releases/download/0.9.158.0/fmd_0.9.158.0_Win64.7z) +This fork of FMD will not be continued any further. You can still use it but it won't get any updates anymore. +You can still find us on our Discord server and talk about FMD or even FMD2, but you should still report bugs, etc. to its repository instead. -## Content +FMD2 can be found here: https://github.com/dazedcat19/FMD2 + + + +## Download -- [About FMD](#about-fmd) -- [Build instructions](#build-instructions) -- [Localization](#localization) +[![Latest release](https://img.shields.io/github/release/fmd-project-team/FMD.svg)](https://github.com/fmd-project-team/FMD/releases/latest) ## About FMD -The Free Manga Downloader is a free open source application written in Object Pascal for managing and downloading manga from various websites. The source code was released under the GPLv2 license. FMD homesite is at https://github.com/riderkick/FMD or http://sf.net/p/newfmd. - +This is an active fork of the Free Manga Downloader which is a free open source application written in Object Pascal for managing and downloading manga from various websites. The source code was released under the GPLv2 license. + ## Build instructions -In order to build FMD from the source code, you must install the latest version of Lazarus and Free Pascal Compiler from http://www.lazarus-ide.org/. Then you must install the following 3rd party libraries and components: - - - [RichMemo](https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/richmemo/) - - [Virtual TreeView](https://github.com/blikblum/VirtualTreeView-Lazarus/tree/lazarus-v4) (and `lclextensions` from the Releases page) - - [Synapse](https://sourceforge.net/p/synalist/code/HEAD/tree/trunk/) (at least revision `r160`) - - [InternetTools](https://github.com/benibela/internettools) - - [MultiLog](https://github.com/blikblum/multilog) - - [DCPCypt](https://sourceforge.net/projects/lazarus-ccr/) - -After everything is installed, open the file `md.lpi` by using Lazarus IDE. Make sure to add `ssl_openssl` to uses list of the `laz_synapse` package. -Then select `Run -> Build` to build the source code. If everything is ok, the binary file should be in `FMD_source_code_folder/bin`. - -If InternetTools fail to compile (incompatible PPU), make sure to compile them individually first. - +In order to build FMD from the source code, you must install the latest version of Lazarus and Free Pascal Compiler: +[![Lazarus IDE 2.0.6](https://img.shields.io/badge/Lazarus%20IDE-2.0.6-Blue.svg)](http://www.lazarus-ide.org/) + +To compile FMD some packages and components are needed. You can download and install most of them by the built-in Online Package Manager (OPM). +The following packages and components are used for building FMD: +![Synapse 40.1](https://img.shields.io/badge/Synapse-OPM%20(40.1)-Blue.svg) (Compile before "InternetTools") +![DCPCrypt 2.0.4.1](https://img.shields.io/badge/DCPCrypt-OPM%20(2.0.4.1)-Blue.svg) +![RichMemo (18.01.2020)](https://img.shields.io/badge/RichMemo-OPM%20(18.01.2020)-Blue.svg) +![LCL Extensions 0.6.1](https://img.shields.io/badge/LCL%20Extensions-OPM%20(0.6.1)-Blue.svg) (Compile before "Virtual TreeView") +![Virtual TreeView 5.5.3.1](https://img.shields.io/badge/Virtual%20TreeView-OPM%20(5.5.3.1)-Blue.svg) +[![MultiLog (02.12.2019)](https://img.shields.io/badge/MultiLog-git%20master%20commit%20fd700fa5343c1b0e08063f88a1e6761036b10efc%20(02.12.2019)-Blue.svg)](https://github.com/blikblum/multilog) +[![InternetTools (19.01.2020)](https://img.shields.io/badge/InternetTools-git%20master%20commit%20b834f9a3699e7d01bbd0cfafa8c4f7f75cff274d%20(19.01.2020)-Blue.svg)](https://github.com/benibela/internettools) + +After everything is installed, open the file `md.lpi` by using Lazarus IDE. +Make sure to add `ssl_openssl` to the uses list of `Synapse` and compile the package again. +To compile and build the source code of FMD select `Run -> Build`. If everything is ok, the binary file should be in `FMD_source_code_folder/bin`. + +If `InternetTools` fails to compile because of a missing or incompatible PPU, make sure to compile `Synapse` first. + +Some other external 3rd party tools and libraries are used as well: +[![7-Zip](https://img.shields.io/badge/7--Zip%20(Standalone)-19.00-Blue.svg)](https://www.7-zip.org) +[![Duktape](https://img.shields.io/badge/Duktape-2.5.0-Blue.svg)](https://github.com/grijjy/DelphiDuktape) +[![WebP (libwebp)](https://img.shields.io/badge/WebP%20(libwebp)-1.1.0-Blue.svg)](https://github.com/webmproject/libwebp/) +[![Lua](https://img.shields.io/badge/Lua-5.3.3-Blue.svg)](http://luabinaries.sourceforge.net/) +[![OpenSSL](https://img.shields.io/badge/OpenSSL-1.1.1d-Blue.svg)](https://www.openssl.org/) +[![SQLite](https://img.shields.io/badge/SQLite-3.30.1-Blue.svg)](https://www.sqlite.org/) + +These tools and libraries are not part of the source. You have to either download pre-compiled binaries, compile them yourself or just copy them from the latest FMD releases. + ## Localization -Translations are stored inside `languages` folder with `.po` extension. In order to translate FMD to your native languages you can copy `fmd.po` and rename it to `fmd.xx.po`, where `xx` stand for two-letter language code. Additionally you can add country code at the end of language code. For reference you can look at http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes and http://en.wikipedia.org/wiki/ISO_3166-1. For example `id_ID` will be recognized as `Bahasa Indonesia (Indonesia)`. To translate the content of the file you need to use translation tools like [Poedit](https://poedit.net). Once you have finished translating all of its content you can launch FMD and it will automatically detect your new languages upon startup. +Translations are stored inside `languages` folder with `.po` extension. +In order to translate FMD to your native language you can copy `fmd.po` and rename it to `fmd.xx.po`, where `xx` stand for two-letter language code. +Additionally you can add country code at the end of language code. For reference you can look at http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes and http://en.wikipedia.org/wiki/ISO_3166-1. For example `id_ID` will be recognized as `Bahasa Indonesia (Indonesia)`. +To translate the content of the file you need to use translation tools like [Poedit](https://poedit.net). +Once you have finished translating all of its content you can launch FMD and it will automatically detect your new languages upon startup. diff --git a/baseunits/BaseCrypto.pas b/baseunits/BaseCrypto.pas index 25c651bca..21bab4ff9 100644 --- a/baseunits/BaseCrypto.pas +++ b/baseunits/BaseCrypto.pas @@ -168,12 +168,13 @@ function AESDecryptCBCMD5Base64ZerosPadding(const s, key, iv: String): String; function MD5Hex(const s: String): String; var - h: array[0 .. 15] of Byte; + h: TBytes; begin with TDCP_md5.Create(nil) do try Init; UpdateStr(s); + SetLength(h, 16); Final(h); finally Free; diff --git a/baseunits/CheckUpdate.pas b/baseunits/CheckUpdate.pas index 33474b785..5cdf262c6 100644 --- a/baseunits/CheckUpdate.pas +++ b/baseunits/CheckUpdate.pas @@ -105,19 +105,22 @@ procedure TCheckUpdateThread.Execute; FChangelog := ''; Synchronize(@SyncStartUpdate); if not Terminated and FHTTP.Get(UPDATE_URL) then - with TStringList.Create do try - LoadFromStream(FHTTP.Document); - if Count <> 0 then begin - NameValueSeparator := '='; - FNewVersionString := Trim(Values['VERSION']); - if not TryStrToProgramVersion(FNewVersionString, FNewVersionNumber) then - FNewVersionNumber := StrToProgramVersion('0.0.0.0'); - if NewerVersion(FNewVersionNumber, FMD_VERSION_NUMBER) then - FUpdateURL := Trim(Values[UpperCase(FMD_TARGETOS)]); + begin + with TStringList.Create do try + LoadFromStream(FHTTP.Document); + if Count <> 0 then begin + NameValueSeparator := '='; + FNewVersionString := Trim(Values['VERSION']); + if not TryStrToProgramVersion(FNewVersionString, FNewVersionNumber) then + FNewVersionNumber := StrToProgramVersion('0.0.0.0'); + if NewerVersion(FNewVersionNumber, FMD_VERSION_NUMBER) then + FUpdateURL := Trim(Values[UpperCase(FMD_TARGETOS)]); + end; + finally + Free; end; - finally - Free; end; + if not Terminated and (FUpdateURL <> '') and FHTTP.Get(CHANGELOG_URL) then FChangelog := StreamToString(FHTTP.Document); Synchronize(@SyncEndUpdate); diff --git a/baseunits/DBDataProcess.pas b/baseunits/DBDataProcess.pas index e3f06d99c..e544f391f 100644 --- a/baseunits/DBDataProcess.pas +++ b/baseunits/DBDataProcess.pas @@ -264,16 +264,10 @@ procedure TDBDataProcess.ResetRecNo(Dataset: TDataSet); procedure TDBDataProcess.GoToRecNo(const ARecIndex: Integer); begin - if FRecNo<>ARecIndex then + if FQuery.RecNo<>(ARecIndex+1) then begin - if FRecNo=ARecIndex+1 then - FQuery.Prior - else - if FRecNo=ARecIndex-1 then - FQuery.Next - else - FQuery.RecNo:=ARecIndex+1; FRecNo:=ARecIndex; + FQuery.RecNo:=ARecIndex+1; end; end; @@ -831,6 +825,8 @@ function TDBDataProcess.DeleteData(const RecIndex: Integer): Boolean; Dec(FRecordCount); Result := True; except + on E: Exception do + Logger.SendException(ClassName+'['+Website+'].DeleteData.Error!',E); end; end; diff --git a/baseunits/DBUpdater.pas b/baseunits/DBUpdater.pas index 80f90fd15..54ed88696 100644 --- a/baseunits/DBUpdater.pas +++ b/baseunits/DBUpdater.pas @@ -5,42 +5,28 @@ interface uses - Classes, SysUtils, httpsendthread, BaseThread, FMDOptions, process, ComCtrls, - Controls, Dialogs, StdCtrls, Buttons, blcksock; + Classes, SysUtils, httpsendthread, FMDOptions, StatusBarDownload, + process, ComCtrls, Controls, Dialogs, Buttons; type { TDBUpdaterThread } - TDBUpdaterThread = class(TBaseThread) + TDBUpdaterThread = class(TStatusBarDownload) private - FStatusBar: TStatusBar; - FProgressBar: TProgressBar; - FButtonCancel: TSpeedButton; - FHTTP: THTTPSendThread; - FTotalSize: Integer; - FCurrentSize: Integer; + FCurrentId: Integer; FCurrentName: String; FFailedList: TStringList; - FCurrentId: Integer; - FStatusText: String; protected - procedure ButtonCancelClick(Sender: TObject); - procedure HTTPSockOnStatus(Sender: TObject; Reason: THookSocketReason; - const Value: String); procedure HTTPRedirected(const AHTTP: THTTPSendThread; const URL: String); protected procedure SyncStart; procedure SyncFinal; - procedure SyncStartDownload; - procedure SyncUpdateProgress; - procedure SyncUpdateStatus; procedure SyncUpdateHint; procedure SyncShowFailed; procedure SyncCloseUsed; procedure SyncReopenUsed; procedure SyncRemoveAttached; - procedure UpdateStatusText(const S: String); procedure Execute; override; public Items: TStringList; @@ -76,31 +62,6 @@ function GetDBURL(const AName: String): String; { TDBUpdaterThread } -procedure TDBUpdaterThread.ButtonCancelClick(Sender: TObject); -begin - Self.Terminate; -end; - -procedure TDBUpdaterThread.HTTPSockOnStatus(Sender: TObject; - Reason: THookSocketReason; const Value: String); -begin - if Terminated then - Exit; - if Reason = HR_ReadCount then - begin - if FTotalSize = 0 then - FTotalSize := StrToIntDef(Trim(FHTTP.Headers.Values['Content-Length']), 0); - Inc(FCurrentSize, StrToInt(Value)); - Synchronize(@SyncUpdateProgress); - end - else - if Reason = HR_Connect then - begin - FCurrentSize := 0; - FTotalSize := 0; - end; -end; - procedure TDBUpdaterThread.HTTPRedirected(const AHTTP: THTTPSendThread; const URL: String); begin @@ -111,110 +72,16 @@ procedure TDBUpdaterThread.HTTPRedirected(const AHTTP: THTTPSendThread; procedure TDBUpdaterThread.SyncStart; begin DBUpdaterThread := Self; - - FStatusBar := TStatusBar.Create(FormMain); - with FStatusBar do - begin - Parent := FormMain; - SimplePanel := False; - with Panels.Add do // panel for progress bar - Width := 100; - Panels.Add; // panel for progress text - Panels.Add; // panel for status text - end; - - FProgressBar := TProgressBar.Create(FormMain); - with FProgressBar do - begin - Parent := FStatusBar; - Align := alNone; - Smooth := True; - Style := pbstNormal; - Min := 0; - Width := FStatusBar.Panels[0].Width - 10; - Anchors := [akTop, akLeft, akBottom]; - AnchorSideTop.Control := FStatusBar; - AnchorSideTop.Side := asrTop; - AnchorSideLeft.Control := FStatusBar; - AnchorSideLeft.Side := asrTop; - AnchorSideBottom.Control := FStatusBar; - AnchorSideBottom.Side := asrBottom; - BorderSpacing.Top := 2; - BorderSpacing.Left := 5; - BorderSpacing.Bottom := 2; - end; - - FButtonCancel := TSpeedButton.Create(FormMain); - with FButtonCancel do - begin - Parent := FStatusBar; - Align := alNone; - AutoSize := True; - Caption := RS_ButtonCancel; - ShowCaption := True; - Flat := True; - Anchors := [akTop, akRight, akBottom]; - AnchorSideTop.Control := FStatusBar; - AnchorSideTop.Side := asrTop; - AnchorSideRight.Control := FStatusBar; - AnchorSideRight.Side := asrRight; - AnchorSideBottom.Control := FStatusBar; - AnchorSideBottom.Side := asrBottom; - BorderSpacing.Top := 2; - BorderSpacing.Right := 5; - BorderSpacing.Bottom := 2; - OnClick := @ButtonCancelClick; - end; end; procedure TDBUpdaterThread.SyncFinal; begin DBUpdaterThread := nil; - FHTTP.Sock.OnStatus := nil; - FreeAndNil(FStatusBar); - FreeAndNil(FProgressBar); - FreeAndNil(FButtonCancel); -end; - -procedure TDBUpdaterThread.SyncStartDownload; -begin - FCurrentSize := 0; - FTotalSize := 0; - FProgressBar.Max := 0; - FProgressBar.Position := 0; - FStatusBar.Panels[1].Text := ''; - FStatusBar.Panels[1].Width := 0; - FStatusBar.Panels[2].Text := Format('[%d/%d] ' + RS_Downloading, - [FCurrentId + 1, Items.Count, - FCurrentName + DBDATA_EXT]); -end; - -procedure TDBUpdaterThread.SyncUpdateProgress; -var - s: String; -begin - if FStatusBar = nil then - Exit; - if FProgressBar.Max <> FTotalSize then - FProgressBar.Max := FTotalSize; - if FProgressBar.Position <> FCurrentSize then - FProgressBar.Position := FCurrentSize; - - s := FormatByteSize(FCurrentSize); - if FTotalSize <> 0 then - s += '/' + FormatByteSize(FTotalSize); - FStatusBar.Panels[1].Width := FStatusBar.Canvas.TextWidth(s) + 10; - FStatusBar.Panels[1].Text := s; -end; - -procedure TDBUpdaterThread.SyncUpdateStatus; -begin - FStatusBar.Panels[2].Text := FStatusText; end; procedure TDBUpdaterThread.SyncUpdateHint; begin - FStatusBar.Hint := Trim(Items.Text); + StatusBar.Hint := Trim(Items.Text); end; procedure TDBUpdaterThread.SyncShowFailed; @@ -240,17 +107,9 @@ procedure TDBUpdaterThread.SyncRemoveAttached; dataProcess.RemoveFilter; end; -procedure TDBUpdaterThread.UpdateStatusText(const S: String); -begin - if FStatusText = S then - Exit; - FStatusText := S; - Synchronize(@SyncUpdateStatus); -end; - procedure TDBUpdaterThread.Execute; var - currentfilename, lurl: String; + currentfilename: String; cont: Boolean; used: Boolean; begin @@ -262,9 +121,9 @@ procedure TDBUpdaterThread.Execute; Break; try FCurrentName := Items[FCurrentId]; - Synchronize(@SyncStartDownload); - lurl := GetDBURL(FCurrentName); - if FHTTP.GET(GetDBURL(FCurrentName)) and (FHTTP.ResultCode < 300) then + UpdateStatusText(Format('[%d/%d] ' + + RS_Downloading, [FCurrentId + 1, Items.Count, FCurrentName + DBDATA_EXT])); + if HTTP.GET(GetDBURL(FCurrentName)) and (HTTP.ResultCode < 300) then begin cont := True; // save to data folder @@ -274,7 +133,7 @@ procedure TDBUpdaterThread.Execute; DeleteFile(currentfilename); if not FileExists(currentfilename) then begin - FHTTP.Document.SaveToFile(currentfilename); + HTTP.Document.SaveToFile(currentfilename); if not FileExists(currentfilename) then begin FFailedList.Add(Format(RS_FailedToSave, [FCurrentName])); @@ -331,8 +190,8 @@ procedure TDBUpdaterThread.Execute; end; end else - FFailedList.Add(Format(RS_FailedDownload, [FCurrentName, FHTTP.ResultCode, - FHTTP.ResultString])); + FFailedList.Add(Format(RS_FailedDownload, [FCurrentName, HTTP.ResultCode, + HTTP.ResultString])); except on E: Exception do FFailedList.Add(E.Message); @@ -343,13 +202,9 @@ procedure TDBUpdaterThread.Execute; constructor TDBUpdaterThread.Create; begin - inherited Create(True); - FreeOnTerminate := True; + inherited Create(True, FormMain, FormMain.IconList, 24); FFailedList := TStringList.Create; - FHTTP := THTTPSendThread.Create(Self); - FHTTP.UserAgent := UserAgentCURL; - FHTTP.Sock.OnStatus := @HTTPSockOnStatus; - FHTTP.OnRedirected:=@HTTPRedirected; + HTTP.OnRedirected := @HTTPRedirected; Items := TStringList.Create; Synchronize(@SyncStart); end; @@ -359,7 +214,6 @@ destructor TDBUpdaterThread.Destroy; if (not Terminated) and (FFailedList.Count <> 0) then Synchronize(@SyncShowFailed); Synchronize(@SyncFinal); - FHTTP.Free; FFailedList.Free; FreeAndNil(Items); inherited Destroy; @@ -401,7 +255,6 @@ procedure TDBUpdaterThread.Add(const S: TStrings); procedure TDBUpdaterThread.UpdateStatus; begin - SyncUpdateStatus; SyncUpdateHint; end; diff --git a/baseunits/DownloadsDB.pas b/baseunits/DownloadsDB.pas index ef35063a3..434b6400a 100644 --- a/baseunits/DownloadsDB.pas +++ b/baseunits/DownloadsDB.pas @@ -18,14 +18,29 @@ TDownloadsDB = class(TSQliteData) procedure SetAutoCommitCount(AValue: Integer); public constructor Create(const AFilename: String); - function Open: Boolean; - function Add(var Adlid: Integer; - const Aenabled: Boolean; - const Aorder, Ataskstatus, Achapterptr, Anumberofpages, Acurrentpage: Integer; - const Awebsite, Alink, Atitle, Astatus, Aprogress, Asaveto: String; - const Adatetime: TDateTime; - const Achapterslinks, Achaptersnames, Apagelinks, Apagecontainerlinks, Afilenames, Acustomfilenames, - Achaptersstatus: String): Boolean; + procedure InternalAdd( + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String); inline; + procedure InternalUpdate( + const Adlid:Integer; + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String); inline; + procedure InternalUpdateOrderEnabled(const Adlid,AOrder:Integer;const Aenabled:Boolean); + procedure InternalUpdateOrder(const Adlid,AOrder:Integer); + procedure InternalUpdateEnabled(const Adlid:Integer;const Aenabled:Boolean); + function Add( + var Adlid:Integer; + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String):Boolean; procedure Delete(const ADlId: Integer); procedure Commit; override; procedure Close; override; @@ -99,93 +114,116 @@ constructor TDownloadsDB.Create(const AFilename: String); SelectParams := 'SELECT ' + FieldsParams + ' FROM '+QuotedStrD(TableName)+' ORDER BY "order"'; end; -function TDownloadsDB.Open: Boolean; +procedure TDownloadsDB.InternalAdd( + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String); begin - Result := inherited Open(False, False); - Table.SQL.Text := 'SELECT * FROM ' + QuotedStrD(TableName); - Table.Open; - if Table.Active then - begin - // convert table, replace failedchapterlink, failedchaptername with chaptersstatus - if (Table.Fields.Count = 22) and (Table.Fields[20].FieldName = 'failedchapterlinks') then - begin - Table.Close; - with Connection do - begin - ExecuteDirect('DROP TABLE IF EXISTS ' + QuotedStrD('temp' + TableName)); - ExecuteDirect('CREATE TABLE ' + QuotedStrD('temp' + TableName) + ' (' + CreateParams + ')'); - ExecuteDirect('INSERT INTO ' + QuotedStrD('temp' + TableName) + ' (' + FieldsParams + ') SELECT ' + - '"dlid","enabled","order","taskstatus","chapterptr","numberofpages","currentpage","website","link","title","status","progress","saveto","datetime","chapterslinks"||"failedchapterlinks","chaptersnames"||"failedchapternames","pagelinks","pagecontainerlinks","filenames","customfilenames",""' - + ' FROM "' + TableName + '"'); - ExecuteDirect('DROP TABLE ' + QuotedStrD(TableName)); - ExecuteDirect('ALTER TABLE ' + QuotedStrD('temp' + TableName) + ' RENAME TO ' + QuotedStrD(TableName)); - Transaction.Commit; - end; - end; - end; - CloseTable; + Connection.ExecuteDirect('INSERT INTO "downloads" ("enabled","order","taskstatus","chapterptr","numberofpages","currentpage","website","link","title","status","progress","saveto","datetime","chapterslinks","chaptersnames","pagelinks","pagecontainerlinks","filenames","customfilenames","chaptersstatus")' + + ' VALUES (' + + QuotedStr(Aenabled) + ', ' + + QuotedStr(Aorder) + ', ' + + QuotedStr(Ataskstatus) + ', ' + + QuotedStr(Achapterptr) + ', ' + + QuotedStr(Anumberofpages) + ', ' + + QuotedStr(Acurrentpage) + ', ' + + QuotedStr(Awebsite) + ', ' + + QuotedStr(Alink) + ', ' + + QuotedStr(Atitle) + ', ' + + QuotedStr(Astatus) + ', ' + + QuotedStr(Aprogress) + ', ' + + QuotedStr(Asaveto) + ', ' + + QuotedStr(Adatetime) + ', ' + + QuotedStr(Achapterslinks) + ', ' + + QuotedStr(Achaptersnames) + ', ' + + QuotedStr(Apagelinks) + ', ' + + QuotedStr(Apagecontainerlinks) + ', ' + + QuotedStr(Afilenames) + ', ' + + QuotedStr(Acustomfilenames) + ', ' + + QuotedStr(Achaptersstatus) + + ')'); +end; + +procedure TDownloadsDB.InternalUpdate( + const Adlid:Integer; + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String); +begin + Connection.ExecuteDirect('UPDATE "downloads" SET ' + + '"enabled"=' + QuotedStr(Aenabled) + ', ' + + '"order"=' + QuotedStr(Aorder) + ', ' + + '"taskstatus"=' + QuotedStr(Ataskstatus) + ', ' + + '"chapterptr"=' + QuotedStr(Achapterptr) + ', ' + + '"numberofpages"=' + QuotedStr(Anumberofpages) + ', ' + + '"currentpage"=' + QuotedStr(Acurrentpage) + ', ' + + '"website"=' + QuotedStr(Awebsite) + ', ' + + '"link"=' + QuotedStr(Alink) + ', ' + + '"title"=' + QuotedStr(Atitle) + ', ' + + '"status"=' + QuotedStr(Astatus) + ', ' + + '"progress"=' + QuotedStr(Aprogress) + ', ' + + '"saveto"=' + QuotedStr(Asaveto) + ', ' + + '"datetime"=' + QuotedStr(Adatetime) + ', ' + + '"chapterslinks"=' + QuotedStr(Achapterslinks) + ', ' + + '"chaptersnames"=' + QuotedStr(Achaptersnames) + ', ' + + '"pagelinks"=' + QuotedStr(Apagelinks) + ', ' + + '"pagecontainerlinks"=' + QuotedStr(Apagecontainerlinks) + ', ' + + '"filenames"=' + QuotedStr(Afilenames) + ', ' + + '"customfilenames"=' + QuotedStr(Acustomfilenames) + ', ' + + '"chaptersstatus"=' + QuotedStr(Achaptersstatus) + + ' WHERE "dlid"=' + QuotedStr(Adlid)); +end; + +procedure TDownloadsDB.InternalUpdateOrderEnabled(const Adlid, AOrder: Integer; + const Aenabled: Boolean); +begin + Connection.ExecuteDirect('UPDATE "downloads" SET ' + + '"enabled"=' + QuotedStr(Aenabled) + ', ' + + '"order"=' + QuotedStr(Aorder) + + ' WHERE "dlid"=' + QuotedStr(Adlid)); +end; + +procedure TDownloadsDB.InternalUpdateOrder(const Adlid, AOrder: Integer); +begin + Connection.ExecuteDirect('UPDATE "downloads" SET ' + + '"order"='+QuotedStr(AOrder)+ + ' WHERE "dlid"=' + QuotedStr(Adlid)); +end; + +procedure TDownloadsDB.InternalUpdateEnabled(const Adlid: Integer; + const Aenabled: Boolean); +begin + Connection.ExecuteDirect('UPDATE "downloads" SET ' + + '"enabled"=' +QuotedStr(Aenabled)+ + ' WHERE "dlid"=' + QuotedStr(Adlid)); end; -function TDownloadsDB.Add(var Adlid: Integer; const Aenabled: Boolean; - const Aorder, Ataskstatus, Achapterptr, Anumberofpages, - Acurrentpage: Integer; const Awebsite, Alink, Atitle, Astatus, Aprogress, - Asaveto: String; const Adatetime: TDateTime; const Achapterslinks, - Achaptersnames, Apagelinks, Apagecontainerlinks, Afilenames, - Acustomfilenames, Achaptersstatus: String): Boolean; +function TDownloadsDB.Add( + var Adlid:Integer; + const Aenabled:Boolean; + const Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage:Integer; + const Awebsite,Alink,Atitle,Astatus,Aprogress,Asaveto:String; + const Adatetime:TDateTime; + const Achapterslinks,Achaptersnames,Apagelinks,Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus:String):Boolean; begin Result := False; if not Connection.Connected then Exit; try if Adlid = -1 then begin - Connection.ExecuteDirect('INSERT INTO "downloads" ("enabled","order","taskstatus","chapterptr","numberofpages","currentpage","website","link","title","status","progress","saveto","datetime","chapterslinks","chaptersnames","pagelinks","pagecontainerlinks","filenames","customfilenames","chaptersstatus")' + - ' VALUES (' + - QuotedStr(Aenabled) + ', ' + - QuotedStr(Aorder) + ', ' + - QuotedStr(Ataskstatus) + ',' + - QuotedStr(Achapterptr) + ',' + - QuotedStr(Anumberofpages) + ',' + - QuotedStr(Acurrentpage) + ',' + - QuotedStr(Awebsite) + ', ' + - QuotedStr(Alink) + ', ' + - QuotedStr(Atitle) + ', ' + - QuotedStr(Astatus) + ', ' + - QuotedStr(Aprogress) + ', ' + - QuotedStr(Asaveto) + ', ' + - QuotedStr(Adatetime) + ', ' + - QuotedStr(Achapterslinks) + ', ' + - QuotedStr(Achaptersnames) + ', ' + - QuotedStr(Apagelinks) + ', ' + - QuotedStr(Apagecontainerlinks) + ', ' + - QuotedStr(Afilenames) + ', ' + - QuotedStr(Acustomfilenames) + ', ' + - QuotedStr(Achaptersstatus) + - ')'); + InternalAdd(Aenabled,Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage,Awebsite, + Alink,Atitle,Astatus,Aprogress,Asaveto,Adatetime,Achapterslinks,Achaptersnames,Apagelinks, + Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus); Adlid := Connection.GetInsertID; end else - Connection.ExecuteDirect('UPDATE "downloads" SET ' + - '"enabled"=' + QuotedStr(Aenabled) + ', ' + - '"order"=' + QuotedStr(Aorder) + ', ' + - '"taskstatus"=' + QuotedStr(Ataskstatus) + ',' + - '"chapterptr"=' + QuotedStr(Achapterptr) + ',' + - '"numberofpages"=' + QuotedStr(Anumberofpages) + ',' + - '"currentpage"=' + QuotedStr(Acurrentpage) + ',' + - '"website"=' + QuotedStr(Awebsite) + ', ' + - '"link"=' + QuotedStr(Alink) + ', ' + - '"title"=' + QuotedStr(Atitle) + ', ' + - '"status"=' + QuotedStr(Astatus) + ', ' + - '"progress"=' + QuotedStr(Aprogress) + ', ' + - '"saveto"=' + QuotedStr(Asaveto) + ', ' + - '"datetime"=' + QuotedStr(Adatetime) + ', ' + - '"chapterslinks"=' + QuotedStr(Achapterslinks) + ', ' + - '"chaptersnames"=' + QuotedStr(Achaptersnames) + ', ' + - '"pagelinks"=' + QuotedStr(Apagelinks) + ', ' + - '"pagecontainerlinks"=' + QuotedStr(Apagecontainerlinks) + ', ' + - '"filenames"=' + QuotedStr(Afilenames) + ', ' + - '"customfilenames"=' + QuotedStr(Acustomfilenames) + ', ' + - '"chaptersstatus"=' + QuotedStr(Achaptersstatus) + - ' WHERE "dlid"=' + QuotedStr(Adlid)); + InternalUpdate(Adlid,Aenabled,Aorder,Ataskstatus,Achapterptr,Anumberofpages,Acurrentpage,Awebsite, + Alink,Atitle,Astatus,Aprogress,Asaveto,Adatetime,Achapterslinks,Achaptersnames,Apagelinks, + Apagecontainerlinks,Afilenames,Acustomfilenames,Achaptersstatus); Inc(FCommitCount); if FCommitCount >= FAutoCommitCount then Commit; diff --git a/baseunits/Duktape.Api.pas b/baseunits/Duktape.Api.pas index 67a1cc3a9..2bc33f37b 100644 --- a/baseunits/Duktape.Api.pas +++ b/baseunits/Duktape.Api.pas @@ -1,36 +1,3 @@ -{ - -https://github.com/grijjy/DelphiDuktape - -DelphiDuktape is licensed under the Simplified BSD License. - -------------------------------------------------------------------------------- - -Copyright (c) 2018 by Grijjy, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -} - unit Duktape.Api; interface @@ -48,9 +15,12 @@ interface {$ELSEIF Defined(IOS)} LIB_DUKTAPE = 'libduktape_ios.a'; PREFIX = ''; - {$ELSEIF Defined(MACOS)} + {$ELSEIF Defined(MACOS32)} LIB_DUKTAPE = 'libduktape_osx32.dylib'; PREFIX = '_'; + {$ELSEIF Defined(MACOS64)} + LIB_DUKTAPE = 'libduktape_osx64.a'; + PREFIX = ''; {$ELSEIF Defined(LINUX)} LIB_DUKTAPE = 'libduktape_linux64.so'; PREFIX = ''; @@ -61,7 +31,7 @@ interface (* * duk_config.h configuration header generated by genconfig.py. * - * Git commit a459cf3c9bd1779fc01b435d69302b742675a08f (v2.2.0). + * Git commit d7fdb67f18561a50e06bafd196c6b423af9ad6fe (v2.3.0). * Git branch: master * * Supported platforms: @@ -309,7 +279,7 @@ interface * * (http://opensource.org/licenses/MIT) * - * Copyright (c) 2013-2017 by Duktape authors (see AUTHORS.rst) + * Copyright (c) 2013-2018 by Duktape authors (see AUTHORS.rst) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -379,6 +349,14 @@ interface * * Steven Don (https://github.com/shdon) * * Simon Stone (https://github.com/sstone1) * * \J. McC. (https://github.com/jmhmccr) + * * Jakub Nowakowski (https://github.com/jimvonmoon) + * * Tommy Nguyen (https://github.com/tn0502) + * * Fabrice Fontaine (https://github.com/ffontaine) + * * Christopher Hiller (https://github.com/boneskull) + * * Gonzalo Diethelm (https://github.com/gonzus) + * * Michal Kasperek (https://github.com/michalkas) + * * Andrew Janke (https://github.com/apjanke) + * * Steve Fan (https://github.com/stevefan1999) * * Other contributions * =================== @@ -417,6 +395,8 @@ interface * * https://github.com/chris-y * * Laurent Zubiaur (https://github.com/lzubiaur) * * Neil Kolban (https://github.com/nkolban) + * * Wilhelm Wanecek (https://github.com/wanecek) + * * Andrew Janke (https://github.com/apjanke) * * If you are accidentally missing from this list, send me an e-mail * (``sami.vaarala@iki.fi``) and I'll fix the omission. @@ -432,21 +412,21 @@ interface (* Duktape version, (major * 10000) + (minor * 100) + patch. Allows C code * to #if (DUK_VERSION >= NNN) against Duktape API version. The same value - * is also available to Ecmascript code in Duktape.version. Unofficial + * is also available to ECMAscript code in Duktape.version. Unofficial * development snapshots have 99 for patch level (e.g. 0.10.99 would be a * development version after 0.10.0 but before the next official release). *) const - DUK_VERSION = 20200; + DUK_VERSION = 20300; (* Git commit, describe, and branch for Duktape build. Useful for * non-official snapshot builds so that application code can easily log - * which Duktape snapshot was used. Not available in the Ecmascript + * which Duktape snapshot was used. Not available in the ECMAscript * environment. *) const - DUK_GIT_COMMIT = 'a459cf3c9bd1779fc01b435d69302b742675a08f'; - DUK_GIT_DESCRIBE = 'v2.2.0'; + DUK_GIT_COMMIT = 'd7fdb67f18561a50e06bafd196c6b423af9ad6fe'; + DUK_GIT_DESCRIBE = 'v2.3.0'; DUK_GIT_BRANCH = 'master'; (* @@ -554,12 +534,12 @@ TDukTimeComponents = record const DUK_TYPE_MIN = 0; DUK_TYPE_NONE = 0; (* no value, e.g. invalid index *) - DUK_TYPE_UNDEFINED = 1; (* Ecmascript undefined *) - DUK_TYPE_NULL = 2; (* Ecmascript null *) - DUK_TYPE_BOOLEAN = 3; (* Ecmascript boolean: 0 or 1 *) - DUK_TYPE_NUMBER = 4; (* Ecmascript number: double *) - DUK_TYPE_STRING = 5; (* Ecmascript string: CESU-8 / extended UTF-8 encoded *) - DUK_TYPE_OBJECT = 6; (* Ecmascript object: includes objects, arrays, functions, threads *) + DUK_TYPE_UNDEFINED = 1; (* ECMAScript undefined *) + DUK_TYPE_NULL = 2; (* ECMAScript null *) + DUK_TYPE_BOOLEAN = 3; (* ECMAScript boolean: 0 or 1 *) + DUK_TYPE_NUMBER = 4; (* ECMAScript number: double *) + DUK_TYPE_STRING = 5; (* ECMAScript string: CESU-8 / extended UTF-8 encoded *) + DUK_TYPE_OBJECT = 6; (* ECMAScript object: includes objects, arrays, functions, threads *) DUK_TYPE_BUFFER = 7; (* fixed or dynamic, garbage collected byte buffer *) DUK_TYPE_POINTER = 8; (* raw void pointer *) DUK_TYPE_LIGHTFUNC = 9; (* lightweight function pointer *) @@ -839,7 +819,10 @@ function duk_push_lstring(ctx: PDukContext; const str: MarshaledAString; len: TD procedure duk_push_pointer(ctx: PDukContext; p: Pointer); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_pointer'; function duk_push_sprintf(ctx: PDukContext; const fmt: MarshaledAString): MarshaledAString; cdecl; varargs external LIB_DUKTAPE name PREFIX + 'duk_push_sprintf'; +function duk_push_literal(ctx: PDukContext; const str: MarshaledAString): MarshaledAString; cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_string'; + procedure duk_push_this(ctx: PDukContext); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_this'; +procedure duk_push_new_target(ctx: PDukContext); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_new_target'; procedure duk_push_current_function(ctx: PDukContext); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_current_function'; procedure duk_push_current_thread(ctx: PDukContext); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_current_thread'; procedure duk_push_global_object(ctx: PDukContext); cdecl external LIB_DUKTAPE name PREFIX + 'duk_push_global_object'; @@ -1110,28 +1093,34 @@ procedure duk_config_buffer(ctx: PDukContext; idx: TDukIdx; ptr: Pointer; len: T (* * Property access * - * The basic function assumes key is on stack. The _string variant takes - * a C string as a property name, while the _index variant takes an array - * index as a property name (e.g. 123 is equivalent to the key "123"). + * The basic function assumes key is on stack. The _(l)string variant takes + * a C string as a property name; the _literal variant takes a C literal. + * The _index variant takes an array index as a property name (e.g. 123 is + * equivalent to the key "123"). The _heapptr variant takes a raw, borrowed + * heap pointer. *) function duk_get_prop(ctx: PDukContext; obj_idx: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop'; function duk_get_prop_string(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop_string'; +function duk_get_prop_literal(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop_string'; function duk_get_prop_lstring(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop_lstring'; function duk_get_prop_index(ctx: PDukContext; obj_idx: TDukIdx; arr_idx: TDukUArrIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop_index'; function duk_get_prop_heapptr(ctx: PDukContext; obj_idx: TDukIdx; ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_prop_heapptr'; function duk_put_prop(ctx: PDukContext; obj_idx: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop'; function duk_put_prop_string(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop_string'; +function duk_put_prop_literal(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop_string'; function duk_put_prop_lstring(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop_lstring'; function duk_put_prop_index(ctx: PDukContext; obj_idx: TDukIdx; arr_idx: TDukUArrIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop_index'; function duk_put_prop_heapptr(ctx: PDukContext; obj_idx: TDukIdx; ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_prop_heapptr'; function duk_del_prop(ctx: PDukContext; obj_idx: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop'; function duk_del_prop_string(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop_string'; +function duk_del_prop_literal(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop_string'; function duk_del_prop_lstring(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop_lstring'; function duk_del_prop_index(ctx: PDukContext; obj_idx: TDukIdx; arr_idx: TDukUArrIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop_index'; function duk_del_prop_heapptr(ctx: PDukContext; obj_idx: TDukIdx; ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_del_prop_heapptr'; function duk_has_prop(ctx: PDukContext; obj_idx: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop'; function duk_has_prop_string(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop_string'; +function duk_has_prop_literal(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop_string'; function duk_has_prop_lstring(ctx: PDukContext; obj_idx: TDukIdx; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop_lstring'; function duk_has_prop_index(ctx: PDukContext; obj_idx: TDukIdx; arr_idx: TDukUArrIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop_index'; function duk_has_prop_heapptr(ctx: PDukContext; obj_idx: TDukIdx; ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_has_prop_heapptr'; @@ -1140,9 +1129,13 @@ procedure duk_get_prop_desc(ctx: PDukContext; obj_idx: TDukIdx; flags: TDukUInt) procedure duk_def_prop(ctx: PDukContext; obj_idx: TDukIdx; flags: TDukUInt); cdecl external LIB_DUKTAPE name PREFIX + 'duk_def_prop'; function duk_get_global_string(ctx: PDukContext; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_global_string'; +function duk_get_global_literal(ctx: PDukContext; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_global_string'; +function duk_get_global_heapptr(ctx: PDukContext; const ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_global_heapptr'; function duk_get_global_lstring(ctx: PDukContext; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_get_global_lstring'; function duk_put_global_string(ctx: PDukContext; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_global_string'; +function duk_put_global_literal(ctx: PDukContext; const key: MarshaledAString): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_global_string'; function duk_put_global_lstring(ctx: PDukContext; const key: MarshaledAString; key_len: TDukSize): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_global_lstring'; +function duk_put_global_heapptr(ctx: PDukContext; const ptr: Pointer): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_put_global_heapptr'; (* * Inspection @@ -1209,7 +1202,7 @@ procedure duk_trim(ctx: PDukContext; idx: TDukIdx); cdecl external LIB_DUKTAPE n function duk_char_code_at(ctx: PDukContext; idx: TDukIdx; char_offset: TDukSize): TDukCodepoint; cdecl external LIB_DUKTAPE name PREFIX + 'duk_char_code_at'; (* - * Ecmascript operators + * ECMAScript operators *) function duk_equals(ctx: PDukContext; idx1: TDukIdx; idx2: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_equals'; @@ -1217,6 +1210,11 @@ function duk_strict_equals(ctx: PDukContext; idx1: TDukIdx; idx2: TDukIdx): TDuk function duk_samevalue(ctx: PDukContext; idx1: TDukIdx; idx2: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_samevalue'; function duk_instanceof(ctx: PDukContext; idx1: TDukIdx; idx2: TDukIdx): TDukBool; cdecl external LIB_DUKTAPE name PREFIX + 'duk_instanceof'; +(* + * Random + *) +function duk_random(ctx: PDukContext): TDukDouble; cdecl external LIB_DUKTAPE name PREFIX + 'duk_random'; + (* * Function (method) calls *) @@ -1319,7 +1317,7 @@ function duk_components_to_time(ctx: PDukContext; comp: PDukTimeComponents): TDu DUK_DATE_MSEC_HOUR = 60 * 60 * 1000; DUK_DATE_MSEC_DAY = 24 * 60 * 60 * 1000; -(* Ecmascript date range is 100 million days from Epoch: +(* ECMAScript date range is 100 million days from Epoch: * > 100e6 * 24 * 60 * 60 * 1000 // 100M days in millisecs * 8640000000000000 * (= 8.64e15) @@ -1328,7 +1326,7 @@ function duk_components_to_time(ctx: PDukContext; comp: PDukTimeComponents): TDu DUK_DATE_MSEC_100M_DAYS = 8.64e15; DUK_DATE_MSEC_100M_DAYS_LEEWAY = 8.64e15 + 24 * 3600e3; -(* Ecmascript year range: +(* ECMAScript year range: * > new Date(100e6 * 24 * 3600e3).toISOString() * '+275760-09-13T00:00:00.000Z' * > new Date(-100e6 * 24 * 3600e3).toISOString() @@ -1339,7 +1337,7 @@ function duk_components_to_time(ctx: PDukContext; comp: PDukTimeComponents): TDu DUK_DATE_MAX_ECMA_YEAR = 275760; (* Part indices for internal breakdowns. Part order from DUK_DATE_IDX_YEAR - * to DUK_DATE_IDX_MILLISECOND matches argument ordering of Ecmascript API + * to DUK_DATE_IDX_MILLISECOND matches argument ordering of ECMAScript API * calls (like Date constructor call). Some functions in duk_bi_date.c * depend on the specific ordering, so change with care. 16 bits are not * enough for all parts (year, specifically). diff --git a/baseunits/FMDOptions.pas b/baseunits/FMDOptions.pas index 17df664b4..11413e13a 100644 --- a/baseunits/FMDOptions.pas +++ b/baseunits/FMDOptions.pas @@ -25,7 +25,6 @@ TIniFileRun = class(IniFiles.TMemIniFile) TFMDDo = (DO_NOTHING, DO_EXIT, DO_POWEROFF, DO_HIBERNATE, DO_UPDATE); const - FMD_REVISION = '$WCREV$'; FMD_INSTANCE = '_FreeMangaDownloaderInstance_'; FMD_TARGETOS = {$i %FPCTARGETOS%}; FMD_TARGETCPU = {$i %FPCTARGETCPU%}; @@ -46,7 +45,6 @@ TIniFileRun = class(IniFiles.TMemIniFile) ZIP_EXE = '7za.exe'; RUN_EXE = '.run'; - SOCKHEARTBEATRATE = 500; {$IFDEF WINDOWS} {$IFDEF WIN32} @@ -62,6 +60,11 @@ TIniFileRun = class(IniFiles.TMemIniFile) MAX_CONNECTIONPERHOSTLIMIT = 32; {$ENDIF} + BACKUP_FILE_PREFIX = 'fmdbackup_'; + BACKUP_FILE_EXT = '7z'; + +{$i revision.inc} + var FMD_VERSION_NUMBER: TProgramVersion; FMD_VERSION_STRING, @@ -95,7 +98,10 @@ TIniFileRun = class(IniFiles.TMemIniFile) EXTRAS_FOLDER, MANGAFOXTEMPLATE_FOLDER, LUA_WEBSITEMODULE_FOLDER, - LUA_WEBSITEMODULE_FILE: String; + LUA_REPO_FOLDER, + LUA_REPO_FILE, + LUA_REPO_WORK_FILE, + BACKUP_FOLDER: String; // ini files revisionfile, @@ -103,13 +109,13 @@ TIniFileRun = class(IniFiles.TMemIniFile) configfile: TIniFileRun; // base url, should be in base.ini - DEFAULT_SELECTED_WEBSITES: String = 'MangaFox,MangaHere,MangaInn,MangaReader'; + DEFAULT_SELECTED_WEBSITES: String = 'MangaDex,MangaHere,MangaInn,MangaReader'; DB_URL: String = 'https://sourceforge.net/projects/newfmd/files/data/.7z/download'; - UPDATE_URL: String = 'https://raw.githubusercontent.com/riderkick/FMD/master/update'; - CHANGELOG_URL: String = 'https://raw.githubusercontent.com/riderkick/FMD/master/changelog.txt'; + UPDATE_URL: String = 'https://raw.githubusercontent.com/fmd-project-team/FMD/master/update'; + CHANGELOG_URL: String = 'https://raw.githubusercontent.com/fmd-project-team/FMD/master/changelog.txt'; UPDATE_PACKAGE_NAME: String = 'updatepackage.7z'; - MODULES_URL: String = 'https://api.github.com/repos/riderkick/FMD/contents/lua/modules'; - MODULES_URL2: String = 'https://github.com/riderkick/FMD/file-list/master/lua/modules'; + MODULES_URL: String = 'https://api.github.com/repos/fmd-project-team/FMD/contents/lua/modules'; + MODULES_URL2: String = 'https://github.com/fmd-project-team/FMD/file-list/master/lua/modules'; currentWebsite: String; @@ -119,6 +125,7 @@ TIniFileRun = class(IniFiles.TMemIniFile) // general OptionLetFMDDo: TFMDDo = DO_NOTHING; OptionDeleteCompletedTasksOnClose: Boolean = False; + OptionSortDownloadsWhenAddingNewDownloadTasks: Boolean = False; // saveto OptionChangeUnicodeCharacter: Boolean = False; @@ -148,10 +155,17 @@ TIniFileRun = class(IniFiles.TMemIniFile) OptionConnectionTimeout: Integer = 30; OptionRetryFailedTask: Integer = 1; OptionAlwaysStartTaskFromFailedChapters: Boolean = True; + OptionEnableCloudflareBypass: Boolean = True; + OptionAutomaticallyDisableCloudflareBypass: Boolean = False; // view OptionEnableLoadCover: Boolean = False; OptionShowBalloonHint: Boolean = True; + OptionShowFavoritesTabOnNewManga: Boolean = False; + OptionShowDownloadsTabOnNewTasks: Boolean = True; + + // favorites (context menu settings) + OptionDefaultAction: Integer = 0; // updates OptionAutoCheckLatestVersion: Boolean = True; @@ -183,6 +197,7 @@ TIniFileRun = class(IniFiles.TMemIniFile) CL_BSOdd: TColor = clBtnFace; CL_BSEven: TColor = clWindow; CL_BSSortedColumn: TColor = $F8E6D6; + CL_BSEnabledWebsiteSettings: TColor = clYellow; //mangalist color CL_MNNewManga: TColor = $FDC594; @@ -207,7 +222,7 @@ procedure DoRestartFMD; implementation -uses FMDVars, UTF8Process; +uses FMDVars, process, UTF8Process; { TIniFileRun } @@ -237,10 +252,12 @@ destructor TIniFileRun.Destroy; procedure TIniFileRun.UpdateFile; begin if CacheUpdates and (Dirty = False) then Exit; - inherited UpdateFile; + EnterCriticalSection(FCSLock); try + inherited UpdateFile; CopyFile(FileName, FRealFileName, [cffOverwriteFile, cffPreserveTime, cffCreateDestDirectory]); - except + finally + LeaveCriticalSection(FCSLock); end; end; @@ -295,11 +312,12 @@ procedure SetFMDdirectory(const ADir: String); README_FILE := FMD_DIRECTORY + 'readme.rtf'; EXTRAS_FOLDER := FMD_DIRECTORY + 'extras' + PathDelim; MANGAFOXTEMPLATE_FOLDER := EXTRAS_FOLDER + 'mangafoxtemplate' + PathDelim; - DEFAULT_LOG_FILE := FMD_DIRECTORY + FMD_EXENAME + '.log'; + DEFAULT_LOG_FILE := FMD_EXENAME + '.log'; CURRENT_UPDATER_EXE := FMD_DIRECTORY + UPDATER_EXE; OLD_CURRENT_UPDATER_EXE := FMD_DIRECTORY + OLD_UPDATER_EXE; CURRENT_ZIP_EXE := FMD_DIRECTORY + ZIP_EXE; + BACKUP_FOLDER := FMD_DIRECTORY + 'backup' + PathDelim; ReadBaseFile; end; @@ -314,7 +332,8 @@ procedure SetAppDataDirectory(const ADir: String); CONFIG_FILE := CONFIG_FOLDER + 'config.ini'; ACCOUNTS_FILE := CONFIG_FOLDER + 'accounts.db'; MODULES_FILE := CONFIG_FOLDER + 'modules.json'; - LUA_WEBSITEMODULE_FILE := CONFIG_FOLDER + 'luamodules.json'; + LUA_REPO_FILE := CONFIG_FOLDER + 'lua.json'; + LUA_REPO_WORK_FILE := CONFIG_FOLDER + 'lua_repo.json'; DATA_FOLDER := APPDATA_DIRECTORY + 'data' + PathDelim; @@ -327,6 +346,7 @@ procedure SetAppDataDirectory(const ADir: String); FAVORITESDB_FILE := WORK_FOLDER + 'favorites.db'; LUA_WEBSITEMODULE_FOLDER := FMD_DIRECTORY + 'lua' + PathDelim + 'modules' + PathDelim; + LUA_REPO_FOLDER := FMD_DIRECTORY + 'lua' + PathDelim; SetIniFiles; end; @@ -340,15 +360,18 @@ procedure RestartFMD; procedure DoRestartFMD; var p: TProcessUTF8; - i: Integer; begin p := TProcessUTF8.Create(nil); try p.InheritHandles := False; - p.CurrentDirectory := ExtractFilePath(Application.ExeName); + p.CurrentDirectory := FMD_DIRECTORY; p.Executable := Application.ExeName; - for i := 1 to ParamCount do - p.Parameters.Add(ParamStrUTF8(i)); + p.Options := []; + p.InheritHandles := False; + p.Parameters.AddStrings(AppParams); + {$ifdef windows} + p.Parameters.Add('--dorestart-handle=' + IntToStr(Integer(Application.Handle))); + {$ifend} p.Execute; finally p.Free; diff --git a/baseunits/FMDVars.pas b/baseunits/FMDVars.pas index adc93738b..c5c089de1 100644 --- a/baseunits/FMDVars.pas +++ b/baseunits/FMDVars.pas @@ -7,7 +7,7 @@ interface uses frmMain, uDownloadsManager, uFavoritesManager, uUpdateThread, DBDataProcess, uSilentThread, uBaseUnit, uGetMangaInfosThread, CheckUpdate, - FMDOptions, DBUpdater, SelfUpdater, FileChannel, simpleipc; + FMDOptions, DBUpdater, SelfUpdater, Classes, FileChannel, simpleipc; var FormMain: TMainForm; @@ -20,6 +20,7 @@ interface //Instance FMDInstance: TSimpleIPCServer; + AppParams:TStringList; // update fmd through main thread DoAfterFMD: TFMDDo; @@ -28,10 +29,6 @@ interface // file logger FileLogger: TFileChannel; - // status in status bar update - ulTotalPtr, - ulWorkPtr: Integer; - // download manager DLManager: TDownloadManager; @@ -62,5 +59,12 @@ interface implementation +initialization + AppParams:=TStringList.Create; + AppParams.NameValueSeparator:='='; + +finalization + AppParams.Free; + end. diff --git a/baseunits/FavoritesDB.pas b/baseunits/FavoritesDB.pas index c46555ea5..163b57d01 100644 --- a/baseunits/FavoritesDB.pas +++ b/baseunits/FavoritesDB.pas @@ -13,20 +13,19 @@ interface TFavoritesDB = class(TSQliteData) private - FNFEnabled: Boolean; FCommitCount: Integer; FAutoCommitCount: Integer; procedure SetAutoCommitCount(AValue: Integer); - protected - function ConvertNewTableIF: Boolean; override; public constructor Create(const AFilename: String); - function Add(const AOrder: Integer; - const AEnabled: Boolean; - const AWebsite, ALink, ATitle, ACurrentChapter, ADownloadedChapterList, ASaveTo: String): Boolean; + procedure InternalUpdate(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo: String); inline; + procedure InternalAdd(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo: String); inline; + function Add(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo:String):Boolean; procedure Delete(const AWebsite, ALink: String); procedure Commit; override; - function Open: Boolean; procedure Close; override; property AutoCommitCount: Integer read FAutoCommitCount write SetAutoCommitCount; end; @@ -52,16 +51,9 @@ procedure TFavoritesDB.SetAutoCommitCount(AValue: Integer); FAutoCommitCount := AValue; end; -function TFavoritesDB.ConvertNewTableIF: Boolean; -begin - Result := Table.Fields.Count < 9; - FNFEnabled := Table.Fields.Count = 8; -end; - constructor TFavoritesDB.Create(const AFilename: String); begin inherited Create; - FNFEnabled := False; FCommitCount := 0; FAutoCommitCount := 500; Filename := AFilename; @@ -81,26 +73,44 @@ constructor TFavoritesDB.Create(const AFilename: String); SelectParams := 'SELECT * FROM ' + QuotedStrD(TableName) + ' ORDER BY "order"'; end; -function TFavoritesDB.Add(const AOrder: Integer; const AEnabled: Boolean; - const AWebsite, ALink, ATitle, ACurrentChapter, ADownloadedChapterList, - ASaveTo: String): Boolean; +procedure TFavoritesDB.InternalUpdate(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo:String); +begin + Connection.ExecuteDirect('UPDATE "favorites" SET ' + + '"order"='+QuotedStr(AOrder)+', '+ + '"enabled"='+QuotedStr(AEnabled)+', '+ + '"title"='+QuotedStr(ATitle)+', '+ + '"currentchapter"='+QuotedStr(ACurrentChapter)+', '+ + '"downloadedchapterlist"='+QuotedStr(ADownloadedChapterList)+', '+ + '"saveto"='+QuotedStr(ASaveTo)+ + ' WHERE "websitelink"='+QuotedStr(LowerCase(AWebsite+ALink))); +end; + +procedure TFavoritesDB.InternalAdd(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo:String); +begin + Connection.ExecuteDirect('INSERT OR REPLACE INTO "favorites" (' + + FieldsParams + + ') VALUES (' + + QuotedStr(LowerCase(AWebsite + ALink)) + ', ' + + QuotedStr(AOrder) + ', ' + + QuotedStr(AEnabled) + ', ' + + QuotedStr(AWebsite) + ', ' + + QuotedStr(ALink) + ', ' + + QuotedStr(ATitle) + ', ' + + QuotedStr(ACurrentChapter) + ', ' + + QuotedStr(ADownloadedChapterList) + ', ' + + QuotedStr(ASaveTo) + ')'); +end; + +function TFavoritesDB.Add(const AOrder:Integer;const AEnabled:Boolean; + const AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo:String):Boolean; begin Result := False; if (AWebsite = '') or (ALink = '') then Exit; if not Connection.Connected then Exit; try - Connection.ExecuteDirect('INSERT OR REPLACE INTO "favorites" (' + - FieldsParams + - ') VALUES (' + - QuotedStr(LowerCase(AWebsite + ALink)) + ', ' + - QuotedStr(AOrder) + ', ' + - QuotedStr(AEnabled) + ', ' + - QuotedStr(AWebsite) + ', ' + - QuotedStr(ALink) + ', ' + - QuotedStr(ATitle) + ', ' + - QuotedStr(ACurrentChapter) + ', ' + - QuotedStr(ADownloadedChapterList) + ', ' + - QuotedStr(ASaveTo) + ')'); + InternalAdd(AOrder,AEnabled,AWebsite,ALink,ATitle,ACurrentChapter,ADownloadedChapterList,ASaveTo); Result := True; Inc(FCommitCount); if FCommitCount >= FAutoCommitCount then @@ -142,19 +152,6 @@ procedure TFavoritesDB.Commit; end; end; -function TFavoritesDB.Open: Boolean; -begin - Result := inherited Open(True, False); - if FNFEnabled then - try - Connection.ExecuteDirect('UPDATE "favorites" SET "enabled"=''1'''); - Transaction.Commit; - finally - FNFEnabled := False; - end; - CloseTable; -end; - procedure TFavoritesDB.Close; begin if FCommitCount <> 0 then diff --git a/baseunits/GitHubRepoV3.pas b/baseunits/GitHubRepoV3.pas new file mode 100644 index 000000000..c842f613f --- /dev/null +++ b/baseunits/GitHubRepoV3.pas @@ -0,0 +1,229 @@ +unit GitHubRepoV3; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fgl, httpsendthread, BaseThread, fpjson; + +type +{ TGitHubRepo } + + TTreeItem = class + path, + sha: String; + end; + + TTreeItems = specialize TFPGObjectList; + + TGitHubRepo = class + private + fdirty:boolean; + ConfigFile: String; + WorkFile: String; + HTTP: THTTPSendThread; + protected + procedure ReadWorkFile; + procedure WriteWorkFile; + public + api_url, + download_url, + owner, + name, + ref, + path: String; + + last_commit_sha, + last_commit_etag, + tree_sha, + tree_etag: String; + + Tree: TTreeItems; + public + constructor Create(const AConfigFile, AWorkFile: String; const AThread: TBaseThread = nil); + destructor Destroy; override; + function GetLastCommit: String; + function GetTree: Boolean; + function GetUpdate: Boolean; + function GetDownloadURL(const AName: String): String; + end; + +implementation + +uses jsonparser, jsonscanner, jsonConf, IniFiles, uBaseUnit; + +function AppendURLDelim(const URL: String): String; +begin + Result := URL; + if (URL <> '') and (URL[Length(URL)] <> '/') then + Result := URL + '/'; +end; + +{ TGitHubRepo } + +procedure TGitHubRepo.ReadWorkFile; +begin + if FileExists(WorkFile) then + with TJSONConfig.Create(nil) do + begin + try + Filename := WorkFile; + last_commit_sha := GetValue('last_commit_sha', ''); + last_commit_etag := GetValue('last_commit_etag', ''); + fdirty := false; + except + end; + Free; + end; +end; + +procedure TGitHubRepo.WriteWorkFile; +begin + if not fdirty then Exit; + if FileExists(WorkFile) then DeleteFile(WorkFile); + with TJSONConfig.Create(nil) do + begin + try + Filename := WorkFile; + FormatOptions := AsCompressedJSON; + Formatted := True; + SetValue('last_commit_sha' , last_commit_sha); + SetValue('last_commit_etag', last_commit_etag); + fdirty:=false; + except + end; + Free; + end; +end; + +constructor TGitHubRepo.Create(const AConfigFile, AWorkFile: String; + const AThread: TBaseThread); +begin + ConfigFile := AConfigFile; + WorkFile := AWorkFile; + + HTTP := THTTPSendThread.Create(AThread); + HTTP.FollowRedirection := False; + HTTP.UserAgent := UserAgentCURL; + HTTP.ResetBasic; + + if FileExists(ConfigFile) then + with TIniFile.Create(ConfigFile) do + try + api_url := ReadString ('GitHub', 'api_url' , ''); + download_url := ReadString ('GitHub', 'download_url', ''); + owner := ReadString ('GitHub', 'owner' , ''); + name := ReadString ('GitHub', 'name' , ''); + ref := ReadString ('GitHub', 'ref' , ''); + path := ReadString ('GitHub', 'path' , ''); + finally + Free; + end; + if api_url = '' then api_url := 'https://api.github.com/'; + if ref = '' then ref := 'master'; + + ReadWorkFile; + Tree:=TTreeItems.Create; +end; + +destructor TGitHubRepo.Destroy; +begin + Tree.Free; + HTTP.Free; + WriteWorkFile; + inherited Destroy; +end; + +function TGitHubRepo.GetLastCommit: String; +var + s: String; + d: TJSONData; +begin + Result:=''; + HTTP.ResetBasic; + // use conditional etag, ignore if return 304 not modified + // https://developer.github.com/v3/#conditional-requests + if last_commit_etag<>'' then HTTP.Headers.Values['If-None-Match']:=' '+last_commit_etag; + s:=AppendURLDelim(api_url)+'repos/'+owner+'/'+name+'/commits?sha='+ref+'&per_page=1'; + if path<>'' then s+='&path='+path; + if HTTP.GET(s) then + begin + s:=Trim(HTTP.Headers.Values['ETag']); + if s<>'' then last_commit_etag := s; + d:=GetJSON(HTTP.Document); + if Assigned(d) then + begin + try + if d.JSONType=jtArray then + last_commit_sha:=TJSONObject(TJSONArray(d).Items[0]).Get('sha'); + except + end; + d.Free; + end; + end; + Result:=last_commit_sha; +end; + +function TGitHubRepo.GetTree: Boolean; +var + d: TJSONData; + a: TJSONArray; + s: String; + i: Integer; + item: TTreeItem; +begin + Result:=false; + HTTP.ResetBasic; + s:=last_commit_sha; if s='' then s:=ref; + s:=AppendURLDelim(api_url)+'repos/'+owner+'/'+name+'/git/trees/'+s+':'+path+'?recursive=1'; + if HTTP.GET(s) then + begin + d:=GetJSON(HTTP.Document); + if Assigned(d) then + begin + try + a:=TJSONArray(d.GetPath('tree')); + Tree.Clear; + for i:=0 to a.Count-1 do + with TJSONObject(a.Items[i]) do + begin + s:=Get('type'); + if s<>'tree' then + begin + item:=TTreeItem.Create; + item.path := Get('path'); + item.sha := Get('sha'); + Tree.Add(item); + end; + end; + except + end; + d.Free; + end; + Result:=Tree.Count<>0; + end +end; + +function TGitHubRepo.GetUpdate: Boolean; +var + old_commit_sha, new_commit_sha: String; +begin + result:=false; + old_commit_sha:=last_commit_sha; + new_commit_sha:=GetLastCommit; + if (new_commit_sha<>'') and (new_commit_sha<>old_commit_sha) then + result:=GetTree; + fdirty:=result; +end; + +function TGitHubRepo.GetDownloadURL(const AName: String): String; +var + lpath: String; +begin + lpath:=path; if lpath<>'' then lpath:=lpath+'/'; + Result:=AppendURLDelim(download_url)+owner+'/'+name+'/'+ref+'/'+lpath+AName; +end; + +end. + diff --git a/baseunits/GithubRepo.pas b/baseunits/GithubRepo.pas new file mode 100644 index 000000000..2c6db38a1 --- /dev/null +++ b/baseunits/GithubRepo.pas @@ -0,0 +1,271 @@ +unit GitHubRepo; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, httpsendthread, BaseThread, fpjson; + +type +{ TGitHubRepo } + + TGitHubRepo = class + private + ConfigFile: String; + HTTP: THTTPSendThread; + Tree: TJSONArray; + Props: TJSONObject; + protected + procedure SetAuth; + function QueryLastCommit: String; + function QueryTree: String; + function QueryProps(const ANames: TStrings): String; + public + api_url, + download_url, + owner, + name, + token, + ref, + path, + last_commit: String; + max_deep: Integer; + constructor Create(const AConfigFile: String; const AThread: TBaseThread = nil); + destructor Destroy; override; + function GetLastCommit: String; + function GetTree: TJSONArray; + function GetDownloadURL(const AName: String): String; + function GetProps(const ANames: TStrings): TJSONObject; + end; + +implementation + +uses jsonparser, jsonscanner, IniFiles, uBaseUnit; + +{ TGitHubRepo } + +procedure TGitHubRepo.SetAuth; +begin + HTTP.Headers.Values['Authorization']:=' bearer '+token; +end; + +function TGitHubRepo.QueryLastCommit: String; +var + lpath: String; +begin + lpath:=path; if lpath<>'' then lpath := ', path: "'+lpath+'"'; + Result := '{"query": "'+StringToJSONString( + '{'+ + 'repository(owner: "'+owner+'", name: "'+name+'") {'+ + 'ref(qualifiedName: "'+ref+'") {'+ + 'target {'+ + '... on Commit {'+ + 'history(first: 1'+lpath+'){ '+ + 'nodes {'+ + 'oid'+ + '}'+ + '}'+ + '}'+ + '}'+ + '}'+ + '}'+ + '}' + )+'"}'; +end; + +function TGitHubRepo.QueryTree: String; + + function onTree(const x: Integer):string; + begin + Result:= '... on Tree {'+ + 'entries {'+ + 'oid '+ + 'name '; + if x>1 then + Result+= 'object {'+ + onTree(x-1)+ + '}'; + Result+= '}'+ + '}'; + end; +begin + Result := '{"query": "'+StringToJSONString( + '{'+ + 'repository(owner: "'+owner+'", name: "'+name+'") {'+ + 'object(expression: "'+last_commit+':'+path+'") {'+ + onTree(max_deep)+ + '}'+ + '}'+ + '}' + )+'"}'; +end; + +function TGitHubRepo.QueryProps(const ANames: TStrings): String; + function onProps:String; + var + i: Integer; + lpath: String; + begin + Result:=''; + lpath :=path; if lpath<>'' then lpath+='/'; + for i:=0 to ANames.Count-1 do + begin + Result += 'p'+IntToStr(i)+': history(path: "'+lpath+ANames[i]+'", first: 1) {'+ + 'nodes {'+ + 'message '+ + 'committedDate'+ + '}'+ + '} '; + end; + end; +begin + Result := '{"query": "'+StringToJSONString( + '{'+ + 'repository(owner: "'+owner+'", name: "'+name+'") {'+ + 'object(oid: "'+last_commit+'") {'+ + '... on Commit {'+ + onProps+ + '}'+ + '}'+ + '}'+ + '}' + )+'"}'; +end; + +constructor TGitHubRepo.Create(const AConfigFile: String; + const AThread: TBaseThread); +begin + ConfigFile := AConfigFile; + HTTP := THTTPSendThread.Create(AThread); + if FileExists(ConfigFile) then + with TIniFile.Create(ConfigFile) do + try + api_url := ReadString ('GitHub', 'api_url' , ''); + download_url := ReadString ('GitHub', 'download_url', ''); + owner := ReadString ('GitHub', 'owner' , ''); + name := ReadString ('GitHub', 'name' , ''); + token := DecryptString(ReadString ('GitHub', 'token' , '')); + ref := ReadString ('GitHub', 'ref' , ''); + path := ReadString ('GitHub', 'path' , ''); + max_deep := ReadInteger ('GitHub', 'max_deep' , 2); + finally + Free; + end; + if api_url = '' then api_url := 'https://api.github.com/graphql'; + if ref = '' then ref := 'master'; + if max_deep < 1 then max_deep := 1; + last_commit:= ref; +end; + +destructor TGitHubRepo.Destroy; +begin + if Assigned(Tree) then Tree.Free; + if Assigned(Props) then Props.Free; + HTTP.Free; + inherited Destroy; +end; + +function TGitHubRepo.GetLastCommit: String; +var + d: TJSONData; + a: TJSONArray; +begin + last_commit:=''; + HTTP.Reset; + SetAuth; + d:=nil; + if HTTP.POST(api_url, QueryLastCommit) then + with TJSONParser.Create(HTTP.Document, [joUTF8]) do + try + d:=Parse; + finally + free; + end; + if Assigned(d) then + try + a:=TJSONArray(d.GetPath('data.repository.ref.target.history.nodes')); + if Assigned(a) then + last_commit:=TJSONObject(a.Items[0]).Get('oid',''); + except + end; + d.free; + Result := last_commit; +end; + +function TGitHubRepo.GetTree: TJSONArray; +var + d: TJSONData; + a: TJSONArray; +begin + result:=nil; + HTTP.Reset; + SetAuth; + if last_commit='' then + last_commit := 'master'; + d:=nil; + if HTTP.POST(api_url, QueryTree) then + with TJSONParser.Create(HTTP.Document, [joUTF8]) do + try + d:=Parse; + finally + free; + end; + if Assigned(Tree) then + FreeAndNil(Tree); + if Assigned(d) then + begin + try + a:=TJSONArray(d.GetPath('data.repository.object.entries')); + if Assigned(a) then + Tree:=TJSONArray(a.Clone); + except + end; + d.free; + end; + Result := Tree; +end; + +function TGitHubRepo.GetDownloadURL(const AName: String): String; +var + lpath: String; +begin + lpath:=path; if lpath<>'' then lpath:=lpath+'/'; + Result:=AppendURLDelim(download_url)+owner+'/'+name+'/'+ref+'/'+lpath+AName; +end; + +function TGitHubRepo.GetProps(const ANames: TStrings): TJSONObject; +var + d: TJSONData; + o: TJSONObject; +begin + result:=nil; + HTTP.Reset; + SetAuth; + if last_commit='' then + last_commit := 'master'; + d:=nil; + if HTTP.POST(api_url, QueryProps(ANames)) then + with TJSONParser.Create(HTTP.Document, [joUTF8]) do + try + d:=Parse; + finally + free; + end; + if Assigned(Props) then + FreeAndNil(Props); + if Assigned(d) then + begin + try + o:=TJSONObject(d.GetPath('data.repository.object')); + if Assigned(o) then + Props:=TJSONObject(o.Clone); + except + end; + d.free; + end; + Result := Props; +end; + +end. + diff --git a/baseunits/Img2Pdf.pas b/baseunits/Img2Pdf.pas index 63d8d1245..39618fc08 100644 --- a/baseunits/Img2Pdf.pas +++ b/baseunits/Img2Pdf.pas @@ -36,7 +36,7 @@ interface uses Classes, SysUtils, LazFileUtils, LazUTF8Classes, FPimage, ImgInfos, MemBitmap, FPReadJPEG, FPWriteJPEG, FPReadPNG, JPEGLib, JdAPImin, JDataSrc, Jerror, - zstream; + zstream, AnimatedGif, MultiLog; type TCompressionQuality = 0..100; @@ -377,6 +377,38 @@ procedure WEBPToPageInfo(const PageInfo: TPageInfo); end; end; +{ --- Does not work yet. --- } + +{ procedure GIFToPageInfo(const PageInfo: TPageInfo); } +{ var } + { AMS: TMemoryStreamUTF8; } + { MBM: TAnimatedGif; } + { WRT: TFPWriterPNG; } +{ begin } + { AMS := TMemoryStreamUTF8.Create; } + { try } + { MBM := TAnimatedGif.Create(PageInfo.FileName); } + { MBM.CurrentImage := 0; } + { try } + { if Assigned(MBM) then } + { try } + { WRT := TFPWriterPNG.create; } + { WRT.Indexed := False; } + { WRT.UseAlpha := MBM.MemBitmap.HasTransparentPixels; } + { WRT.CompressionLevel := clnone; } + { MBM.MemBitmap.SaveToStream(AMS, WRT); } + { finally } + { WRT.Free; } + { end; } + { finally } + { MBM.Free; } + { end; } + { PNGToPageInfo(PageInfo, AMS); } + { finally } + { AMS.Free; } + { end; } +{ end; } + procedure ImageToPageInfo(const PageInfo: TPageInfo); var IMG: TFPCustomImage; @@ -444,6 +476,9 @@ procedure TPageInfo.LoadImageData; else if Ext = 'webp' then WEBPToPageInfo(Self) + { else } + { if Ext = 'gif' then } + { GIFToPageInfo(Self) } else ImageToPageInfo(Self); except diff --git a/baseunits/ImgInfos.pas b/baseunits/ImgInfos.pas index 13bff4399..dd1b60c2b 100644 --- a/baseunits/ImgInfos.pas +++ b/baseunits/ImgInfos.pas @@ -56,10 +56,10 @@ TImageHandlerRec = record TimageHandlerMgr = class private - FList: array of TImageHandlerRec; FEmptyHandlerRec: TImageHandlerRec; function GetCount: Integer; public + List: array of TImageHandlerRec; constructor Create; destructor Destroy; override; procedure Add(const ReaderClass: TFPCustomImageReaderClass; @@ -82,7 +82,7 @@ TimageHandlerMgr = class function GetImageFileWriterClass(const FileName: String): TFPCustomImageWriterClass; inline; function GetImageExtWriterClass(const Ext: String): TFPCustomImageWriterClass; inline; function GetImageWriterExt(const Ext: String): String; inline; - public + published property Count: Integer read GetCount; end; @@ -180,7 +180,7 @@ function GetImageWriterExt(const Ext: String): String; function TimageHandlerMgr.GetCount: Integer; begin - Result := Length(FList); + Result := Length(List); end; constructor TimageHandlerMgr.Create; @@ -190,7 +190,7 @@ constructor TimageHandlerMgr.Create; destructor TimageHandlerMgr.Destroy; begin - SetLength(Flist, 0); + SetLength(List, 0); inherited Destroy; end; @@ -202,17 +202,17 @@ procedure TimageHandlerMgr.Add(const ReaderClass: TFPCustomImageReaderClass; var i: Integer; begin - i := Length(FList); - SetLength(FList, i + 1); - FList[i].ReaderClass := ReaderClass; - FList[i].WriterClass := WriterClass; - FList[i].CheckImageStream := CheckImageStreamFunc; - FList[i].GetImageStreamSize := GetImageStreamSizeProc; - FList[i].Ext := Ext; + i := Length(List); + SetLength(List, i + 1); + List[i].ReaderClass := ReaderClass; + List[i].WriterClass := WriterClass; + List[i].CheckImageStream := CheckImageStreamFunc; + List[i].GetImageStreamSize := GetImageStreamSizeProc; + List[i].Ext := Ext; if WExt <> '' then - FList[i].WExt := WExt + List[i].WExt := WExt else - FList[i].WExt := Ext; + List[i].WExt := Ext; end; function TimageHandlerMgr.GetImageHandlerByStream(const Stream: TStream): PImageHandlerRec; @@ -225,12 +225,12 @@ function TimageHandlerMgr.GetImageHandlerByStream(const Stream: TStream): PImage if Stream.Size = 0 then Exit; P := Stream.Position; try - for i := Low(FList) to High(Flist) do + for i := Low(List) to High(List) do begin Stream.Position := 0; - if FList[i].CheckImageStream(Stream) then + if List[i].CheckImageStream(Stream) then begin - Result := @FList[i]; + Result := @List[i]; Break; end; end; @@ -258,10 +258,10 @@ function TimageHandlerMgr.GetImageHandlerByExt(const Ext: String): PImageHandler i: Integer; begin Result := @FEmptyHandlerRec; - for i := Low(FList) to High(Flist) do - if Ext = FList[i].Ext then + for i := Low(List) to High(List) do + if Ext = List[i].Ext then begin - Result := @FList[i]; + Result := @List[i]; Break; end; end; @@ -633,10 +633,10 @@ initialization ImageHandlerMgr := TimageHandlerMgr.Create; ImageHandlerMgr.Add(TFPReaderJPEG, TFPWriterJPEG, @JPEGCheckImageStream, @JPEGGetImageSize, 'jpg'); ImageHandlerMgr.Add(TFPReaderPNG, TFPWriterPNG, @PNGCheckImageStream, @PNGGetImageSize, 'png'); + ImageHandlerMgr.Add(nil, nil, @WEBPCheckImageStream, @WEBPGetImageSize, 'webp'); ImageHandlerMgr.Add(TFPReaderGif, TFPWriterPNG, @GIFCheckImageStream, @GIFGetImageSize, 'gif', 'png'); ImageHandlerMgr.Add(TFPReaderBMP, TFPWriterBMP, @BMPCheckImageStream, @BMPGetImageSize, 'bmp'); ImageHandlerMgr.Add(TFPReaderTiff, TFPWriterTiff, @TIFFCheckImageStream, @TIFFGetImageSize, 'tif'); - ImageHandlerMgr.Add(nil, nil, @WEBPCheckImageStream, @WEBPGetImageSize, 'webp'); finalization ImageHandlerMgr.Free; diff --git a/baseunits/ModuleList.inc b/baseunits/ModuleList.inc index a20acb166..0f831860d 100644 --- a/baseunits/ModuleList.inc +++ b/baseunits/ModuleList.inc @@ -1,52 +1,16 @@ -uses - MangaFox, - Mangacan, - MangaReader, - MangaLife, - MangaHere, - MangaTr, - Madokami, - RawSenManga, - KissManga, - MangaHome, - MangaChanRU, - MintMangaRU, - MangaHubRU, - AcademyVN, - Webtoons, - Tsumino, - WebtoonTr, - KuManga, - SenManga, - MangaInn, - LeoManga, - MangaIndo, - GoodManga, - MangaZuki, - ReadMangaToday, - MangaOnlineBR, - Tapas, - Taadd, - NineManga, - BlogTruyen, - MangaAe, - MangaTube, - Mangaf, - MangaRock, - PsychoPlay, - MangaWindow, - TranslateWebtoon, - // Raw Official - SundayWebEvery, - TonariNoYoungJump, - YoungAceUp, - NewType, - Comico, - Shogakukan, - // Adult - WPAdultSiteSkins, - EHentai, - Luscious, - EightMuses, - HentaiCafe, - Hentai2Read; +BlogTruyen, +Comico, +EHentai, +EightMuses, +Hentai2Read, +KissManga, +Madokami, +MangaFox, +MangaHome, +RawSenManga, +SundayWebEvery, +Tapas, +TonariNoYoungJump, +TranslateWebtoon, +Tsumino, +WPAdultSiteSkins; diff --git a/baseunits/SQLiteData.pas b/baseunits/SQLiteData.pas index 1f6efaec8..0a649f29f 100644 --- a/baseunits/SQLiteData.pas +++ b/baseunits/SQLiteData.pas @@ -5,15 +5,18 @@ interface uses - SysUtils, LazFileUtils, strutils, sqlite3conn, sqldb; + SysUtils, Classes, LazFileUtils, strutils, sqlite3conn, sqldb, SQLite3Dyn; type { TSQLite3ConnectionH } TSQLite3ConnectionH = class(TSQLite3Connection) + protected + procedure DoInternalDisconnect; override; public property Handle read GetHandle; + property Statements; end; TExceptionEvent = procedure(Sender: TObject; E: Exception) of object; @@ -28,7 +31,7 @@ TSQliteData = class FOnError: TExceptionEvent; FTrans: TSQLTransaction; FQuery: TSQLQuery; - FFilename: String; + FFileName: String; FTableName: String; FCreateParams: String; FSelectParams: String; @@ -65,7 +68,7 @@ TSQliteData = class property Connection: TSQLite3ConnectionH read FConn; property Transaction: TSQLTransaction read FTrans; property Table: TSQLQuery read FQuery; - property Filename: String read FFilename write FFilename; + property Filename: String read FFileName write FFileName; property TableName: String read FTableName write FTableName; property CreateParams: String read FCreateParams write SetCreateParams; property SelectParams: String read FSelectParams write SetSelectParams; @@ -84,6 +87,7 @@ function QuotedStrD(const S: Integer): String; overload; inline; implementation + function QuotedStr(const S: Integer): String; begin Result := AnsiQuotedStr(IntToStr(S), ''''); @@ -94,9 +98,26 @@ function QuotedStr(const S: Boolean): String; Result := AnsiQuotedStr(BoolToStr(S, '1', '0'), ''''); end; +function ToStrZeroPad(const i, len: Word): String; +begin + Result:=IntToStr(i); + if Length(Result) nil then + begin + checkerror(sqlite3_close_v2(lhandle)); + ReleaseSQLite; + end; +end; + { TSQliteData } procedure TSQliteData.DoOnError(E: Exception); @@ -163,9 +208,9 @@ procedure TSQliteData.SetSelectParams(AValue: String); function TSQliteData.OpenDB: Boolean; begin Result := False; - if FFilename = '' then Exit; + if FFileName = '' then Exit; try - FConn.DatabaseName := FFilename; + FConn.DatabaseName := FFileName; FConn.Connected := True; FTrans.Active := True; except @@ -274,7 +319,7 @@ constructor TSQliteData.Create; AutoApplyUpdates := True; FAutoVacuum := True; FRecordCount := 0; - FFilename := ''; + FFileName := ''; FTableName := 'maintable'; FCreateParams := ''; end; @@ -292,8 +337,8 @@ function TSQliteData.Open(const AOpenTable: Boolean; const AGetRecordCount: Boolean): Boolean; begin Result := False; - if (FFilename = '') or (FCreateParams = '') then Exit; - if FileExistsUTF8(FFilename) then + if (FFileName = '') or (FCreateParams = '') then Exit; + if FileExistsUTF8(FFileName) then Result := OpenDB else Result := CreateDB; diff --git a/baseunits/SelfUpdater.pas b/baseunits/SelfUpdater.pas index aad9ecbf1..c5b83a2ea 100644 --- a/baseunits/SelfUpdater.pas +++ b/baseunits/SelfUpdater.pas @@ -5,38 +5,24 @@ interface uses - Classes, SysUtils, httpsendthread, BaseThread, FMDOptions, process, ComCtrls, Controls, - Dialogs, StdCtrls, Buttons, Forms, blcksock; + Classes, SysUtils, httpsendthread, FMDOptions, StatusBarDownload, process, + Controls, Dialogs, Forms; type { TSelfUpdaterThread } - TSelfUpdaterThread = class(TBaseThread) + TSelfUpdaterThread = class(TStatusBarDownload) private - FStatusBar: TStatusBar; - FProgressBar: TProgressBar; - FButtonCancel: TSpeedButton; - FHTTP: THTTPSendThread; - FTotalSize: Integer; - FCurrentSize: Integer; FFailedMessage: String; - FStatusText: String; protected - procedure ButtonCancelClick(Sender: TObject); - procedure HTTPSockOnStatus(Sender: TObject; Reason: THookSocketReason; - const Value: String); procedure HTTPRedirected(const AHTTP: THTTPSendThread; const URL: String); protected procedure SyncStart; procedure SyncFinal; - procedure SyncStartDownload; - procedure SyncUpdateProgress; - procedure SyncUpdateStatus; procedure SyncShowFailed; procedure SyncFinishRestart; procedure ProceedUpdate; - procedure UpdateStatusText(const S: String); procedure Execute; override; public UpdateURL: String; @@ -64,31 +50,6 @@ implementation { TSelfUpdaterThread } -procedure TSelfUpdaterThread.ButtonCancelClick(Sender: TObject); -begin - Self.Terminate; -end; - -procedure TSelfUpdaterThread.HTTPSockOnStatus(Sender: TObject; - Reason: THookSocketReason; const Value: String); -begin - if Terminated then - Exit; - if Reason = HR_ReadCount then - begin - if FTotalSize = 0 then - FTotalSize := StrToIntDef(Trim(FHTTP.Headers.Values['Content-Length']), 0); - Inc(FCurrentSize, StrToInt(Value)); - Synchronize(@SyncUpdateProgress); - end - else - if Reason = HR_Connect then - begin - FCurrentSize := 0; - FTotalSize := 0; - end; -end; - procedure TSelfUpdaterThread.HTTPRedirected(const AHTTP: THTTPSendThread; const URL: String); begin @@ -98,105 +59,13 @@ procedure TSelfUpdaterThread.HTTPRedirected(const AHTTP: THTTPSendThread; procedure TSelfUpdaterThread.SyncStart; begin SelfUpdaterThread := Self; - - FStatusBar := TStatusBar.Create(FormMain); - with FStatusBar do - begin - Parent := FormMain; - SimplePanel := False; - with Panels.Add do // panel for progress bar - Width := 100; - Panels.Add; // panel for progress text - Panels.Add; // panel for status text - end; - - FProgressBar := TProgressBar.Create(FormMain); - with FProgressBar do - begin - Parent := FStatusBar; - Align := alNone; - Smooth := True; - Style := pbstNormal; - Min := 0; - Width := FStatusBar.Panels[0].Width - 10; - Anchors := [akTop, akLeft, akBottom]; - AnchorSideTop.Control := FStatusBar; - AnchorSideTop.Side := asrTop; - AnchorSideLeft.Control := FStatusBar; - AnchorSideLeft.Side := asrTop; - AnchorSideBottom.Control := FStatusBar; - AnchorSideBottom.Side := asrBottom; - BorderSpacing.Top := 2; - BorderSpacing.Left := 5; - BorderSpacing.Bottom := 2; - end; - - FButtonCancel := TSpeedButton.Create(FormMain); - with FButtonCancel do - begin - Parent := FStatusBar; - Align := alNone; - AutoSize := True; - Caption := RS_ButtonCancel; - ShowCaption := True; - Flat := True; - Anchors := [akTop, akRight, akBottom]; - AnchorSideTop.Control := FStatusBar; - AnchorSideTop.Side := asrTop; - AnchorSideRight.Control := FStatusBar; - AnchorSideRight.Side := asrRight; - AnchorSideBottom.Control := FStatusBar; - AnchorSideBottom.Side := asrBottom; - BorderSpacing.Top := 2; - BorderSpacing.Right := 5; - BorderSpacing.Bottom := 2; - OnClick := @ButtonCancelClick; - end; end; procedure TSelfUpdaterThread.SyncFinal; begin - FHTTP.Sock.OnStatus := nil; - FreeAndNil(FStatusBar); - FreeAndNil(FProgressBar); - FreeAndNil(FButtonCancel); SelfUpdaterThread := nil; end; -procedure TSelfUpdaterThread.SyncStartDownload; -begin - FCurrentSize := 0; - FTotalSize := 0; - FProgressBar.Max := 0; - FProgressBar.Position := 0; - FStatusBar.Panels[1].Text := ''; - FStatusBar.Panels[1].Width := 0; - SyncUpdateStatus; -end; - -procedure TSelfUpdaterThread.SyncUpdateProgress; -var - s: String; -begin - if FStatusBar = nil then - Exit; - if FProgressBar.Max <> FTotalSize then - FProgressBar.Max := FTotalSize; - if FProgressBar.Position <> FCurrentSize then - FProgressBar.Position := FCurrentSize; - - s := FormatByteSize(FCurrentSize); - if FTotalSize <> 0 then - s += '/' + FormatByteSize(FTotalSize); - FStatusBar.Panels[1].Width := FStatusBar.Canvas.TextWidth(s) + 10; - FStatusBar.Panels[1].Text := s; -end; - -procedure TSelfUpdaterThread.SyncUpdateStatus; -begin - FStatusBar.Panels[2].Text := FStatusText; -end; - procedure TSelfUpdaterThread.SyncShowFailed; begin MessageDlg(RS_FailedTitle, FFailedMessage, mtError, [mbOK], 0); @@ -239,36 +108,27 @@ procedure TSelfUpdaterThread.ProceedUpdate; FFailedMessage := Format(RS_MissingFile, [OLD_CURRENT_UPDATER_EXE]); end; -procedure TSelfUpdaterThread.UpdateStatusText(const S: String); -begin - if FStatusText = S then - Exit; - FStatusText := S; - Synchronize(@SyncUpdateStatus); -end; - procedure TSelfUpdaterThread.Execute; begin DownloadSuccess := False; if UpdateURL = '' then Exit; try - FStatusText := Format(RS_Downloading, [UpdateURL]); - Synchronize(@SyncStartDownload); - if FHTTP.GET(UpdateURL) and (FHTTP.ResultCode < 300) then + UpdateStatusText(Format(RS_Downloading, [UpdateURL])); + if HTTP.GET(UpdateURL) and (HTTP.ResultCode < 300) then begin DownloadSuccess := True; Filename := FMD_DIRECTORY + UPDATE_PACKAGE_NAME; if FileExists(Filename) then DeleteFile(Filename); if not FileExists(Filename) then - FHTTP.Document.SaveToFile(Filename); + HTTP.Document.SaveToFile(Filename); if FileExists(Filename) then DeleteFile(Filename); if not FileExists(Filename) then begin - FHTTP.Document.SaveToFile(Filename); + HTTP.Document.SaveToFile(Filename); if not FileExists(Filename) then begin FFailedMessage := Format(RS_FailedToSave, [Filename]); @@ -289,7 +149,7 @@ procedure TSelfUpdaterThread.Execute; end else FFailedMessage := Format(RS_FailedDownload, [NewVersionString, - FHTTP.ResultCode, FHTTP.ResultString]); + HTTP.ResultCode, HTTP.ResultString]); except on E: Exception do FFailedMessage := E.Message; @@ -298,13 +158,9 @@ procedure TSelfUpdaterThread.Execute; constructor TSelfUpdaterThread.Create; begin - inherited Create(True); - FreeOnTerminate := True; + inherited Create(True, FormMain, FormMain.IconList, 24); FFailedMessage := ''; - FHTTP := THTTPSendThread.Create(Self); - FHTTP.UserAgent := UserAgentCURL; - FHTTP.Sock.OnStatus := @HTTPSockOnStatus; - FHTTP.OnRedirected := @HTTPRedirected; + HTTP.OnRedirected := @HTTPRedirected; Synchronize(@SyncStart); end; @@ -316,7 +172,6 @@ destructor TSelfUpdaterThread.Destroy; if DownloadSuccess then Synchronize(@SyncFinishRestart); Synchronize(@SyncFinal); - FHTTP.Free; inherited Destroy; end; diff --git a/baseunits/SimpleException/SimpleException.pas b/baseunits/SimpleException/SimpleException.pas index 4518ba882..dcff26366 100644 --- a/baseunits/SimpleException/SimpleException.pas +++ b/baseunits/SimpleException/SimpleException.pas @@ -31,6 +31,7 @@ unit SimpleException; {$mode objfpc}{$H+} +{$define MULTILOG} interface @@ -525,6 +526,7 @@ procedure TSimpleException.CreateExceptionReport; if Assigned(FLastException) then begin FLastReport := FLastReport + + 'Exception Object : ' + HexStr(FLastException) + LineEnding + 'Exception Class : ' + FLastException.ClassName + LineEnding + 'Message : ' + FLastException.Message + LineEnding; end; @@ -534,7 +536,7 @@ procedure TSimpleException.CreateExceptionReport; if Logger.Enabled then begin if Assigned(FLastException) then - Logger.SendExceptionStr(FLastException.ClassName + ' - ' + FLastException.Message, S) + Logger.SendExceptionStr(HexStr(FLastException) + ' ' + FLastException.ClassName + ' - ' + FLastException.Message, S) else Logger.SendExceptionStr('Program exception!', S); end diff --git a/baseunits/SimpleTranslator.pas b/baseunits/SimpleTranslator.pas index cd9bd514e..ebac7b4b8 100644 --- a/baseunits/SimpleTranslator.pas +++ b/baseunits/SimpleTranslator.pas @@ -26,7 +26,7 @@ interface uses Classes, SysUtils, strutils, gettext, LazFileUtils, LazUTF8, LCLTranslator, - Translations, LResources, Forms; + Translations, LResources, Forms, LCLVersion; type TLanguageItem = record @@ -499,7 +499,7 @@ TLanguageItem = record function SetLang(const lang: string; appname: string = ''): Boolean; function SetLangByIndex(const Index: Integer): Boolean; - function GetDefaultLang: string; + function GetDefaultLang: string; inline; implementation @@ -768,11 +768,11 @@ function SetLangByIndex(const Index: Integer): Boolean; function GetDefaultLang: string; begin - {$IF FPC_FULLVERSION >= 20701} + {$if lcl_fullversion > 2000600} + Result := LCLTranslator.SetDefaultLang(''); + {$else} Result := LCLTranslator.GetDefaultLang; - {$ELSE} - Result := ''; - {$ENDIF} + {$ifend} end; initialization diff --git a/baseunits/StatusBarDownload.pas b/baseunits/StatusBarDownload.pas new file mode 100644 index 000000000..c4adae427 --- /dev/null +++ b/baseunits/StatusBarDownload.pas @@ -0,0 +1,274 @@ +unit StatusBarDownload; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, BaseThread, httpsendthread, blcksock, ExtCtrls, Forms, + Controls, Buttons, Graphics, ComCtrls; + +type + + { TStatusBarDownload } + + TStatusBarDownload = class(TBaseThread) + private + FOwnerForm: TForm; + FImageList: TImageList; + FStatusBar: TPanel; + FButtonCancel: TSpeedButton; + FButtonCancelImageIndex: Integer; + + FControlMargin: Integer; + FResized: Boolean; + FProgressBarRect, + FProgressBarPercentsRect, + FStatusTextRect: TRect; + FProgressText, + FStatusText: String; + + FTimerRepaint: TTimer; + FNeedRepaint: Boolean; + + FHTTP: THTTPSendThread; + FTotalSize, + FCurrentSize: Integer; + FPercents: Double; + protected + procedure SyncCreate; + procedure SyncDestroy; + procedure TimerRepaintTimer(Sender: TObject); + procedure StatusBarPaint(Sender: TObject); + procedure StatusBarRezise(Sender: TObject); + procedure ButtonCancelClick(Sender: TObject); + procedure HTTPSockOnStatus(Sender: TObject; Reason: THookSocketReason; + const Value: String); + procedure UpdateStatusText(AStatusText: String); + public + constructor Create(CreateSuspended: Boolean = True; AOwnerForm: TForm = nil; + AImageList: TImageList = nil; AButtonCancelImageIndex: Integer = -1); + destructor Destroy; override; + published + property HTTP: THTTPSendThread read FHTTP; + property StatusBar: TPanel read FStatusBar; + end; + +implementation + +uses uBaseUnit; + +const + CL_ProgressBarBaseLine = $bcbcbc; + CL_ProgressBarBase = $e6e6e6; + CL_ProgressBarLine = $25b006; + CL_ProgressBar = $42d932; + +{ TStatusBarDownload } + +procedure TStatusBarDownload.SyncCreate; +var + txtHeight: Integer; +begin + FControlMargin := FOwnerForm.ScaleFontTo96(2); + FStatusBar := TPanel.Create(nil); + with FStatusBar do begin + Parent := FOwnerForm; + DoubleBuffered := True; + Align := alBottom; + AutoSize := False; + txtHeight := Canvas.GetTextHeight('A'); + Height := txtHeight + (FControlMargin * 4); + Caption := ''; + Color := clBtnFace; + BevelOuter := bvNone; + BevelInner := bvNone; + BorderStyle := bsNone; + BorderSpacing.Top := FControlMargin; + OnPaint := @StatusBarPaint; + OnResize := @StatusBarRezise; + Canvas.Brush.Style := bsSolid; + Canvas.Pen.Style := psSolid; + FResized := True; + end; + + FButtonCancel := TSpeedButton.Create(FStatusBar); + with FButtonCancel do begin + Parent := FStatusBar; + Align := alNone; + AutoSize := False; + Flat := True; + Anchors := [akTop, akRight, akBottom]; + AnchorSideTop.Control := FStatusBar; + AnchorSideTop.Side := asrTop; + BorderSpacing.Top := FControlMargin; + AnchorSideRight.Control := FStatusBar; + AnchorSideRight.Side := asrRight; + BorderSpacing.Right := FControlMargin; + AnchorSideBottom.Control := FStatusBar; + AnchorSideBottom.Side := asrBottom; + BorderSpacing.Bottom := FControlMargin; + Width := Height; + OnClick := @ButtonCancelClick; + if Assigned(FImageList) and (FButtonCancelImageIndex > -1) then begin + Images := FImageList; + ImageIndex := FButtonCancelImageIndex; + end; + end; + + StatusBarRezise(FStatusBar); + + FTimerRepaint := TTimer.Create(FStatusBar); + FTimerRepaint.Interval := 1000; + FTimerRepaint.OnTimer := @TimerRepaintTimer; + FTimerRepaint.Enabled := True; + FNeedRepaint := True; +end; + +procedure TStatusBarDownload.SyncDestroy; +begin + FStatusBar.Free; +end; + +procedure TStatusBarDownload.TimerRepaintTimer(Sender: TObject); +begin + if FNeedRepaint then + begin + FNeedRepaint := False; + FStatusBar.Repaint; + end; +end; + +procedure TStatusBarDownload.StatusBarPaint(Sender: TObject); +var + txtWidth, txtHeight: integer; +begin + with FStatusBar.Canvas do begin + Pen.Color := clActiveBorder; + Line(0,0,FStatusBar.ClientRect.Right,0); + + if FResized then begin + FProgressBarRect := FStatusBar.ClientRect; + FProgressBarRect.Inflate(-FControlMargin, -(FControlMargin * 2)); + FStatusTextRect := FStatusBar.ClientRect; + FProgressBarRect.Width := GetTextWidth('_999.99 MB/999.99 MB_'); + FStatusTextRect.Left := FProgressBarRect.Right + (FControlMargin * 2); + FStatusTextRect.Right := FButtonCancel.Left - FControlMargin; + FResized := False; + end; + + Brush.Style := bsSolid; + Pen.Style := psSolid; + + Pen.Color := CL_ProgressBarBaseLine; + Brush.Color := CL_ProgressBarBase; + Rectangle(FProgressBarRect); + + if FPercents > 0 then begin + FProgressBarPercentsRect := FProgressBarRect; + FProgressBarPercentsRect.Right := + Round((FProgressBarPercentsRect.Right - FProgressBarPercentsRect.Left) * FPercents) + FProgressBarPercentsRect.Left; + + Pen.Color := CL_ProgressBarLine; + Brush.Color := CL_ProgressBar; + + Frame(FProgressBarPercentsRect); + FProgressBarPercentsRect.Inflate(-2, -2); + GradientFill(FProgressBarPercentsRect, BlendColor(Brush.Color, CL_ProgressBarBase, 128), Brush.Color, gdHorizontal); + + Brush.Style := bsClear; + + GetTextSize(FProgressText, txtWidth, txtHeight); + TextRect(FProgressBarRect, FProgressBarRect.Left + ((FProgressBarRect.Right - FProgressBarRect.Left - txtWidth) div 2), + FProgressBarRect.Top + ((FProgressBarRect.Bottom - FProgressBarRect.Top - txtHeight) div 2), FProgressText); + end; + txtHeight := GetTextHeight(FStatusText); + TextRect(FStatusTextRect, FStatusTextRect.Left, FStatusTextRect.Top + ((FStatusTextRect.Bottom - FStatusTextRect.Top - txtHeight) div 2), FStatusText); + end; +end; + +procedure TStatusBarDownload.StatusBarRezise(Sender: TObject); +begin + FResized := True; +end; + +procedure TStatusBarDownload.ButtonCancelClick(Sender: TObject); +begin + Self.Terminate; +end; + +procedure TStatusBarDownload.HTTPSockOnStatus(Sender: TObject; + Reason: THookSocketReason; const Value: String); +begin + if Terminated then + Exit; + if Reason = HR_ReadCount then + begin + FNeedRepaint := True; + if FTotalSize = 0 then + begin + FTotalSize := StrToIntDef(Trim(FHTTP.Headers.Values['Content-Length']), 0); + FPercents := 0; + end; + + Inc(FCurrentSize, StrToInt(Value)); + if (FCurrentSize <> 0) then + begin + if FTotalSize < FCurrentSize then + FPercents := 0 + else + FPercents := FCurrentSize / FTotalSize; + end; + FProgressText := FormatByteSize(FCurrentSize); + if FTotalSize <> 0 then + FProgressText := FProgressText + '/' + FormatByteSize(FTotalSize); + end + else + if Reason = HR_Connect then + begin + FNeedRepaint := True; + FCurrentSize := 0; + FTotalSize := 0; + end; +end; + +procedure TStatusBarDownload.UpdateStatusText(AStatusText: String); +begin + if AStatusText <> FStatusText then + begin + FStatusText := AStatusText; + FNeedRepaint := True; + end; +end; + +constructor TStatusBarDownload.Create(CreateSuspended: Boolean; + AOwnerForm: TForm; AImageList: TImageList; AButtonCancelImageIndex: Integer); +begin + inherited Create(CreateSuspended); + FreeOnTerminate := True; + FOwnerForm := AOwnerForm; + FImageList := AImageList; + FButtonCancelImageIndex := AButtonCancelImageIndex; + FProgressText := ''; + FStatusText := ''; + + FHTTP := THTTPSendThread.Create(Self); + FHTTP.UserAgent := UserAgentCURL; + FHTTP.Sock.OnStatus := @HTTPSockOnStatus; + FTotalSize := 0; + FTotalSize := 0; + FPercents := 0; + + Synchronize(@SyncCreate); +end; + +destructor TStatusBarDownload.Destroy; +begin + Synchronize(@SyncDestroy); + + FHTTP.Free; + inherited Destroy; +end; + +end. + diff --git a/baseunits/WebsiteModules.pas b/baseunits/WebsiteModules.pas index 48654d25c..0f946df7d 100644 --- a/baseunits/WebsiteModules.pas +++ b/baseunits/WebsiteModules.pas @@ -11,8 +11,8 @@ interface uses Classes, SysUtils, fgl, uData, uDownloadsManager, FMDOptions, httpsendthread, - WebsiteModulesSettings, LazLogger, Cloudflare, RegExpr, fpjson, jsonparser, - jsonscanner, fpjsonrtti; + WebsiteModulesSettings, Process, Multilog, LazLogger, Cloudflare, RegExpr, fpjson, jsonparser, + jsonscanner, fpjsonrtti, uBaseUnit, httpcookiemanager, syncobjs; const MODULE_NOT_FOUND = -1; @@ -73,16 +73,18 @@ TWebsiteOptionItem = record TWebsiteModuleAccount = class private - FCookies: String; FEnabled: Boolean; FPassword: String; FStatus: TAccountStatus; FUsername: String; + public + Guardian: TCriticalSection; + constructor Create; + destructor Destroy; override; published property Enabled: Boolean read FEnabled write FEnabled; property Username: String read FUsername write FUsername; property Password: String read FPassword write FPassword; - property Cookies: String read FCookies write FCookies; property Status: TAccountStatus read FStatus write FStatus; end; @@ -99,6 +101,7 @@ TModuleContainer = class FTotalDirectory: Integer; FCloudflareCF: TCFProps; FCloudflareEnabled: Boolean; + FCookieManager: THTTPCookieManager; procedure SetAccountSupport(AValue: Boolean); procedure SetCloudflareEnabled(AValue: Boolean); procedure CheckCloudflareEnabled(const AHTTP: THTTPSendThread); @@ -107,6 +110,7 @@ TModuleContainer = class procedure AddOption(const AOptionType: TWebsiteOptionType; const ABindValue: Pointer; const AName: String; const ACaption: PString; const AItems: PString = nil); public + Guardian: TCriticalSection; Tag: Integer; TagPtr: Pointer; Website: String; @@ -165,6 +169,7 @@ TModuleContainer = class property Settings: TWebsiteModuleSettings read FSettings write FSettings; property AccountSupport: Boolean read FAccountSupport write SetAccountSupport; property Account: TWebsiteModuleAccount read FAccount write FAccount; + property CookieManager: THTTPCookieManager read FCookieManager; end; TModuleContainers = specialize TFPGList; @@ -296,6 +301,7 @@ function CleanOptionName(const S: String): String; implementation +uses {$I ModuleList.inc} var @@ -321,6 +327,19 @@ function CleanOptionName(const S: String): String; Inc(i); end; +{ TWebsiteModuleAccount } + +constructor TWebsiteModuleAccount.Create; +begin + Guardian := TCriticalSection.Create; +end; + +destructor TWebsiteModuleAccount.Destroy; +begin + Guardian.Free; + inherited Destroy; +end; + { TModuleContainer } procedure TModuleContainer.SetCloudflareEnabled(AValue: Boolean); @@ -328,7 +347,7 @@ procedure TModuleContainer.SetCloudflareEnabled(AValue: Boolean); if FCloudflareEnabled = AValue then Exit; FCloudflareEnabled := AValue; if FCloudflareEnabled then - FCloudflareCF := TCFProps.Create + FCloudflareCF := TCFProps.Create(self) else begin FCloudflareCF.Free; @@ -377,6 +396,7 @@ procedure TModuleContainer.SetTotalDirectory(AValue: Integer); constructor TModuleContainer.Create; begin + Guardian := TCriticalSection.Create; FSettings := TWebsiteModuleSettings.Create; FID := -1; ActiveTaskCount := 0; @@ -389,6 +409,7 @@ constructor TModuleContainer.Create; TotalDirectory := 1; CurrentDirectoryIndex := 0; CloudflareEnabled := True; + FCookieManager := THTTPCookieManager.Create; end; destructor TModuleContainer.Destroy; @@ -400,6 +421,8 @@ destructor TModuleContainer.Destroy; if Assigned(FAccount) then FAccount.Free; FSettings.Free; + Guardian.Free; + FCookieManager.Free; inherited Destroy; end; @@ -431,19 +454,21 @@ procedure TModuleContainer.PrepareHTTP(const AHTTP: THTTPSendThread); var s: String; begin + AHTTP.CookieManager := FCookieManager; + //todo: replace it with website challenges, there is more than cloudflare CheckCloudflareEnabled(AHTTP); - if not Settings.Enabled then Exit; + if not Settings.Enabled then exit; with Settings.HTTP do begin + if Cookies<>'' then + AHTTP.MergeCookies(Cookies); if UserAgent<>'' then AHTTP.UserAgent:=UserAgent; - if Cookies<>'' then - AHTTP.Cookies.Text:=Cookies; with Proxy do begin s:=''; case Proxy.ProxyType of - ptDirect:AHTTP.SetNoProxy; + ptDefault,ptDirect:AHTTP.SetNoProxy; ptHTTP:s:='HTTP'; ptSOCKS4:s:='SOCKS4'; ptSOCKS5:s:='SOCKS5'; @@ -845,7 +870,10 @@ function TWebsiteModules.Login(const AHTTP: THTTPSendThread; if ModuleExist(ModuleId) then with FModuleList[ModuleId] do if Assigned(OnLogin) then + begin + PrepareHTTP(AHTTP); Result := OnLogin(AHTTP, FModuleList[ModuleId]); + end; end; function TWebsiteModules.Login(const AHTTP: THTTPSendThread; @@ -914,6 +942,8 @@ procedure TWebsiteModules.LoadFromFile; fs: TFileStream; jp: TJSONParser; jo, jo2: TJSONObject; + j_cookies: TJSONArray; + c: THTTPCookie; begin if FModuleList.Count=0 then Exit; if not FileExists(MODULES_FILE) then Exit; @@ -934,6 +964,7 @@ procedure TWebsiteModules.LoadFromFile; if (ja<>nil) and (ja.Count<>0) then try jd:=TJSONDeStreamer.Create(nil); + jd.Options:=jd.Options+[jdoIgnorePropertyErrors]; for i:=FModuleList.Count-1 downto 0 do with FModuleList[i] do begin @@ -965,7 +996,23 @@ procedure TWebsiteModules.LoadFromFile; begin jo2:=jo.Get('Account',TJSONObject(nil)); if jo2<>nil then + begin jd.JSONToObject(jo2,Account); + if Account.Username<>'' then Account.Username := DecryptString(Account.Username); + if Account.Password<>'' then Account.Password := DecryptString(Account.Password); + if Account.Status=asChecking then + Account.Status:=asUnknown; + end; + end; + j_cookies:=jo.Get('Cookies',TJSONArray(nil)); + if Assigned(j_cookies) then + begin + for k:=0 to j_cookies.Count-1 do + begin + c:=THTTPCookie.Create; + CookieManager.Cookies.Add(c); + jd.JSONToObject(TJSONObject(j_cookies.Items[k]), c); + end; end; ja.Delete(j); end; @@ -980,14 +1027,15 @@ procedure TWebsiteModules.SaveToFile; var i, j: Integer; js: TJSONStreamer; - ja: TJSONArray; - fs: TFileStream; + ja, j_cookies: TJSONArray; + fs: TMemoryStream; jo: TJSONObject; jo2: TJSONObject; begin if FModuleList.Count=0 then Exit; ja:=TJSONArray.Create; js:=TJSONStreamer.Create(nil); + js.Options:=js.Options+[jsoDateTimeAsString]; try for i:=0 to FModuleList.Count-1 do with FModuleList[i] do @@ -1009,11 +1057,24 @@ procedure TWebsiteModules.SaveToFile; end; end; if Account<>nil then - jo.Add('Account',js.ObjectToJSON(Account)); + begin + jo2:=js.ObjectToJSON(Account); + jo2.Strings['Username']:=EncryptString(Account.Username); + jo2.Strings['Password']:=EncryptString(Account.Password); + jo.Add('Account',jo2); + end; + j_cookies:=TJSONArray.Create; + for j:=0 to CookieManager.Cookies.Count-1 do + begin + if CookieManager.Cookies[j].Persistent then + j_cookies.Add(js.ObjectToJSON(CookieManager.Cookies[j])); + end; + jo.Add('Cookies', j_cookies); end; - fs:=TFileStream.Create(MODULES_FILE,fmCreate); + fs:=TMemoryStream.Create; try ja.DumpJSON(fs); + fs.SaveToFile(MODULES_FILE); finally fs.Free; end; diff --git a/baseunits/WebsiteModulesSettings.pas b/baseunits/WebsiteModulesSettings.pas index 1c84a0654..9fbd9e439 100644 --- a/baseunits/WebsiteModulesSettings.pas +++ b/baseunits/WebsiteModulesSettings.pas @@ -42,6 +42,18 @@ THTTPSettings = class property UserAgent: String read FUserAgent write FUserAgent; property Proxy: TProxySettings read FProxy write FProxy; end; + + { TOverrideSettings } + + TOverrideSettings = class + private + FSaveToPath: String; + public + constructor Create; + destructor Destroy; override; + published + property SaveToPath: String read FSaveToPath write FSaveToPath; + end; { TWebsiteModuleSettings } @@ -49,6 +61,7 @@ TWebsiteModuleSettings = class private FEnabled: Boolean; FHTTP: THTTPSettings; + FOverrideSettings: TOverrideSettings; FMaxConnectionLimit: Integer; FMaxTaskLimit: Integer; FMaxThreadPerTaskLimit: Integer; @@ -65,6 +78,7 @@ TWebsiteModuleSettings = class property UpdateListNumberOfThread: Integer read FUpdateListNumberOfThread write FUpdateListNumberOfThread default 0; property UpdateListDirectoryPageNumber: Integer read FUpdateListDirectoryPageNumber write FUpdateListDirectoryPageNumber default 0; property HTTP: THTTPSettings read FHTTP write FHTTP; + property OverrideSettings: TOverrideSettings read FOverrideSettings write FOverrideSettings; end; implementation @@ -82,16 +96,30 @@ destructor THTTPSettings.Destroy; inherited Destroy; end; +{ TOverrideSettings } + +constructor TOverrideSettings.Create; +begin + +end; + +destructor TOverrideSettings.Destroy; +begin + inherited Destroy; +end; + { TWebsiteModuleSettings } constructor TWebsiteModuleSettings.Create; begin HTTP:=THTTPSettings.Create; + OverrideSettings:=TOverrideSettings.Create; end; destructor TWebsiteModuleSettings.Destroy; begin HTTP.Free; + OverrideSettings.Free; inherited Destroy; end; diff --git a/baseunits/httpcookiemanager.pas b/baseunits/httpcookiemanager.pas new file mode 100644 index 000000000..b95b9d6ae --- /dev/null +++ b/baseunits/httpcookiemanager.pas @@ -0,0 +1,259 @@ +unit httpcookiemanager; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fgl, StrUtils, DateUtils, syncobjs, synautil, httpsend; + +type + + { THTTPCookie } + + THTTPCookie = class + private + FName, + FValue, + FDomain, + FPath, + FSameSite: String; + FExpires: TDateTime; + FHostOnly, + FHttpOnly, + FSecure, + FPersistent: Boolean; + published + property Name: String read FName write FName; + property Value: String read FValue write FValue; + property Domain: String read FDomain write FDomain; + property Path: String read FPath write FPath; + property SameSite: String read FSameSite write FSameSite; + property Expires: TDateTime read FExpires write FExpires; + property HostOnly: Boolean read FHostOnly write FHostOnly; + property HttpOnly: Boolean read FHttpOnly write FHttpOnly; + property Secure: Boolean read FSecure write FSecure; + property Persistent: Boolean read FPersistent write FPersistent; + end; + + THTTPCookies = specialize TFPGObjectList; + + { THTTPCookieManager } + + THTTPCookieManager = class + private + FCookies: THTTPCookies; + FGuardian: TCriticalSection; + protected + procedure AddServerCookie(const AURL, ACookie: String; const AServerDate: TDateTime); + public + constructor Create; + destructor Destroy; override; + procedure AddServerCookies(const AURL: String; const AHTTP: THTTPSend); + procedure SetCookies(const AURL: String; const AHTTP: THTTPSend); + procedure Clear; + published + property Cookies: THTTPCookies read FCookies; + end; + +implementation + +{ defined in RFC 6265 https://tools.ietf.org/html/rfc6265 } + +{ THTTPCookieManager } + +constructor THTTPCookieManager.Create; +begin + FGuardian := TCriticalSection.Create; + FCookies := THTTPCookies.Create(True); +end; + +destructor THTTPCookieManager.Destroy; +begin + FCookies.Free; + FGuardian.Free; + inherited Destroy; +end; + +procedure THTTPCookieManager.AddServerCookie(const AURL, ACookie: String; + const AServerDate: TDateTime); +var + s, n, ni, v: String; + c: THTTPCookie; + Prot, User, Pass, Host, Port, Path, Para: String; + i: Integer; +begin + if Trim(ACookie) = '' then Exit; + n := Trim(SeparateLeft(ACookie, '=')); + for i := 0 to FCookies.Count-1 do + begin + if n = FCookies[i].Name then + begin + FCookies.Delete(i); + Break; + end; + end; + c := THTTPCookie.Create; + FCookies.Add(c); + for s in ACookie.Split([';']) do + begin + n := Trim(SeparateLeft(s,'=')); + v := Trim(SeparateRight(s, '=')); + ni := LowerCase(n); + if ni = 'domain' then + { + leading %x2E (".") is ignored per revised specification + https://tools.ietf.org/html/rfc6265#section-4.1.2.3 + } + c.Domain := LowerCase(TrimLeftSet(v, ['.'])) + else if ni = 'path' then + c.Path := v + else if ni = 'expires' then + begin + c.Expires := DecodeRfcDateTime(v); + c.Persistent := True; + end + else if ni = 'max-age' then + begin + c.Expires := IncSecond(AServerDate, StrToIntDef(v, 0)); + c.Persistent := True; + end + else if ni = 'secure' then + c.Secure := True + else if ni = 'httponly' then + c.HttpOnly := True + else if ni = 'samesite' then + c.SameSite := LowerCase(v) + else + begin + c.Name := n; + c.Value := v; + end; + end; + + ParseURL(AURL, Prot, User, Pass, Host, Port, Path, Para); + if c.Domain = '' then + c.Domain := LowerCase(Host); + if c.Path = '' then + c.Path := Path; + if c.SameSite = '' then + c.SameSite := 'none'; +end; + +procedure THTTPCookieManager.AddServerCookies(const AURL: String; + const AHTTP: THTTPSend); +var + i: Integer; + s: String; + d: TDateTime; +begin + FGuardian.Enter; + try + s := Trim(AHTTP.Headers.Values['Date']); + if s <> '' then + d := DecodeRfcDateTime(s) + else + d := Now; + for i := 0 to AHTTP.Headers.Count - 1 do + begin + if Pos('set-cookie', LowerCase(AHTTP.Headers[i])) = 1 then + begin + AddServerCookie(AURL, Trim(AHTTP.Headers.ValueFromIndex[i]), d); + end; + end; + finally + FGuardian.Leave; + end; +end; + +function CharEquals(const AString: string; const ACharPos: Integer; const AValue: Char): Boolean; +begin + if ACharPos < 1 then Exit(False); + Result := (ACharPos <= Length(AString)) and (AString[ACharPos] = AValue); +end; + +procedure THTTPCookieManager.SetCookies(const AURL: String; + const AHTTP: THTTPSend); +var + Prot, User, Pass, Host, Port, Path, Para: String; + i: Integer; + c: THTTPCookie; + + function IsPathMatch: Boolean; + begin + Result := SameText(Path, c.Path) or + ( Path.StartsWith(c.Path) and + ( c.Path.EndsWith('/') or CharEquals(Path, Length(c.Path), '/') ) + ); + end; + + function IsDomainMatch: Boolean; + begin + Result := False; + if (Host <> '') and (c.Domain <> '') then + begin + if SameText(Host, c.Domain) then + Result := True + else + if Host.EndsWith(c.Domain) then + begin + if Copy(Host, 1, Length(Host)-Length(c.Domain)).EndsWith('.') then + Result := True; + end; + end; + end; + + function MatchesHost: Boolean; + begin + if c.HostOnly then + Result := SameText(Host, c.Domain) + else + Result := IsDomainMatch; + end; + + function IsHTTP: Boolean; + begin + Result := (Prot = 'http') or (Prot = 'https'); + end; + +begin + if FCookies.Count = 0 then Exit; + FGuardian.Enter; + try + ParseURL(AURL, Prot, User, Pass, Host, Port, Path, Para); + Prot := LowerCase(Prot); + Host := LowerCase(Host); + i := 0; + while i <= FCookies.Count - 1 do + begin + c := FCookies[i]; + if (c.Persistent) and (c.Expires <= Now) then + FCookies.Delete(i) + else + begin + Inc(i); + if MatchesHost and IsPathMatch and + ((not c.Secure) or (c.Secure and c.Secure)) and + ((not c.HttpOnly) or (c.HttpOnly and IsHTTP)) then + begin + AHTTP.Cookies.Values[c.Name] := c.Value; + end; + end; + end; + finally + FGuardian.Leave; + end; +end; + +procedure THTTPCookieManager.Clear; +begin + FGuardian.Enter; + try + FCookies.Clear; + finally + FGuardian.Leave; + end; +end; + +end. + diff --git a/baseunits/httpsendthread.pas b/baseunits/httpsendthread.pas index 377cbfc6e..b7f481e57 100644 --- a/baseunits/httpsendthread.pas +++ b/baseunits/httpsendthread.pas @@ -6,7 +6,7 @@ interface uses Classes, SysUtils, httpsend, synautil, synacode, ssl_openssl, blcksock, - GZIPUtils, BaseThread, dateutils, strutils; + GZIPUtils, BaseThread, httpcookiemanager, dateutils, strutils; const @@ -61,11 +61,11 @@ THTTPSendThread = class(THTTPSend) FFollowRedirection: Boolean; FMaxRedirect: Integer; FAllowServerErrorResponse: Boolean; - FCookiesExpires: TDateTime; procedure SetTimeout(AValue: Integer); procedure OnOwnerTerminate(Sender: TObject); protected - procedure ParseCookiesExpires; + procedure SetHTTPCookies; + procedure ParseHTTPCookies; function InternalHTTPRequest(const Method, URL: String; const Response: TObject = nil): Boolean; public constructor Create(AOwner: TBaseThread = nil); @@ -77,6 +77,9 @@ THTTPSendThread = class(THTTPSend) function POST(const URL: String; const POSTData: String = ''; const Response: TObject = nil): Boolean; function XHR(const URL: String; const Response: TObject = nil): Boolean; function GetCookies: String; + procedure MergeCookies(const ACookies: String); + function GetLastModified: TDateTime; + function GetOriginalFileName: String; function ThreadTerminated: Boolean; procedure RemoveCookie(const CookieName: String); procedure SetProxy(const ProxyType, Host, Port, User, Pass: String); @@ -84,19 +87,21 @@ THTTPSendThread = class(THTTPSend) procedure SetNoProxy; procedure SetDefaultProxy; procedure Reset; + procedure ResetBasic; + procedure SaveDocumentToFile(const AFileName: String; const ATryOriginalFileName: Boolean = False; const ALastModified: TDateTime = -1); property Timeout: Integer read FTimeout write SetTimeout; property RetryCount: Integer read FRetryCount write FRetryCount; property GZip: Boolean read FGZip write FGZip; property FollowRedirection: Boolean read FFollowRedirection write FFollowRedirection; property AllowServerErrorResponse: Boolean read FAllowServerErrorResponse write FAllowServerErrorResponse; property Thread: TBaseThread read FOwner; - property CookiesExpires: TDateTime read FCookiesExpires; property MaxRedirect: Integer read FMaxRedirect write FMaxRedirect; public BeforeHTTPMethod: THTTPMethodEvent; AfterHTTPMethod: THTTPMethodEvent; OnHTTPRequest: THTTPRequestEvent; OnRedirected: THTTPMethodRedirectEvent; + CookieManager: THTTPCookieManager; property LastURL: String read FURL; end; @@ -117,10 +122,10 @@ function FormatByteSize(const ABytes: Integer; AShowPerSecond: Boolean = False): const UserAgentSynapse = 'Mozilla/4.0 (compatible; Synapse)'; - UserAgentCURL = 'curl/7.58.0'; + UserAgentCURL = 'curl/7.68.0'; UserAgentGooglebot = 'Mozilla/5.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)'; UserAgentMSIE = 'Mozilla/5.0 (Windows NT 10.0; Win64; Trident/7.0; rv:11.0) like Gecko'; - UserAgentFirefox = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0'; + UserAgentFirefox = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0'; UserAgentChrome = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36'; UserAgentVivaldi = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.90 Safari/537.36 Vivaldi/1.91.867.3'; UserAgentOpera = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 OPR/45.0.2552.888'; @@ -379,29 +384,16 @@ procedure THTTPSendThread.OnOwnerTerminate(Sender: TObject); Sock.AbortSocket; end; -procedure THTTPSendThread.ParseCookiesExpires; -var - i, p: Integer; - c: TDateTime; - s: String; +procedure THTTPSendThread.SetHTTPCookies; begin - FCookiesExpires := 0.0; - for i := 0 to FHeaders.Count-1 do - if Pos('set-cookie', LowerCase(FHeaders[i])) = 1 then - begin - s := SeparateRight(FHeaders[i], ':'); - p := Pos('expires', lowercase(s)); - if p <> 0 then - begin - s := Copy(s, p, Length(s)); - s := SeparateLeft(SeparateRight(s,'='),';'); - s := Trim(SeparateLeft(s, 'GMT')); - c := DecodeRfcDateTime(s); - if (FCookiesExpires = 0.0) or (c < FCookiesExpires) then - FCookiesExpires := c; - end; - end; - write + if Assigned(CookieManager) then + CookieManager.SetCookies(FURL, Self); +end; + +procedure THTTPSendThread.ParseHTTPCookies; +begin + if Assigned(CookieManager) then + CookieManager.AddServerCookies(FURL, Self); end; function THTTPSendThread.InternalHTTPRequest(const Method, URL: String; @@ -422,6 +414,7 @@ constructor THTTPSendThread.Create(AOwner: TBaseThread); Protocol := '1.1'; Headers.NameValueSeparator := ':'; Cookies.NameValueSeparator := '='; + Cookies.Delimiter := ';'; FGZip := True; FFollowRedirection := True; FAllowServerErrorResponse := False; @@ -467,9 +460,9 @@ function THTTPSendThread.HTTPMethod(const Method, URL: string): Boolean; aurl:=URL; if Assigned(BeforeHTTPMethod) then BeforeHTTPMethod(Self, amethod, aurl); - FCookiesExpires := 0.0; + SetHTTPCookies; Result := inherited HTTPMethod(amethod, aurl); - ParseCookiesExpires; + ParseHTTPCookies; if Assigned(AfterHTTPMethod) then AfterHTTPMethod(Self, amethod, aurl); end; @@ -619,6 +612,37 @@ function THTTPSendThread.GetCookies: String; end; end; +procedure THTTPSendThread.MergeCookies(const ACookies: String); +var + s: String; +begin + for s in ACookies.Split(';') do + begin + if Pos('=', s) > 0 then + Cookies.Values[SeparateLeft(s,'=')] := SeparateRight(s,'='); + end; +end; + +function THTTPSendThread.GetLastModified: TDateTime; +var + s: String; +begin + Result := Now; + s := Trim(Headers.Values['last-modified']); + if s <> '' then + Result := DecodeRfcDateTime(s); +end; + +function THTTPSendThread.GetOriginalFileName: String; +var + s: String; +begin + Result := ''; + s := Headers.Values['content-disposition']; + if s <> '' then + Result := GetBetween('filename="', '"', s); +end; + function THTTPSendThread.ThreadTerminated: Boolean; begin if Assigned(FOwner) then @@ -729,6 +753,30 @@ procedure THTTPSendThread.Reset; if FGZip then Headers.Values['Accept-Encoding'] := ' gzip, deflate'; end; +procedure THTTPSendThread.ResetBasic; +begin + Clear; + if FGZip then Headers.Values['Accept-Encoding'] := ' gzip, deflate'; +end; + +procedure THTTPSendThread.SaveDocumentToFile(const AFileName: String; + const ATryOriginalFileName: Boolean; const ALastModified: TDateTime); +var + f: String; + d: TDateTime; +begin + d := ALastModified; + if d = -1 then + d := GetLastModified; + f := ''; + if ATryOriginalFileName then + f := GetOriginalFileName; + if f = '' then f := AFileName; + Document.SaveToFile(f); + if FileExists(AFileName) then + FileSetDate(AFileName, DateTimeToFileDate(d)); +end; + initialization InitCriticalSection(CS_ALLHTTPSendThread); ALLHTTPSendThread := TFPList.Create; diff --git a/baseunits/lua/LuaBase.pas b/baseunits/lua/LuaBase.pas index 14b087b19..1be9109bc 100644 --- a/baseunits/lua/LuaBase.pas +++ b/baseunits/lua/LuaBase.pas @@ -23,8 +23,9 @@ function LuaLoadFromStream(L: Plua_State; AStream: TMemoryStream; AName: PAnsiCh implementation uses - LuaStrings, LuaBaseUnit, LuaRegExpr, LuaSynaUtil, LuaSynaCode, MultiLog, - LuaCrypto, LuaImagePuzzle, LuaDuktape; + LuaStrings, LuaBaseUnit, LuaRegExpr, LuaPCRE2, LuaSynaUtil, LuaSynaCode, + MultiLog, LuaCrypto, LuaImagePuzzle, LuaDuktape, LuaCriticalSection, + LuaLogger, LuaUtils, LuaMemoryStream; function luabase_print(L: Plua_State): Integer; cdecl; var @@ -36,7 +37,7 @@ function luabase_print(L: Plua_State): Integer; cdecl; LUA_TBOOLEAN: Logger.Send(BoolToStr(lua_toboolean(L, i), 'true', 'false')); else - Logger.Send(lua_tostring(L, i)); + Logger.Send(luaGetString(L, i)); end; end; @@ -53,10 +54,12 @@ procedure LuaBaseRegister(L: Plua_State); luaBaseUnitRegister(L); luaRegExprRegister(L); + luaPCRE2Register(L); luaSynaUtilRegister(L); luaSynaCodeRegister(L); luaCryptoRegister(L); luaDuktapeRegister(L); + luaLoggerRegister(L); luaClassRegisterAll(L); end; @@ -151,7 +154,7 @@ function LuaDumpFileToStream(L: Plua_State; AFilename: String; except Result.Free; Result := nil; - Logger.SendError(lua_tostring(L, -1)); + Logger.SendError(luaGetString(L, -1)); end; end; diff --git a/baseunits/lua/LuaBaseUnit.pas b/baseunits/lua/LuaBaseUnit.pas index 74480e510..4101e2988 100644 --- a/baseunits/lua/LuaBaseUnit.pas +++ b/baseunits/lua/LuaBaseUnit.pas @@ -16,19 +16,19 @@ implementation function lua_pos(L: Plua_State): Integer; cdecl; begin - lua_pushinteger(L, Pos(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushinteger(L, Pos(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function lua_trim(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, Trim(lua_tostring(L, 1))); + lua_pushstring(L, Trim(luaGetString(L, 1))); Result := 1; end; function lua_maybefillhost(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, MaybeFillHost(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, MaybeFillHost(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; @@ -44,10 +44,10 @@ function lua_invertstrings(L: Plua_State): Integer; cdecl; function lua_mangainfostatusifpos(L: Plua_State): Integer; cdecl; begin case lua_gettop(L) of - 3: lua_pushstring(L, MangaInfoStatusIfPos(lua_tostring(L, 1), - lua_tostring(L, 2), lua_tostring(L, 3))); - 2: lua_pushstring(L, MangaInfoStatusIfPos(lua_tostring(L, 1), lua_tostring(L, 2))); - 1: lua_pushstring(L, MangaInfoStatusIfPos(lua_tostring(L, 1))); + 3: lua_pushstring(L, MangaInfoStatusIfPos(luaGetString(L, 1), + luaGetString(L, 2), luaGetString(L, 3))); + 2: lua_pushstring(L, MangaInfoStatusIfPos(luaGetString(L, 1), luaGetString(L, 2))); + 1: lua_pushstring(L, MangaInfoStatusIfPos(luaGetString(L, 1))); else Exit(0); end; @@ -56,49 +56,49 @@ function lua_mangainfostatusifpos(L: Plua_State): Integer; cdecl; function lua_appendurldelim(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, AppendURLDelim(lua_tostring(L, 1))); + lua_pushstring(L, AppendURLDelim(luaGetString(L, 1))); Result := 1; end; function lua_removeurldelim(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, RemoveURLDelim(lua_tostring(L, 1))); + lua_pushstring(L, RemoveURLDelim(luaGetString(L, 1))); Result := 1; end; function lua_appendurldelimleft(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, AppendURLDelimLeft(lua_tostring(L, 1))); + lua_pushstring(L, AppendURLDelimLeft(luaGetString(L, 1))); Result := 1; end; function lua_removeurldelimleft(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, RemoveURLDelimLeft(lua_tostring(L, 1))); + lua_pushstring(L, RemoveURLDelimLeft(luaGetString(L, 1))); Result := 1; end; function lua_regexprgetmatch(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, RegExprGetMatch(lua_tostring(L, 1), lua_tostring(L, 2), lua_tointeger(L, 3))); + lua_pushstring(L, RegExprGetMatch(luaGetString(L, 1), luaGetString(L, 2), lua_tointeger(L, 3))); Result := 1; end; function lua_htmldecode(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, HTMLDecode(lua_tostring(L, 1))); + lua_pushstring(L, HTMLDecode(luaGetString(L, 1))); Result := 1; end; function lua_htmlencode(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, EscapeHTML(lua_tostring(L, 1))); + lua_pushstring(L, EscapeHTML(luaGetString(L, 1))); Result := 1; end; function lua_urldecode(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, URLDecode(lua_tostring(L, 1))); + lua_pushstring(L, URLDecode(luaGetString(L, 1))); Result := 1; end; @@ -112,7 +112,7 @@ function lua_incstr(L: Plua_State): Integer; cdecl; if lua_isinteger(L, 1) then lua_pushstring(L, IncStr(lua_tointeger(L, 1), n)) else - lua_pushstring(L, IncStr(lua_tostring(L, 1), n)); + lua_pushstring(L, IncStr(luaGetString(L, 1), n)); Result := 1; end; @@ -127,6 +127,12 @@ function lua_streamtostring(L: Plua_State): Integer; cdecl; Result := 0; end; +function lua_stringtostream(L: Plua_State): Integer; cdecl; +begin + Result := 0; + StringToStream(luaGetString(L, 1), TStream(luaGetUserData(L, 2))); +end; + function lua_round(L: Plua_State): Integer; cdecl; begin lua_pushinteger(L, round(lua_tonumber(L, 1))); @@ -145,6 +151,62 @@ function lua_getcurrenttime(L: Plua_State): Integer; cdecl; Result := 1; end; +function lua_encryptstring(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, EncryptString(luaGetString(L, 1))); + Result := 1; +end; + +function lua_decryptstring(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, DecryptString(luaGetString(L, 1))); + Result := 1; +end; + +function lua_Base64Encode(L: Plua_State): Integer; cdecl; +var + obj: TObject; +begin + Result := 0; + if lua_isstring(L, 1) then + begin + lua_pushstring(L, Base64Encode(String(luaGetString(L, 1)))); + Result := 1; + end + else + if lua_isuserdata(L, 1) then + begin + obj := TObject(luaGetUserData(L, 1)); + if obj is TStream then + begin + lua_pushboolean(L, Base64Encode(TStream(obj))); + Result := 1; + end; + end; +end; + +function lua_Base64Decode(L: Plua_State): Integer; cdecl; +var + obj: TObject; +begin + Result := 0; + if lua_isstring(L, 1) then + begin + lua_pushstring(L, Base64Decode(String(luaGetString(L, 1)))); + Result := 1; + end + else + if lua_isuserdata(L, 1) then + begin + obj := TObject(luaGetUserData(L, 1)); + if obj is TStream then + begin + lua_pushboolean(L, Base64Decode(TStream(obj))); + Result := 1; + end; + end; +end; + procedure luaBaseUnitRegister(L: Plua_State); begin luaPushFunctionGlobal(L, 'Pos', @lua_pos); @@ -162,9 +224,14 @@ procedure luaBaseUnitRegister(L: Plua_State); luaPushFunctionGlobal(L, 'URLDecode', @lua_urldecode); luaPushFunctionGlobal(L, 'IncStr', @lua_incstr); luaPushFunctionGlobal(L, 'StreamToString', @lua_streamtostring); + luaPushFunctionGlobal(L, 'StringToStream', @lua_stringtostream); luaPushFunctionGlobal(L, 'Round', @lua_round); luaPushFunctionGlobal(L, 'TrimStrings', @lua_trimstrings); luaPushFunctionGlobal(L, 'GetCurrentTime', @lua_getcurrenttime); + luaPushFunctionGlobal(L, 'EncryptString', @lua_encryptstring); + luaPushFunctionGlobal(L, 'DecryptString', @lua_decryptstring); + luaPushFunctionGlobal(L, 'Base64Encode', @lua_Base64Encode); + luaPushFunctionGlobal(L, 'Base64Decode', @lua_Base64Decode); end; end. diff --git a/baseunits/lua/LuaClass.pas b/baseunits/lua/LuaClass.pas index 59cef5d62..686a56e6c 100644 --- a/baseunits/lua/LuaClass.pas +++ b/baseunits/lua/LuaClass.pas @@ -95,7 +95,7 @@ function __index(L: Plua_State): Integer; cdecl; Exit; lua_getmetatable(L, 1); // 1 should be userdata - lua_pushstring(L, AnsiLowerCase(lua_tostring(L, 2))); // 2 should be the key + lua_pushstring(L, AnsiLowerCase(luaGetString(L, 2))); // 2 should be the key lua_rawget(L, -2); // get metatable[key] if lua_istable(L, -1) then @@ -127,7 +127,7 @@ function __newindex(L: Plua_State): Integer; cdecl; Exit(0); lua_getmetatable(L, 1); - lua_pushstring(L, AnsiLowerCase(lua_tostring(L, 2))); + lua_pushstring(L, AnsiLowerCase(luaGetString(L, 2))); lua_rawget(L, -2); if lua_istable(L, -1) then @@ -161,7 +161,7 @@ function __indexarray(L: Plua_State): Integer; cdecl; begin Result := 1; - if lua_tostring(L, 2) = '__get' then + if luaGetString(L, 2) = '__get' then begin lua_pushvalue(L, 1); Exit; @@ -177,7 +177,7 @@ function __newindexarray(L: Plua_State): Integer; cdecl; begin Result := 1; - if lua_tostring(L, 2) = '__set' then + if luaGetString(L, 2) = '__set' then begin lua_pushvalue(L, 1); Exit; @@ -438,7 +438,7 @@ function luaclass_string_get(L: Plua_State): Integer; cdecl; function luaclass_string_set(L: Plua_State): Integer; cdecl; begin Result := 0; - String(luaClassGetClosure(L)^) := lua_tostring(L, -1); + String(luaClassGetClosure(L)^) := luaGetString(L, -1); end; function luaclass_int_get(L: Plua_State): Integer; cdecl; diff --git a/baseunits/lua/LuaCriticalSection.pas b/baseunits/lua/LuaCriticalSection.pas new file mode 100644 index 000000000..36c14a826 --- /dev/null +++ b/baseunits/lua/LuaCriticalSection.pas @@ -0,0 +1,59 @@ +unit LuaCriticalSection; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, lua53; + +procedure luaCriticalSectionAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, + UserData: Integer; AutoFree: Boolean = False); + +implementation + +uses + syncobjs, LuaClass; + +type + TUserData = TCriticalSection; + +function lua_tryenter(L: Plua_State): Integer; cdecl; +begin + lua_pushboolean(L, TUserData(luaClassGetObject(L)).TryEnter); + Result := 1; +end; + +function lua_enter(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).Enter; +end; + +function lua_Leave(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).Leave; +end; + +const + methods: packed array [0..3] of luaL_Reg = ( + (name: 'TryEnter'; func: @lua_tryenter), + (name: 'Enter'; func: @lua_enter), + (name: 'Leave'; func: @lua_Leave), + (name: nil; func: nil) + ); + +procedure luaCriticalSectionAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, + UserData: Integer; AutoFree: Boolean = False); +begin + with TUserData(Obj) do + begin + luaClassAddFunction(L, MetaTable, UserData, methods); + end; +end; + +initialization + luaClassRegister(TCriticalSection, @luaCriticalSectionAddMetaTable); + +end. diff --git a/baseunits/lua/LuaCrypto.pas b/baseunits/lua/LuaCrypto.pas index ca3b65fbb..80da87429 100644 --- a/baseunits/lua/LuaCrypto.pas +++ b/baseunits/lua/LuaCrypto.pas @@ -16,25 +16,25 @@ implementation function crypto_hextostr(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, HexToStr(lua_tostring(L, 1))); + lua_pushstring(L, HexToStr(luaGetString(L, 1))); Result := 1; end; function crypto_strtohexstr(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, StrToHexStr(lua_tostring(L, 1))); + lua_pushstring(L, StrToHexStr(luaGetString(L, 1))); Result := 1; end; function crypto_md5hex(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, MD5Hex(lua_tostring(L, 1))); + lua_pushstring(L, MD5Hex(luaGetString(L, 1))); Result := 1; end; function crypto_aesdecryptcbc(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, AESDecryptCBC(lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3))); + lua_pushstring(L, AESDecryptCBC(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3))); Result := 1; end; diff --git a/baseunits/lua/LuaDuktape.pas b/baseunits/lua/LuaDuktape.pas index bb0e50f9e..4cd1165b5 100644 --- a/baseunits/lua/LuaDuktape.pas +++ b/baseunits/lua/LuaDuktape.pas @@ -15,7 +15,7 @@ implementation function lua_execjs(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, ExecJS(lua_tostring(L, 1))); + lua_pushstring(L, ExecJS(luaGetString(L, 1))); Result := 1; end; diff --git a/baseunits/lua/LuaHTTPSend.pas b/baseunits/lua/LuaHTTPSend.pas index bbccc9aee..effa35b98 100644 --- a/baseunits/lua/LuaHTTPSend.pas +++ b/baseunits/lua/LuaHTTPSend.pas @@ -13,33 +13,33 @@ procedure luaHTTPSendThreadAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, implementation uses - uBaseUnit, httpsendthread, LuaClass; + uBaseUnit, httpsendthread, LuaClass, LuaUtils; type TUserData = THTTPSendThread; function http_get(L: Plua_State): Integer; cdecl; begin - lua_pushboolean(L, TUserData(luaClassGetObject(L)).GET(lua_tostring(L, 1))); + lua_pushboolean(L, TUserData(luaClassGetObject(L)).GET(luaGetString(L, 1))); Result := 1; end; function http_post(L: Plua_State): Integer; cdecl; begin - lua_pushboolean(L, TUserData(luaClassGetObject(L)).POST(lua_tostring(L, 1), - lua_tostring(L, 2))); + lua_pushboolean(L, TUserData(luaClassGetObject(L)).POST(luaGetString(L, 1), + luaGetString(L, 2))); Result := 1; end; function http_head(L: Plua_State): Integer; cdecl; begin - lua_pushboolean(L, TUserData(luaClassGetObject(L)).head(lua_tostring(L, 1))); + lua_pushboolean(L, TUserData(luaClassGetObject(L)).head(luaGetString(L, 1))); Result := 1; end; function http_xhr(L: Plua_State): Integer; cdecl; begin - lua_pushboolean(L, TUserData(luaClassGetObject(L)).XHR(lua_tostring(L, 1))); + lua_pushboolean(L, TUserData(luaClassGetObject(L)).XHR(luaGetString(L, 1))); Result := 1; end; @@ -51,8 +51,8 @@ function http_reset(L: Plua_State): Integer; cdecl; function http_getcookies(L: Plua_State): Integer; cdecl; begin - Result := 0; - TUserData(luaClassGetObject(L)).GetCookies; + lua_pushstring(L, TUserData(luaClassGetObject(L)).GetCookies); + Result := 1; end; function http_threadterminated(L: Plua_State): Integer; cdecl; @@ -64,8 +64,8 @@ function http_threadterminated(L: Plua_State): Integer; cdecl; function http_setproxy(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).SetProxy(lua_tostring(L, 1), lua_tostring(L, 2), - lua_tostring(L, 3), lua_tostring(L, 4), lua_tostring(L, 5)); + TUserData(luaClassGetObject(L)).SetProxy(luaGetString(L, 1), luaGetString(L, 2), + luaGetString(L, 3), luaGetString(L, 4), luaGetString(L, 5)); end; function http_threadlasturl(L: Plua_State): Integer; cdecl; @@ -116,7 +116,7 @@ procedure luaHTTPSendThreadAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, luaClassAddObject(L, MetaTable, Cookies, 'Cookies'); luaClassAddStringProperty(L, MetaTable, 'MimeType', @TUserData(Obj).MimeType); luaClassAddStringProperty(L, MetaTable, 'UserAgent', @TUserData(Obj).UserAgent); - luaClassAddUserData(L, MetaTable, TUserData(Obj).Document, 'Document'); + luaClassAddObject(L, MetaTable, TUserData(Obj).Document, 'Document'); end; end; diff --git a/baseunits/lua/LuaIXQValue.pas b/baseunits/lua/LuaIXQValue.pas index 486393fb4..7c1fee665 100644 --- a/baseunits/lua/LuaIXQValue.pas +++ b/baseunits/lua/LuaIXQValue.pas @@ -21,7 +21,7 @@ procedure luaIXQValuePush(L: Plua_State; Obj: TLuaIXQValue); inline; implementation uses - LuaClass; + LuaClass, LuaUtils; type TUserData = TLuaIXQValue; @@ -42,7 +42,31 @@ function ixqvalue_tostring(L: Plua_State): Integer; cdecl; function ixqvalue_getattribute(L: Plua_State): Integer; cdecl; begin lua_pushstring(L, TUserData(luaClassGetObject(L)).FIXQValue.toNode.getAttribute( - lua_tostring(L, 1))); + luaGetString(L, 1))); + Result := 1; +end; + +function ixqvalue_getproperty(L: Plua_State): Integer; cdecl; +begin + luaIXQValuePush(L, TUserData.Create(TUserData(luaClassGetObject(L)).FIXQValue.getProperty(luaGetString(L, 1)))); + Result := 1; +end; + +function ixqvalue_innerHTML(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, TUserData(luaClassGetObject(L)).FIXQValue.toNode.innerHTML()); + Result := 1; +end; + +function ixqvalue_outerHTML(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, TUserData(luaClassGetObject(L)).FIXQValue.toNode.outerHTML()); + Result := 1; +end; + +function ixqvalue_innerText(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, TUserData(luaClassGetObject(L)).FIXQValue.toNode.innerText()); Result := 1; end; @@ -59,9 +83,13 @@ function ixqvalue_get(L: Plua_State): Integer; cdecl; end; const - methods: packed array [0..2] of luaL_Reg = ( - (name: 'GetAttribute'; func: @ixqvalue_getattribute), - (name: 'Get'; func: @ixqvalue_get), + methods: packed array [0..6] of luaL_Reg = ( + (name: 'get'; func: @ixqvalue_get), + (name: 'getAttribute'; func: @ixqvalue_getattribute), + (name: 'getProperty'; func: @ixqvalue_getproperty), + (name: 'innerHTML'; func: @ixqvalue_innerHTML), + (name: 'outerHTML'; func: @ixqvalue_outerHTML), + (name: 'innerText'; func: @ixqvalue_innerText), (name: nil; func: nil) ); props: packed array [0..2] of luaL_Reg_prop = ( diff --git a/baseunits/lua/LuaLogger.pas b/baseunits/lua/LuaLogger.pas new file mode 100644 index 000000000..2c360887c --- /dev/null +++ b/baseunits/lua/LuaLogger.pas @@ -0,0 +1,48 @@ +unit LuaLogger; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, lua53; + +procedure luaLoggerRegister(L: Plua_State); + +implementation + +uses + MultiLog, LuaClass, LuaUtils; + +function logger_send(L: Plua_State): Integer; cdecl; +begin + Result := 0; + Logger.Send(luaGetString(L, 1)); +end; + +function logger_sendwarning(L: Plua_State): Integer; cdecl; +begin + Result := 0; + Logger.SendWarning(luaGetString(L, 1)); +end; + +function logger_senderror(L: Plua_State): Integer; cdecl; +begin + Result := 0; + Logger.SendError(luaGetString(L, 1)); +end; + +const + methods: packed array [0..3] of luaL_Reg = ( + (name: 'Send'; func: @logger_send), + (name: 'SendWarning'; func: @logger_sendwarning), + (name: 'SendError'; func: @logger_senderror), + (name: nil; func: nil) + ); + +procedure luaLoggerRegister(L: Plua_State); +begin + luaClassNewLib(L, 'logger', methods); +end; + +end. diff --git a/baseunits/lua/LuaMemoryStream.pas b/baseunits/lua/LuaMemoryStream.pas new file mode 100644 index 000000000..35908209d --- /dev/null +++ b/baseunits/lua/LuaMemoryStream.pas @@ -0,0 +1,90 @@ +unit LuaMemoryStream; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, lua53; + +procedure luaMemoryStreamAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, + UserData: Integer; AutoFree: Boolean = False); + +implementation + +uses + uBaseUnit, LuaClass, LuaUtils; + +type + TUserData = TMemoryStream; + +function mem_toString(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, StreamToString(TUserData(luaClassGetObject(L)))); + Result := 1; +end; + +function mem_readAnsiString(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, TUserData(luaClassGetObject(L)).ReadAnsiString); + Result := 1; +end; + +function mem_writeAnsiString(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).WriteAnsiString(luaGetString(L, 1)); +end; + +function mem_loadFromFile(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).LoadFromFile(luaGetString(L, 1)); +end; + +function mem_saveToFile(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).SaveToFile(luaGetString(L, 1)); +end; + +function mem_getSize(L: Plua_State): Integer; cdecl; +begin + lua_pushinteger(L, TUserData(luaClassGetObject(L)).Size); + Result := 1; +end; + +function mem_setSize(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).Size := lua_tointeger(L, 1); +end; + +const + methods: packed array [0..5] of luaL_Reg = ( + (name: 'ToString'; func: @mem_tostring), + (name: 'ReadAnsiString'; func: @mem_readAnsiString), + (name: 'WriteAnsiString'; func: @mem_writeAnsiString), + (name: 'LoadFromFile'; func: @mem_loadFromFile), + (name: 'SaveToFile'; func: @mem_saveToFile), + (name: nil; func: nil) + ); + props: packed array[0..1] of luaL_Reg_prop = ( + (name: 'Size'; funcget: @mem_getSize; funcset: @mem_setSize), + (name: nil; funcget: nil; funcset: nil) + ); + +procedure luaMemoryStreamAddMetaTable(L: Plua_State; Obj: Pointer; MetaTable, + UserData: Integer; AutoFree: Boolean); +begin + with TUserData(Obj) do + begin + luaClassAddFunction(L, MetaTable, UserData, methods); + luaClassAddProperty(L, MetaTable, UserData, props); + end; +end; + +initialization + luaClassRegister(TMemoryStream, @luaMemoryStreamAddMetaTable); + +end. diff --git a/baseunits/lua/LuaPCRE2.pas b/baseunits/lua/LuaPCRE2.pas new file mode 100644 index 000000000..e1de1c0b6 --- /dev/null +++ b/baseunits/lua/LuaPCRE2.pas @@ -0,0 +1,44 @@ +unit LuaPCRE2; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, lua53; + +procedure luaPCRE2Register(L: Plua_State); + +implementation + +uses + pcre2, LuaClass, LuaUtils; + +function re_match(L: Plua_State): Integer; cdecl; +begin + lua_pushboolean(L, PCRE2Match(luaGetString(L, 1), luaGetString(L, 2))); + Result := 1; +end; + +function re_replace(L: Plua_State): Integer; cdecl; +begin + if lua_gettop(L)=4 then + lua_pushstring(L, PCRE2Replace(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3),lua_toboolean(L,4))) + else + lua_pushstring(L, PCRE2Replace(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3))); + Result := 1; +end; + +const + methods: packed array [0..2] of luaL_Reg = ( + (name: 'match'; func: @re_match), + (name: 'replace'; func: @re_replace), + (name: nil; func: nil) + ); + +procedure luaPCRE2Register(L: Plua_State); +begin + luaClassNewLib(L,'re',methods); +end; + +end. diff --git a/baseunits/lua/LuaRegExpr.pas b/baseunits/lua/LuaRegExpr.pas index ec3910ff8..c7680df9d 100644 --- a/baseunits/lua/LuaRegExpr.pas +++ b/baseunits/lua/LuaRegExpr.pas @@ -16,13 +16,13 @@ implementation function re_exec(L: Plua_State): Integer; cdecl; begin - lua_pushboolean(L, ExecRegExpr(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushboolean(L, ExecRegExpr(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function re_replace(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, ReplaceRegExpr(lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3), True)); + lua_pushstring(L, ReplaceRegExpr(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3), True)); Result := 1; end; diff --git a/baseunits/lua/LuaStrings.pas b/baseunits/lua/LuaStrings.pas index 23b52c7dc..32391fd5d 100644 --- a/baseunits/lua/LuaStrings.pas +++ b/baseunits/lua/LuaStrings.pas @@ -29,7 +29,7 @@ function strings_create(L: Plua_State): Integer; cdecl; function strings_loadfromfile(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).LoadFromFile(lua_tostring(L, 1)); + TUserData(luaClassGetObject(L)).LoadFromFile(luaGetString(L, 1)); end; function strings_loadfromstream(L: Plua_State): Integer; cdecl; @@ -41,7 +41,7 @@ function strings_loadfromstream(L: Plua_State): Integer; cdecl; function strings_settext(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).Text := lua_tostring(L, 1); + TUserData(luaClassGetObject(L)).Text := luaGetString(L, 1); end; function strings_gettext(L: Plua_State): Integer; cdecl; @@ -52,7 +52,7 @@ function strings_gettext(L: Plua_State): Integer; cdecl; function strings_setcommatext(L: Plua_State): Integer; cdecl; begin - TUserData(luaClassGetObject(L)).CommaText := lua_tostring(L, 1); + TUserData(luaClassGetObject(L)).CommaText := luaGetString(L, 1); Result := 0; end; @@ -64,13 +64,13 @@ function strings_getcommatext(L: Plua_State): Integer; cdecl; function strings_add(L: Plua_State): Integer; cdecl; begin - TUserData(luaClassGetObject(L)).Add(lua_tostring(L, 1)); + TUserData(luaClassGetObject(L)).Add(luaGetString(L, 1)); Result := 0; end; function strings_addtext(L: Plua_State): Integer; cdecl; begin - TUserData(luaClassGetObject(L)).AddText(lua_tostring(L, 1)); + TUserData(luaClassGetObject(L)).AddText(luaGetString(L, 1)); Result := 0; end; @@ -84,7 +84,32 @@ function strings_get(L: Plua_State): Integer; cdecl; function strings_set(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).Strings[lua_tointeger(L, 1)] := lua_tostring(L, 2); + TUserData(luaClassGetObject(L)).Strings[lua_tointeger(L, 1)] := luaGetString(L, 2); +end; + +function strings_getdelimitedtext(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, TUserData(luaClassGetObject(L)).DelimitedText); + Result := 1; +end; + +function strings_setdelimitedtext(L: Plua_State): Integer; cdecl; +begin + TUserData(luaClassGetObject(L)).DelimitedText := luaGetString(L, 1); + Result := 0; +end; + +function strings_getdelimiter(L: Plua_State): Integer; cdecl; +begin + lua_pushstring(L, PAnsiChar( + String(TUserData(luaClassGetObject(L)).Delimiter))); + Result := 1; +end; + +function strings_setdelimiter(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TUserData(luaClassGetObject(L)).Delimiter := String(luaGetString(L, 1))[1]; end; function strings_namevalueseparatorget(L: Plua_State): Integer; cdecl; @@ -97,20 +122,20 @@ function strings_namevalueseparatorget(L: Plua_State): Integer; cdecl; function strings_namevalueseparatorset(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).NameValueSeparator := String(lua_tostring(L, 1))[1]; + TUserData(luaClassGetObject(L)).NameValueSeparator := String(luaGetString(L, 1))[1]; end; function strings_valuesget(L: Plua_State): Integer; cdecl; begin lua_pushstring(L, PAnsiChar( - TUserData(luaClassGetObject(L)).Values[lua_tostring(L, 1)])); + TUserData(luaClassGetObject(L)).Values[luaGetString(L, 1)])); Result := 1; end; function strings_valuesset(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).Values[lua_tostring(L, 1)] := lua_tostring(L, 2); + TUserData(luaClassGetObject(L)).Values[luaGetString(L, 1)] := luaGetString(L, 2); end; function strings_getcount(L: Plua_State): Integer; cdecl; @@ -139,13 +164,13 @@ function strings_delete(L: Plua_State): Integer; cdecl; function strings_indexof(L: Plua_State): Integer; cdecl; begin - lua_pushinteger(L, TUserData(luaClassGetObject(L)).IndexOf(lua_tostring(L, 1))); + lua_pushinteger(L, TUserData(luaClassGetObject(L)).IndexOf(luaGetString(L, 1))); Result := 1; end; function strings_indexofname(L: Plua_State): Integer; cdecl; begin - lua_pushinteger(L, TUserData(luaClassGetObject(L)).IndexOfName(lua_tostring(L, 1))); + lua_pushinteger(L, TUserData(luaClassGetObject(L)).IndexOfName(luaGetString(L, 1))); Result := 1; end; @@ -172,12 +197,13 @@ function strings_indexofname(L: Plua_State): Integer; cdecl; (name: 'IndexOfName'; func: @strings_indexofname), (name: nil; func: nil) ); - props: packed array[0..4] of lual_Reg_prop = ( + props: packed array[0..6] of lual_Reg_prop = ( (name: 'Text'; funcget: @strings_gettext; funcset: @strings_settext), (name: 'CommaText'; funcget: @strings_getcommatext; funcset: @strings_setcommatext), (name: 'Count'; funcget: @strings_getcount; funcset: nil), - (name: 'NameValueSeparator'; funcget: @strings_namevalueseparatorget; - funcset: @strings_namevalueseparatorset), + (name: 'DelimitedText'; funcget: @strings_getdelimitedtext; funcset: @strings_setdelimitedtext), + (name: 'Delimiter'; funcget: @strings_getdelimiter; funcset: @strings_setdelimiter), + (name: 'NameValueSeparator'; funcget: @strings_namevalueseparatorget; funcset: @strings_namevalueseparatorset), (name: nil; funcget: nil; funcset: nil) ); arrprops: packed array[0..2] of lual_Reg_prop = ( diff --git a/baseunits/lua/LuaStringsStorage.pas b/baseunits/lua/LuaStringsStorage.pas index 23949907d..7d1fdf82b 100644 --- a/baseunits/lua/LuaStringsStorage.pas +++ b/baseunits/lua/LuaStringsStorage.pas @@ -38,7 +38,7 @@ procedure luaStringsStorageAddMetaTable(L: Plua_State; Obj: Pointer; implementation -uses LuaClass; +uses LuaClass, LuaUtils; { TStringsStorage } @@ -54,7 +54,12 @@ function TStringsStorage.GetText: String; procedure TStringsStorage.SetText(AValue: String); begin - FStrings.Text := AValue; + EnterCriticalsection(FCS); + try + FStrings.Text := AValue; + finally + LeaveCriticalsection(FCS); + end; end; procedure TStringsStorage.SetValues(const AName: String; AValue: String); @@ -115,20 +120,20 @@ function strings_destroy(L: Plua_State): Integer; cdecl; function strings_getvalue(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, TUserData(luaClassGetObject(L)).GetValues(lua_tostring(L, 1))); + lua_pushstring(L, TUserData(luaClassGetObject(L)).GetValues(luaGetString(L, 1))); Result := 1; end; function strings_setvalue(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).SetValues(lua_tostring(L, 1), lua_tostring(L, 2)); + TUserData(luaClassGetObject(L)).SetValues(luaGetString(L, 1), luaGetString(L, 2)); end; function strings_remove(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).Remove(lua_tostring(L, 1)); + TUserData(luaClassGetObject(L)).Remove(luaGetString(L, 1)); end; function strings_gettext(L: Plua_State): Integer; cdecl; @@ -140,7 +145,7 @@ function strings_gettext(L: Plua_State): Integer; cdecl; function strings_settext(L: Plua_State): Integer; cdecl; begin Result := 0; - TUserData(luaClassGetObject(L)).Text := lua_tostring(L, 1); + TUserData(luaClassGetObject(L)).Text := luaGetString(L, 1); end; const diff --git a/baseunits/lua/LuaSynaCode.pas b/baseunits/lua/LuaSynaCode.pas index e41284791..ea2f61951 100644 --- a/baseunits/lua/LuaSynaCode.pas +++ b/baseunits/lua/LuaSynaCode.pas @@ -16,97 +16,97 @@ implementation function lua_decodeurl(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, DecodeURL(lua_tostring(L, 1))); + lua_pushstring(L, DecodeURL(luaGetString(L, 1))); Result := 1; end; function lua_encodeurl(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, EncodeURL(lua_tostring(L, 1))); + lua_pushstring(L, EncodeURL(luaGetString(L, 1))); Result := 1; end; function lua_decodeuu(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, DecodeUU(lua_tostring(L, 1))); + lua_pushstring(L, DecodeUU(luaGetString(L, 1))); Result := 1; end; function lua_encodeuu(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, EncodeUU(lua_tostring(L, 1))); + lua_pushstring(L, EncodeUU(luaGetString(L, 1))); Result := 1; end; function lua_encodeurlelement(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, EncodeURLElement(lua_tostring(L, 1))); + lua_pushstring(L, EncodeURLElement(luaGetString(L, 1))); Result := 1; end; function lua_decodebase64(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, DecodeBase64(lua_tostring(L, 1))); + lua_pushstring(L, DecodeBase64(luaGetString(L, 1))); Result := 1; end; function lua_encodebase64(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, EncodeBase64(lua_tostring(L, 1))); + lua_pushstring(L, EncodeBase64(luaGetString(L, 1))); Result := 1; end; function lua_crc16(L: Plua_State): Integer; cdecl; begin - lua_pushinteger(L, Crc16(lua_tostring(L, 1))); + lua_pushinteger(L, Crc16(luaGetString(L, 1))); Result := 1; end; function lua_crc32(L: Plua_State): Integer; cdecl; begin - lua_pushinteger(L, Crc32(lua_tostring(L, 1))); + lua_pushinteger(L, Crc32(luaGetString(L, 1))); Result := 1; end; function lua_md4(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, MD4(lua_tostring(L, 1))); + lua_pushstring(L, MD4(luaGetString(L, 1))); Result := 1; end; function lua_md5(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, MD5(lua_tostring(L, 1))); + lua_pushstring(L, MD5(luaGetString(L, 1))); Result := 1; end; function lua_hmac_md5(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, HMAC_MD5(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, HMAC_MD5(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function lua_md5longhash(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, MD5LongHash(lua_tostring(L, 1), lua_tointeger(L, 2))); + lua_pushstring(L, MD5LongHash(luaGetString(L, 1), lua_tointeger(L, 2))); Result := 1; end; function lua_sha1(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, SHA1(lua_tostring(L, 1))); + lua_pushstring(L, SHA1(luaGetString(L, 1))); Result := 1; end; function lua_hmac_sha1(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, HMAC_SHA1(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, HMAC_SHA1(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function lua_sha1longhash(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, SHA1LongHash(lua_tostring(L, 1), lua_tointeger(L, 2))); + lua_pushstring(L, SHA1LongHash(luaGetString(L, 1), lua_tointeger(L, 2))); Result := 1; end; diff --git a/baseunits/lua/LuaSynaUtil.pas b/baseunits/lua/LuaSynaUtil.pas index 5e6917940..5f6eea237 100644 --- a/baseunits/lua/LuaSynaUtil.pas +++ b/baseunits/lua/LuaSynaUtil.pas @@ -16,25 +16,25 @@ implementation function lua_getbetween(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, GetBetween(lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3))); + lua_pushstring(L, GetBetween(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3))); Result := 1; end; function lua_separateleft(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, SeparateLeft(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, SeparateLeft(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function lua_separateright(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, SeparateRight(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, SeparateRight(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end; function lua_replacestring(L: Plua_State): Integer; cdecl; begin - lua_pushstring(L, ReplaceString(lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3))); + lua_pushstring(L, ReplaceString(luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3))); Result := 1; end; diff --git a/baseunits/lua/LuaUtils.pas b/baseunits/lua/LuaUtils.pas index 61fe06d39..25b64f1a4 100644 --- a/baseunits/lua/LuaUtils.pas +++ b/baseunits/lua/LuaUtils.pas @@ -28,6 +28,7 @@ procedure luaPushBooleanGlobal(L: Plua_State; Name: PAnsiChar; B: Boolean); procedure luaPushUserData(L: Plua_State; U: Pointer); overload; inline; procedure luaPushUserData(L: Plua_State; U: Pointer; var UIndex: Integer); overload; inline; function luaGetUserData(L: Plua_State; idx: Integer): Pointer; inline; +function luaGetString(L: Plua_State; idx: Integer): String; inline; function LuaToString(L: Plua_State; Idx: Integer): String; function LuaStackToString(L: Plua_State): String; @@ -130,13 +131,21 @@ function luaGetUserData(L: Plua_State; idx: Integer): Pointer; Result := PPointer(lua_touserdata(L, idx))^; end; +function luaGetString(L: Plua_State; idx: Integer): String; +var + slen: size_t; +begin + Result := lua_tolstring(L, idx, @slen); + SetLength(Result, slen); +end; + function LuaToString(L: Plua_State; Idx: Integer): String; begin if lua_isuserdata(L, Idx) then Result := 'userdata: ' + hexStr(lua_touserdata(L, Idx)) else if lua_isstring(L, Idx) then - Result := 'string: ' + lua_tostring(L, Idx) + Result := 'string: ' + luaGetString(L, Idx) else if lua_isinteger(L, Idx) then Result := 'integer: ' + IntToStr(lua_tointeger(L, Idx)) diff --git a/baseunits/lua/LuaWebsiteModules.pas b/baseunits/lua/LuaWebsiteModules.pas index 65bbe4e73..831e8a6c1 100644 --- a/baseunits/lua/LuaWebsiteModules.pas +++ b/baseunits/lua/LuaWebsiteModules.pas @@ -5,7 +5,7 @@ interface uses - Classes, SysUtils, fgl, lua53, LuaStringsStorage, WebsiteModules; + Classes, SysUtils, fgl, lua53, LuaStringsStorage, WebsiteModules, syncobjs; type TLuaWebsiteModulesContainer = class; @@ -68,11 +68,40 @@ TLuaWebsiteModulesContainer = class TLuaWebsiteModulesManager = class public Containers: TLuaWebsiteModulesContainers; - TempModuleList: TLuaWebsiteModules; constructor Create; destructor Destroy; override; end; + { TLuaWebsiteModulesLoader } + + TLuaWebsiteModulesLoader = class + private + FFileList:TStringList; + FThreadCount, + FFileListIndex:Integer; + FFileListGuardian, + FMainGuardian:TCriticalSection; + protected + function GetFileName:String; + public + procedure ScanAndLoadFiles; + constructor Create; + destructor Destroy; override; + end; + + { TLuaWebsiteModulesLoaderThread } + + TLuaWebsiteModulesLoaderThread = class(TThread) + private + FOwner: TLuaWebsiteModulesLoader; + protected + procedure LoadLuaWebsiteModule(const AFileName:String); + procedure Execute; override; + public + constructor Create(const AOwner: TLuaWebsiteModulesLoader); + destructor Destroy; override; + end; + TOptionItem = class Caption: String; end; @@ -107,8 +136,11 @@ implementation uses FMDOptions, FileUtil, MultiLog, LuaClass, LuaBase, LuaMangaInfo, LuaHTTPSend, - LuaXQuery, LuaUtils, LuaDownloadTask, LuaUpdateListManager, LuaStrings, uData, - uDownloadsManager, xquery, httpsendthread, FMDVars; + LuaXQuery, LuaUtils, LuaDownloadTask, LuaUpdateListManager, LuaStrings, + LuaCriticalSection, uData, uDownloadsManager, xquery, httpsendthread, FMDVars; + +threadvar + TempModules:TLuaWebsiteModules; function DoBeforeUpdateList(const Module: TModuleContainer): Boolean; var @@ -127,7 +159,7 @@ function DoBeforeUpdateList(const Module: TModuleContainer): Boolean; Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -150,7 +182,7 @@ function DoAfterUpdateList(const Module: TModuleContainer): Boolean; Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -180,7 +212,7 @@ function DoGetDirectoryPageNumber(const MangaInfo: TMangaInformation; Page := lua_tointeger(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -210,7 +242,7 @@ function DoGetNameAndLink(const MangaInfo: TMangaInformation; Result := lua_tointeger(L, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -236,7 +268,7 @@ function DoGetInfo(const MangaInfo: TMangaInformation; const AURL: String; Result := lua_tointeger(L, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -259,7 +291,7 @@ function DoTaskStart(const Task: TTaskContainer; const Module: TModuleContainer) Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -285,7 +317,7 @@ function DoGetPageNumber(const DownloadThread: TDownloadThread; Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -312,7 +344,7 @@ function DoGetImageURL(const DownloadThread: TDownloadThread; const AURL: String Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -339,7 +371,7 @@ function DoBeforeDownloadImage(const DownloadThread: TDownloadThread; Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -366,7 +398,7 @@ function DoDownloadImage(const DownloadThread: TDownloadThread; Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -389,10 +421,10 @@ function DoSaveImage(const AHTTP: THTTPSendThread; const APath, AName: String; LuaDoMe(l); LuaCallFunction(l, OnSaveImage); - Result := lua_tostring(l, -1); + Result := luaGetString(L, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -415,7 +447,7 @@ function DoAfterImageSaved(const AFilename: String; const Module: TModuleContain Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; @@ -438,13 +470,26 @@ function DoLogin(const AHTTP: THTTPSendThread; const Module: TModuleContainer): Result := lua_toboolean(l, -1); except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(l, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; lua_close(l); end; end; -function LoadLuaToWebsiteModules(AFilename: String): Boolean; +procedure ScanLuaWebsiteModulesFile; +begin + with TLuaWebsiteModulesLoader.Create do + try + ScanAndLoadFiles; + finally + Free; + end; +end; + +{ TLuaWebsiteModulesLoaderThread } + +procedure TLuaWebsiteModulesLoaderThread.LoadLuaWebsiteModule( + const AFileName: String); var l: Plua_State; c: TLuaWebsiteModulesContainer; @@ -452,12 +497,11 @@ function LoadLuaToWebsiteModules(AFilename: String): Boolean; i: Integer; s: String; begin - Result := False; - Logger.Send('Load lua website module', AFilename); + //Logger.Send('Load lua website module', AFilename); try l := LuaNewBaseState; try - m := LuaDumpFileToStream(l, AFilename); + m := LuaDumpFileToStream(l, AFileName); if m <> nil then begin i := lua_pcall(l, 0, 0, 0); @@ -467,26 +511,28 @@ function LoadLuaToWebsiteModules(AFilename: String): Boolean; end; except on E: Exception do - Logger.SendError(E.Message + ': ' + lua_tostring(L, -1)); + Logger.SendError(E.Message + ': ' + luaGetString(L, -1)); end; finally lua_close(l); end; - if LuaWebsiteModulesManager.TempModuleList.Count <> 0 then + if TempModules.Count <> 0 then with LuaWebsiteModulesManager do begin c := TLuaWebsiteModulesContainer.Create; - c.FileName := AFilename; + c.FileName := AFileName; c.ByteCode := m; m := nil; s := ''; + FOwner.FMainGuardian.Enter; Containers.Add(c); - for i := 0 to TempModuleList.Count - 1 do - with TempModuleList[i] do + FOwner.FMainGuardian.Leave; + for i := 0 to TempModules.Count - 1 do + with TempModules[i] do begin s += Module.Website + ', '; - c.Modules.Add(TempModuleList[i]); + c.Modules.Add(TempModules[i]); Container := c; if OnBeforeUpdateList <> '' then Module.OnBeforeUpdateList := @DoBeforeUpdateList; @@ -515,47 +561,97 @@ function LoadLuaToWebsiteModules(AFilename: String): Boolean; if OnLogin <> '' then Module.OnLogin := @DoLogin; end; - TempModuleList.Clear; + TempModules.Clear; SetLength(s, Length(s) - 2); - Logger.Send('Loaded modules from ' + ExtractFileName(AFilename), s); + //Logger.Send('Loaded modules from ' + ExtractFileName(AFilename), s); s := ''; end; if m <> nil then m.Free; end; -procedure ScanLuaWebsiteModulesFile; +procedure TLuaWebsiteModulesLoaderThread.Execute; var - d: String; - f: TStringList; - i: Integer; + f:String; begin - d := LUA_WEBSITEMODULE_FOLDER; + TempModules:=TLuaWebsiteModules.Create; try - f := FindAllFiles(d, '*.lua;*.luac', False, faAnyFile); - if f.Count > 0 then - for i := 0 to f.Count - 1 do - LoadLuaToWebsiteModules(f[i]); + f:=FOwner.GetFileName; + while f<>'' do begin + LoadLuaWebsiteModule(f); + f:=FOwner.GetFileName; + end; finally - f.Free; + TempModules.Free; end; end; +constructor TLuaWebsiteModulesLoaderThread.Create( + const AOwner: TLuaWebsiteModulesLoader); +begin + FOwner:=AOwner; + FOwner.FThreadCount:=InterLockedIncrement(FOwner.FThreadCount); + FreeOnTerminate:=True; + inherited Create(False); +end; + +destructor TLuaWebsiteModulesLoaderThread.Destroy; +begin + FOwner.FThreadCount:=InterLockedDecrement(FOwner.FThreadCount); + inherited Destroy; +end; + +{ TLuaWebsiteModulesLoader } + +function TLuaWebsiteModulesLoader.GetFileName: String; +begin + if FFileListIndex>FFileList.Count-1 then Exit(''); + FFileListGuardian.Enter; + Result:=FFileList[FFileListIndex]; + Inc(FFileListIndex); + FFileListGuardian.Leave; +end; + +procedure TLuaWebsiteModulesLoader.ScanAndLoadFiles; +var + i: Integer; +begin + FindAllFiles(FFileList, LUA_WEBSITEMODULE_FOLDER, '*.lua;*.luac', False, faAnyFile); + if FFileList.Count=0 then Exit; + for i:=1 to GetCPUCount do + TLuaWebsiteModulesLoaderThread.Create(Self); + while FThreadCount<>0 do + Sleep(100); +end; + +constructor TLuaWebsiteModulesLoader.Create; +begin + FFileList:=TStringList.Create; + FThreadCount:=0; + FFileListIndex:=0; + FFileListGuardian:=TCriticalSection.Create; + FMainGuardian:=TCriticalSection.Create; +end; + +destructor TLuaWebsiteModulesLoader.Destroy; +begin + FFileList.Free; + FFileListGuardian.Free; + FMainGuardian.Free; + inherited Destroy; +end; + { TLuaWebsiteModulesManager } constructor TLuaWebsiteModulesManager.Create; begin Containers := TLuaWebsiteModulesContainers.Create; - TempModuleList := TLuaWebsiteModules.Create; end; destructor TLuaWebsiteModulesManager.Destroy; var i: Integer; begin - for i := 0 to TempModuleList.Count - 1 do - TempModuleList[i].Free; - TempModuleList.Free; for i := 0 to Containers.Count - 1 do Containers[i].Free; Containers.Free; @@ -587,7 +683,7 @@ destructor TLuaWebsiteModulesContainer.Destroy; constructor TLuaWebsiteModule.Create; begin - LuaWebsiteModulesManager.TempModuleList.Add(Self); + TempModules.Add(Self); Storage := TStringsStorage.Create; Options := TStringList.Create; Options.OwnsObjects := True; @@ -702,28 +798,28 @@ function lua_addoptioncheckbox(L: Plua_State): Integer; cdecl; begin Result := 0; TLuaWebsiteModule(luaClassGetObject(L)).AddOptionCheckBox( - lua_tostring(L, 1), lua_tostring(L, 2), lua_toboolean(L, 3)); + luaGetString(L, 1), luaGetString(L, 2), lua_toboolean(L, 3)); end; function lua_addoptionedit(L: Plua_State): Integer; cdecl; begin Result := 0; TLuaWebsiteModule(luaClassGetObject(L)).AddOptionEdit( - lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3)); + luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3)); end; function lua_addoptionspinedit(L: Plua_State): Integer; cdecl; begin Result := 0; TLuaWebsiteModule(luaClassGetObject(L)).AddOptionSpinEdit( - lua_tostring(L, 1), lua_tostring(L, 2), lua_tointeger(L, 3)); + luaGetString(L, 1), luaGetString(L, 2), lua_tointeger(L, 3)); end; function lua_addoptioncombobox(L: Plua_State): Integer; cdecl; begin Result := 0; TLuaWebsiteModule(luaClassGetObject(L)).AddOptionComboBox( - lua_tostring(L, 1), lua_tostring(L, 2), lua_tostring(L, 3), lua_tointeger(L, 4)); + luaGetString(L, 1), luaGetString(L, 2), luaGetString(L, 3), lua_tointeger(L, 4)); end; function lua_gettotaldirectory(L: Plua_State): Integer; cdecl; @@ -738,6 +834,12 @@ function lua_settotaldirectory(L: Plua_State): Integer; cdecl; TLuaWebsiteModule(luaClassGetObject(L)).Module.TotalDirectory := lua_tointeger(L, 1); end; +function lua_clearcookies(L: Plua_State): Integer; cdecl; +begin + Result := 0; + TLuaWebsiteModule(luaClassGetObject(L)).Module.CookieManager.Clear; +end; + function lua_getoption(L: Plua_State): Integer; cdecl; var m: TLuaWebsiteModule; @@ -745,7 +847,7 @@ function lua_getoption(L: Plua_State): Integer; cdecl; o: TObject; begin m := TLuaWebsiteModule(luaClassGetObject(L)); - i:=m.Options.IndexOf(lua_tostring(L, 1)); + i:=m.Options.IndexOf(luaGetString(L, 1)); Result := 1; if i = -1 then lua_pushnil(L) @@ -781,11 +883,12 @@ function lua_setaccountsupport(L: Plua_State): Integer; cdecl; end; const - methods: packed array [0..5] of luaL_Reg = ( + methods: packed array [0..6] of luaL_Reg = ( (name: 'AddOptionCheckBox'; func: @lua_addoptioncheckbox), (name: 'AddOptionEdit'; func: @lua_addoptionedit), (name: 'AddOptionSpinEdit'; func: @lua_addoptionspinedit), (name: 'AddOptionCombobox'; func: @lua_addoptioncombobox), + (name: 'ClearCookies'; func: @lua_clearcookies), (name: 'GetOption'; func: @lua_getoption), (name: nil; func: nil) ); @@ -798,8 +901,8 @@ procedure luaWebsiteModuleAccountAddMetaTable(L: Plua_State; Obj: Pointer; luaClassAddBooleanProperty(L, MetaTable, 'Enabled', @Enabled); luaClassAddStringProperty(L, MetaTable, 'Username', @Username); luaClassAddStringProperty(L, MetaTable, 'Password', @Password); - luaClassAddStringProperty(L, MetaTable, 'Cookies', @Cookies); luaClassAddIntegerProperty(L, MetaTable, 'Status', @Status); + luaClassAddObject(L, MetaTable, Guardian, 'Guardian', @luaCriticalSectionAddMetaTable); end; end; @@ -808,6 +911,7 @@ procedure luaWebsiteModuleAddMetaTable(L: Plua_State; Obj: Pointer; begin with TLuaWebsiteModule(Obj) do begin + luaClassAddObject(L, MetaTable, Module.Guardian, 'Guardian', @luaCriticalSectionAddMetaTable); luaClassAddStringProperty(L, MetaTable, 'Website', @Module.Website); luaClassAddStringProperty(L, MetaTable, 'RootURL', @Module.RootURL); luaClassAddStringProperty(L, MetaTable, 'Category', @Module.Category); diff --git a/baseunits/lua/LuaXQuery.pas b/baseunits/lua/LuaXQuery.pas index 2d53ce9af..e33629ce6 100644 --- a/baseunits/lua/LuaXQuery.pas +++ b/baseunits/lua/LuaXQuery.pas @@ -23,7 +23,7 @@ function xquery_create(L: Plua_State): Integer; cdecl; if lua_gettop(L) = 1 then begin if lua_isstring(L, 1) then - luaXQueryPush(L, TXQueryEngineHTML.Create(lua_tostring(L, 1)), '', True) + luaXQueryPush(L, TXQueryEngineHTML.Create(luaGetString(L, 1)), '', True) else if lua_isuserdata(L, 1) then luaXQueryPush(L, TXQueryEngineHTML.Create(TStream(luaGetUserData(L, 1))), @@ -41,7 +41,7 @@ function xquery_parsehtml(L: Plua_State): Integer; cdecl; Result := 0; u := TUserData(luaClassGetObject(L)); if lua_isstring(L, 1) then - u.ParseHTML(lua_tostring(L, 1)) + u.ParseHTML(luaGetString(L, 1)) else if lua_isuserdata(L, 1) then u.ParseHTML(TStream(luaGetUserData(L, 1))); @@ -54,23 +54,44 @@ function xquery_xpath(L: Plua_State): Integer; cdecl; begin u := TUserData(luaClassGetObject(L)); if lua_gettop(L) = 2 then - x := u.XPath(lua_tostring(L, 1), TLuaIXQValue(luaGetUserData(L, 2)).FIXQValue) + x := u.XPath(luaGetString(L, 1), TLuaIXQValue(luaGetUserData(L, 2)).FIXQValue) else - x := u.XPath(lua_tostring(L, 1)); + x := u.XPath(luaGetString(L, 1)); luaIXQValuePush(L, TLuaIXQValue.Create(x)); Result := 1; end; +function xquery_xpathi(L: Plua_State): Integer; cdecl; +var + u: TUserData; + x: IXQValue; + t, i: Integer; +begin + u := TUserData(luaClassGetObject(L)); + if lua_gettop(L) = 2 then + x := u.XPath(luaGetString(L, 1), TLuaIXQValue(luaGetUserData(L, 2)).FIXQValue) + else + x := u.XPath(luaGetString(L, 1)); + lua_newtable(L); + t := lua_gettop(L); + for i := 1 to x.Count do + begin + luaIXQValuePush(L, TLuaIXQValue.Create(x.get(i))); + lua_rawseti(L, t, i); + end; + Result := 1; +end; + function xquery_xpathstring(L: Plua_State): Integer; cdecl; var u: TUserData; begin u := TUserData(luaClassGetObject(L)); if lua_gettop(L) = 2 then - lua_pushstring(L, u.XPathString(lua_tostring(L, 1), + lua_pushstring(L, u.XPathString(luaGetString(L, 1), TLuaIXQValue(luaGetUserData(L, 2)).FIXQValue)) else - lua_pushstring(L, u.XPathString(lua_tostring(L, 1))); + lua_pushstring(L, u.XPathString(luaGetString(L, 1))); Result := 1; end; @@ -82,33 +103,33 @@ function xquery_xpathstringall(L: Plua_State): Integer; cdecl; u := TUserData(luaClassGetObject(L)); case lua_gettop(L) of 1: begin - lua_pushstring(L, u.XPathStringAll(lua_tostring(L, 1))); + lua_pushstring(L, u.XPathStringAll(luaGetString(L, 1))); Result := 1; end; 2: begin if lua_isstring(L, 2) then begin - lua_pushstring(L, u.XPathStringAll(lua_tostring(L, 1), lua_tostring(L, 2))); + lua_pushstring(L, u.XPathStringAll(luaGetString(L, 1), luaGetString(L, 2))); Result := 1; end else if lua_isuserdata(L, 2) then begin - u.XPathStringAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2))); + u.XPathStringAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2))); Result := 0; end; end; 3: begin if lua_isstring(L, 2) then begin - lua_pushstring(L, u.XPathStringAll(lua_tostring(L, 1), lua_tostring(L, 2), + lua_pushstring(L, u.XPathStringAll(luaGetString(L, 1), luaGetString(L, 2), TLuaIXQValue(luaGetUserData(L, 3)).FIXQValue)); Result := 1; end else if lua_isuserdata(L, 2) then begin - u.XPathStringAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2)), + u.XPathStringAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2)), TLuaIXQValue(luaGetUserData(L, 3)).FIXQValue); Result := 0; end; @@ -122,9 +143,9 @@ function xquery_xpathhrefall(L: Plua_State): Integer; cdecl; begin u := TUserData(luaClassGetObject(L)); case lua_gettop(L) of - 3: u.XPathHREFAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2)), + 3: u.XPathHREFAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2)), TStrings(luaGetUserData(L, 3))); - 4: u.XPathHREFAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2)), + 4: u.XPathHREFAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2)), TStrings(luaGetUserData(L, 3)), TLuaIXQValue(luaGetUserData(L, 4)).FIXQValue) end; Result := 0; @@ -136,9 +157,9 @@ function xquery_xpathhreftitleall(L: Plua_State): Integer; cdecl; begin u := TUserData(luaClassGetObject(L)); case lua_gettop(L) of - 3: u.XPathHREFtitleAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2)), + 3: u.XPathHREFtitleAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2)), TStrings(luaGetUserData(L, 3))); - 4: u.XPathHREFtitleAll(lua_tostring(L, 1), TStrings(luaGetUserData(L, 2)), + 4: u.XPathHREFtitleAll(luaGetString(L, 1), TStrings(luaGetUserData(L, 2)), TStrings(luaGetUserData(L, 3)), TLuaIXQValue(luaGetUserData(L, 4)).FIXQValue) end; Result := 0; @@ -150,10 +171,10 @@ function xquery_xpathcount(L: Plua_State): Integer; cdecl; begin u := TUserData(luaClassGetObject(L)); if lua_gettop(L) = 2 then - lua_pushinteger(L, u.XPathCount(lua_tostring(L, 1), + lua_pushinteger(L, u.XPathCount(luaGetString(L, 1), TLuaIXQValue(luaGetUserData(L, 2)).FIXQValue)) else - lua_pushinteger(L, u.XPathCount(lua_tostring(L, 1))); + lua_pushinteger(L, u.XPathCount(luaGetString(L, 1))); Result := 1; end; @@ -163,9 +184,10 @@ function xquery_xpathcount(L: Plua_State): Integer; cdecl; (name: 'Create'; func: @xquery_create), (name: nil; func: nil) ); - methods: packed array [0..7] of luaL_Reg = ( + methods: packed array [0..8] of luaL_Reg = ( (name: 'ParseHTML'; func: @xquery_parsehtml), (name: 'XPath'; func: @xquery_xpath), + (name: 'XPathI'; func: @xquery_xpathi), (name: 'XPathString'; func: @xquery_xpathstring), (name: 'XpathStringAll'; func: @xquery_xpathstringall), (name: 'XpathHREFAll'; func: @xquery_xpathhrefall), diff --git a/baseunits/modules/AcademyVN.pas b/baseunits/modules/AcademyVN.pas deleted file mode 100644 index 61b80afb6..000000000 --- a/baseunits/modules/AcademyVN.pas +++ /dev/null @@ -1,134 +0,0 @@ -unit AcademyVN; - -{$mode objfpc}{$H+} - -interface - -uses - Classes, SysUtils, WebsiteModules, uData, uBaseUnit, uDownloadsManager, - XQueryEngineHTML, httpsendthread, synautil; - -implementation - -const - dirurl = '/manga/all'; - -function GetDirectoryPageNumber(const MangaInfo: TMangaInformation; - var Page: Integer; const WorkPtr: Integer; const Module: TModuleContainer): Integer; -begin - Result := NET_PROBLEM; - Page := 1; - if MangaInfo = nil then Exit(UNKNOWN_ERROR); - if MangaInfo.FHTTP.GET(Module.RootURL + dirurl) then begin - Result := NO_ERROR; - with TXQueryEngineHTML.Create(MangaInfo.FHTTP.Document) do - try - page := StrToIntDef(XPathString('(//*[starts-with(@class,"pagination")]//a)[last()-1]'), 1); - finally - Free; - end; - end; -end; - -function GetNameAndLink(const MangaInfo: TMangaInformation; - const ANames, ALinks: TStringList; const AURL: String; - const Module: TModuleContainer): Integer; -var - v: IXQValue; - s: String; -begin - Result := NET_PROBLEM; - if MangaInfo = nil then Exit(UNKNOWN_ERROR); - s := Module.RootURL + dirurl; - if AURL <> '0' then s += '?page=' + IncStr(AURL); - if MangaInfo.FHTTP.GET(s) then begin - Result := NO_ERROR; - with TXQueryEngineHTML.Create(MangaInfo.FHTTP.Document) do - try - for v in XPath('//table[1]/tbody/tr/td[1]/a') do begin - ALinks.Add(v.toNode.getAttribute('href')); - ANames.Add(v.toString); - end; - finally - Free; - end; - end; -end; - -function GetInfo(const MangaInfo: TMangaInformation; - const AURL: String; const Module: TModuleContainer): Integer; -var - v: IXQValue; - s: String; -begin - Result := NET_PROBLEM; - if MangaInfo = nil then Exit(UNKNOWN_ERROR); - with MangaInfo.FHTTP, MangaInfo.mangaInfo do begin - if GET(FillHost(Module.RootURL, AURL)) then begin - Result := NO_ERROR; - with TXQueryEngineHTML.Create(Document) do - try - coverLink := XPathString('//*[@class="__image"]/img/@src'); - if coverLink <> '' then coverLink := MaybeFillHost(Module.RootURL, coverLink); - if title = '' then title := XPathString('//*[@class="__info"]/h3'); - authors := SeparateRight(XPathString('//*[@class="__info"]/p[starts-with(.,"Tác giả:")]'), ':'); - genres := SeparateRight(XPathString('//*[@class="__info"]/p[starts-with(.,"Thể loại:")]'), ':'); - s := XPathString('//*[@class="__info"]/p[starts-with(.,"Tình trạng:")]'); - if s <> '' then begin - if (Pos('Đang tiến hành', s) > 0) or (Pos('Ngưng', s) > 0) then status := '1' - else status := '0'; - end; - summary := XPathString('//*[@class="__info"]/*[@class="__description"]'); - for v in XPath('//*[@class="table-scroll"]/table/tbody/tr/td[1]/a') do begin - chapterLinks.Add(v.toNode.getAttribute('href')); - chapterName.Add(v.toString); - end; - InvertStrings([chapterLinks, chapterName]); - finally - Free; - end; - end; - end; -end; - -function GetPageNumber(const DownloadThread: TDownloadThread; - const AURL: String; const Module: TModuleContainer): Boolean; -var - v: IXQValue; -begin - Result := False; - if DownloadThread = nil then Exit; - with DownloadThread.FHTTP, DownloadThread.Task.Container do begin - PageLinks.Clear; - PageNumber := 0; - if GET(FillHost(Module.RootURL, AURL)) then begin - Result := True; - with TXQueryEngineHTML.Create(Document) do - try - for v in XPath('//*[@class="manga-container"]/img/@src') do - PageLinks.Add(v.toString); - finally - Free; - end; - end; - end; -end; - -procedure RegisterModule; -begin - with AddModule do - begin - Website := 'AcademyVN'; - RootURL := 'http://hocvientruyentranh.com'; - Category := 'Vietnamese'; - OnGetDirectoryPageNumber := @GetDirectoryPageNumber; - OnGetNameAndLink := @GetNameAndLink; - OnGetInfo := @GetInfo; - OnGetPageNumber := @GetPageNumber; - end; -end; - -initialization - RegisterModule; - -end. diff --git a/baseunits/modules/Cloudflare.pas b/baseunits/modules/Cloudflare.pas index 2255ff358..d5d1b8991 100644 --- a/baseunits/modules/Cloudflare.pas +++ b/baseunits/modules/Cloudflare.pas @@ -6,7 +6,7 @@ interface uses Classes, SysUtils, uBaseUnit, XQueryEngineHTML, httpsendthread, synautil, - JSUtils, RegExpr, dateutils; + synacode, JSUtils, RegExpr, dateutils; type @@ -14,21 +14,20 @@ interface TCFProps = class public - cf_clearance: string; - Expires: TDateTime; + websitemodule: TObject; CS: TRTLCriticalSection; - constructor Create; + constructor Create(awebsitemodule: TObject); destructor Destroy; override; - procedure Reset; - procedure AddCookiesTo(const ACookies: TStringList); end; function CFRequest(const AHTTP: THTTPSendThread; const Method, AURL: String; const Response: TObject; const CFProps: TCFProps): Boolean; implementation +uses WebsiteModules, MultiLog; + const - MIN_WAIT_TIME = 4000; + MIN_WAIT_TIME = 5000; function AntiBotActive(const AHTTP: THTTPSendThread): Boolean; var @@ -43,10 +42,13 @@ function AntiBotActive(const AHTTP: THTTPSendThread): Boolean; s := ''; end; -function JSGetAnsweredURL(const Source, URL: String; var OMethod, OURL: String; +function JSGetAnsweredURL(const Source, URL: String; var OMethod, OURL, opostdata: String; var OSleepTime: Integer): Boolean; var - s, meth, surl, jschl_vc, pass, jschl_answer: String; + meth, surl, r, jschl_vc, pass, jschl_answer, + body, javascript, challenge, innerHTML, i, k, domain: String; + re: TRegExpr; + ms: Integer; begin Result := False; if (Source = '') or (URL = '') then Exit; @@ -61,48 +63,74 @@ function JSGetAnsweredURL(const Source, URL: String; var OMethod, OURL: String; try meth := UpperCase(XPathString('//form[@id="challenge-form"]/@method')); surl := XPathString('//form[@id="challenge-form"]/@action'); + r:=xpathstring('//input[@name="r"]/@value'); jschl_vc := XPathString('//input[@name="jschl_vc"]/@value'); pass := XPathString('//input[@name="pass"]/@value'); finally Free; end; - if (meth = '') or (surl = '') or (jschl_vc = '') or (pass = '') then Exit; - - s := Source; - with TRegExpr.Create do - try - ModifierG := False; - ModifierI := True; - - Expression := 'setTimeout\(function\(\)\{\s*var.*\w,\s+(\S.+a\.value =[^;]+;)'; - Exec(s); - if SubExprMatchCount > 0 then - begin - s := Match[1]; - Expression := '\s{3,}[a-z](\s*=\s*document\.|\.).+;\r?\n'; - s := Replace(s, '', False); - Expression := 't\s=\s*t\.firstChild.href;'; - s := Replace(s, ' t = "' + URL + '";', False); - Expression := 'a\.value\s*='; - s := Replace(s, 'a =', False); - Expression := '^.*\.submit\(.*\},\s*(\d{4,})\).*$'; - OSleepTime := StrToIntDef(Replace(Source, '$1', True), MIN_WAIT_TIME); - jschl_answer := ExecJS(s); - end; - finally - Free; + if (meth = '') or (surl = '') or (r='') or (jschl_vc = '') or (pass = '') then Exit; + + re:=TRegExpr.Create; + try + body:=source; + // main script + re.Expression := '\