unit Unit_PlugActualForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, strutils,
  Dialogs, ExtCtrls, inifiles, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters, cxStyles, dxSkinsCore, dxSkinMoneyTwins, dxSkinscxPCPainter, cxCustomData, cxFilter, cxData, cxDataStorage, cxEdit, cxNavigator, DB, cxDBData, cxCheckBox, cxImageComboBox, cxTextEdit, cxGridLevel, cxGridCustomTableView, cxGridTableView, cxGridDBTableView, cxGridCustomView, cxGrid, MemDS, DBAccess,
  PerlRegEx,dxcore,MyAccess, cxClasses, cxLocalization, StdCtrls, RzEdit, RzButton, RzPanel,ZipMstr, RzPrgres,masks, ImgList;

type
  TMyRegExp = class
   procedure PRReplace(Sender: TObject; var ReplaceWith: utf8String);
  end;
  TACTUALFORM = class(TForm)
    Timer1: TTimer;
    cxLocalizer1: TcxLocalizer;
    SR1: TcxStyleRepository;
    ContentSelected: TcxStyle;
    ContentEven: TcxStyle;
    ContentOdd: TcxStyle;
    ContentDl: TcxStyle;
    ContentLast: TcxStyle;
    ContentMain: TcxStyle;
    ContentDel: TcxStyle;
    ContentActiveCell: TcxStyle;
    db: TMyConnection;
    DS_Scan: TDataSource;
    Q_SCAN: TMyQuery;
    RzPanel1: TRzPanel;
    BtnClose1: TRzButton;
    MLog: TRzMemo;
    BtnScan: TRzButton;
    BtnActual: TRzButton;
    RzPanel22: TRzPanel;
    pb1: TRzProgressBar;
    PSTATUS: TRzPanel;
    GridScan: TcxGrid;
    ScanGrid: TcxGridDBTableView;
    cl_bookid: TcxGridDBColumn;
    cl_author: TcxGridDBColumn;
    cl_title: TcxGridDBColumn;
    cl_filename: TcxGridDBColumn;
    cl_ext: TcxGridDBColumn;
    ResultGrid: TcxGridDBTableView;
    EliminationGrid: TcxGridDBTableView;
    ExeptionGrid: TcxGridDBTableView;
    ScanGridLevel: TcxGridLevel;
    cl_status: TcxGridDBColumn;
    ImageList1: TImageList;
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure BtnScanClick(Sender: TObject);
    procedure dbAfterConnect(Sender: TObject);
    procedure ScanGridCustomDrawCell(Sender: TcxCustomGridTableView; ACanvas: TcxCanvas; AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean);
    procedure FormActivate(Sender: TObject);
    procedure RzPanel22Resize(Sender: TObject);
    procedure BtnActualClick(Sender: TObject);
    procedure SetLang(lng:string);
  private
    { Private declarations }
  public
   CallForm: thandle;
   Replacement,Regex,plugpath: string;
   brk,wrk: boolean;
   actived,ISADD,ISDELETED: boolean;
   ff1,ff2,ff3,ff4,ff5,ff6,ff7,ff8,ff9: boolean;

   pl_fl4_0000,pl_fl4_0001,pl_fl4_0002,pl_fl4_0003,pl_fl4_0004,
   pl_fl4_0005,pl_fl4_0006,pl_fl4_0007,pl_fl4_0008,pl_fl4_0009,
   pl_fl4_0010,pl_fl4_0011,pl_fl4_0012,pl_fl4_0013,pl_fl4_0014,
   pl_fl4_0015,pl_fl4_0016,pl_fl4_0017,pl_fl4_0018,pl_fl4_0019,
   pl_fl4_0020,pl_fl4_0021,pl_fl4_0022,pl_fl4_0023,pl_fl4_0024,
   pl_fl4_0025,pl_fl4_0026,pl_fl4_0027,pl_fl4_0028,pl_fl4_0029,
   pl_fl4_0030,pl_fl4_0031,pl_fl4_0032,pl_fl4_0033,pl_fl4_0034,
   pl_fl4_0035,pl_fl4_0036,pl_fl4_0037,pl_fl4_0038    : string;

   procedure Createparams(var params: TCreateParams);
   function ExistsBook(fn,bookid,md5: string;var l: tstringlist): Boolean;
   function scanbook(var ll,lnotfnd: TStringList):integer;
   procedure Scan;
   procedure AddLog(s: WideString; showtime: boolean =false);
   function ismultiarc(filename: string): boolean;
  protected
   procedure WMGetSysCommand(var message : TMessage); message WM_SYSCOMMAND;
  end;

var
  ACTUALFORM: TACTUALFORM;
  MyRegExp: TMyRegExp;
  otu_bookid_range: widestring;

implementation

uses plug_misc,Unit_PluginConfirmActualForm;

{$R *.dfm}

procedure TMyRegExp.PRReplace(Sender: TObject; var ReplaceWith: utf8String);
begin
 otu_bookid_range:=ReplaceWith;
end;

function ecran(s: string): string;
var
 e, k: string;
 i, j: Integer;
begin
 result := '';
 e := '"\/''';
// e := '%\`"\_/''';
 for j := 1 to Length(s) do
  begin
   k := '';
   for i := 1 to Length(e) do
    if s[j] = e[i]
     then
       begin
        k := '\';
        Break;
       end;
   result := result + k + s[j]
  end;
end;

// Цепляем форму к приложению
procedure TACTUALFORM.Createparams(var params: TCreateParams);
var
 aHWnd: HWND;
begin
 inherited;
 {как-нибудь получаем существующий дескриптор}
 ahWnd := GetForegroundWindow;
 {а теперь:}
 params.WndParent := ahWnd;
end;

procedure TACTUALFORM.dbAfterConnect(Sender: TObject);
var
 q:TMyQuery;
begin
 Q_SCAN.SQL.Text:='select * from `'+plug_misc.basename+'`.`mlactual`';
 try
  Q_SCAN.Open;
 finally
 end;
 q.Free;
end;

procedure TACTUALFORM.WMGetSysCommand(var message : TMessage) ;
begin
 if (message.wParam = SC_MINIMIZE)
  then
   begin
    SendMessage(callform, wm_User, 0, 0);
    ShowWindow(ACTUALFORM.Handle, SW_HIDE);
   end
  else inherited;
end;

procedure TACTUALFORM.AddLog(s: WideString; showtime: boolean =false);
begin
  if showtime
   then MLOG.Lines.Add(formatdatetime('hh:nn:ss',now) + ': ' + s)
   else MLOG.Lines.Add('                '+s);
end;

procedure TACTUALFORM.FormActivate(Sender: TObject);
begin
 Caption:=pl_fl4_0000+' '+plug_misc.basename;
 if actived then exit;
 if (ScanGrid.ViewData.RowCount > 0)and(ScanGrid.Controller.SelectedRowCount<=0)
  then
   begin
    ScanGrid.DataController.SelectRows(0, 0);
    ScanGrid.ViewData.Rows[0].Focused := true;
   end;
 if ScanGrid.Controller.SelectedRowCount > 0
  then ScanGrid.Controller.TopRowIndex:=ScanGrid.Controller.SelectedRows[0].Index;
 PSTATUS.Caption:=' '+pl_fl4_0010+': '+inttostr(Q_SCAN.RecordCount);
 BtnActual.Enabled:=ScanGrid.DataController.RowCount>0;
 actived:=true;
end;

procedure TACTUALFORM.FormCreate(Sender: TObject);
var
 f: tinifile;
 storefile,path,lng,s: string;
begin
 actived:=false;
 ACTUALFORM:=self;
 path:=ExtractFilePath(Application.ExeName);
 f:=TIniFile.Create(plug_misc.INIFile);
  try
   ff1:=f.ReadBool('FomatFile','format1',true);
   ff2:=f.ReadBool('FomatFile','format2',true);
   ff3:=f.ReadBool('FomatFile','format3',true);
   ff4:=f.ReadBool('FomatFile','format4',true);
   ff5:=f.ReadBool('FomatFile','format5',true);
   ff6:=f.ReadBool('FomatFile','format6',true);
   ff7:=f.ReadBool('FomatFile','format7',true);
   ff8:=f.ReadBool('FomatFile','format8',true);
   ff9:=f.ReadBool('FomatFile','format9',true);
  except
  end;
 s:=plug_misc.abs_path('.\plugins\'+ExtractFilePath(f.ReadString('main','plugfilename','')));
 plugpath:=extractfilepath(s);
 Regex:='fb2[-\.](\d+[-]\d+)|(\d+[-]\d+)';
 //f.ReadString('Main','Regex','fb2[-\.](\d+[-\.]\d+)|(\d+[-\.]\d+)');
 Replacement:=f.ReadString('Main','Replacement','\1\2');
 f.Free;
 storefile:=ChangeFileExt(Application.ExeName, '.ini');
 f:=TIniFile.Create(storefile);
 lng:=f.ReadString('main','lang','Russian');
 if FileExists(path+lng+'.lng')
  then
   begin
    try
     cxLocalizer1.FileName:=path+lng+'.lng';
     cxLocalizer1.Active:=true;
     cxLocalizer1.LanguageIndex:=1;
    except
    end;
   end;
 SetLang(path+lng+'.lng');
 db.Port:=f.ReadInteger('MySQL','mysql_port', 3306);
 db.Username:=f.ReadString('MySQL','mysql_username','root');
 db.Password:=f.ReadString('MySQL','mysql_password','');
 db.Server:=f.ReadString('MySQL','mysql_server','localhost');
 f.Free;
 db.Database:=plug_misc.BaseName;
 db.Open;
end;

procedure TACTUALFORM.FormKeyPress(Sender: TObject; var Key: Char);
begin
 if Key=#27 then Close;
end;

procedure TACTUALFORM.RzPanel22Resize(Sender: TObject);
begin
 PSTATUS.Width:=RzPanel22.Width-214;
 pb1.Left:=PSTATUS.Width+14;
end;

procedure TACTUALFORM.Timer1Timer(Sender: TObject);
begin
 // Есди основная форма видима а форма импорта не всплыла - показать форму импорта
 if (IsWindowVisible(CallForm))and not (IsWindowVisible(ACTUALFORM.Handle))
  then ShowWindow(ACTUALFORM.Handle, SW_SHOW);
 // Есди форма импорта видима а основная форма спрятана - спрятать форму импорта
 if (IsWindowVisible(ACTUALFORM.Handle))and not (IsWindowVisible(CallForm))
  then ShowWindow(ACTUALFORM.Handle, SW_HIDE);
 // Если форма импорта "провалилась" за основную - "выбросить" форму импорта на передний план
 if (IsWindowVisible(ACTUALFORM.Handle))and (GetActiveWindow = callform)
  then SetForegroundWindow(ACTUALFORM.Handle);
end;

procedure TACTUALFORM.ScanGridCustomDrawCell(Sender: TcxCustomGridTableView; ACanvas: TcxCanvas; AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean);
begin
 if (AViewInfo.RecordViewInfo.Selected)
  then
   if (not AViewInfo.Selected) and ((sender as TcxGridDBTableView).VisibleColumnCount>1)
    then
     begin
      ACanvas.Brush.Color:=ContentActiveCell.Color;
      ACanvas.Font:=ContentActiveCell.Font;
      ACanvas.Font.Color:=ContentActiveCell.TextColor;
     end
    else
     begin
      ACanvas.Brush.Color:=ContentSelected.Color;
      ACanvas.Font:=ContentSelected.Font;
      ACanvas.Font.Color:=ContentSelected.TextColor;
     end;
end;

procedure TACTUALFORM.BtnActualClick(Sender: TObject);
var
 q: TMyQuery;
 i: integer;
 aform: TCONFIRMACTUALFORM;

begin
 aform:=TCONFIRMACTUALFORM.Create(self);
 aform.left:=Left+Width div 2 - aform.Width div 2;
 aform.top:=Top+Height div 2 - aform.height div 2;
 aform.CBDELETED.Caption:=pl_fl4_0011;
 aform.CBADD.Caption:=pl_fl4_0012;
 if aform.ShowModal<>mrOk then exit;
 ISADD:=aform.CBADD.Checked;
 ISDELETED:=aform.CBDELETED.Checked;
 aform.Free;
 application.ProcessMessages;
 BtnClose1.Enabled:=false;
 BtnScan.Enabled:=false;
 BtnActual.Enabled:=false;
 q:=TMyQuery.Create(nil);
 q.Connection:=db;
 AddLog(pl_fl4_0013,true);
 try
 if ISDELETED
  then
   begin
    q.SQL.Text:='delete ignore from `'+plug_misc.basename+'`.`mldownloaddata` where bookid in (select bookid from `'+plug_misc.basename+'`.`mlactual` where status=1)';
    try
     q.Execute;
    except
    end;
    q.SQL.Text:='delete ignore from `'+plug_misc.basename+'`.`mlactual` where status=1';
    try
     q.Execute;
    except
    end;
   end;
 if ISADD
  then
   begin
    q.SQL.Text:='insert ignore into `'+plug_misc.basename+'`.`mldownloaddata` (select null, `bookid`, now() from `'+plug_misc.basename+'`.`mlactual` where status=2)';
    try
     q.Execute;
    except
    end;
    q.SQL.Text:='delete ignore from `'+plug_misc.basename+'`.`mlactual` where status=2';
    try
     q.Execute;
    except
    end;
   end;
 except
 end;
 q.SQL.Text:='SELECT * FROM `'+plug_misc.basename+'`.`mldownloaddata` WHERE NOT EXISTS (SELECT 1 FROM `'+plug_misc.basename+'`.`mlbook` WHERE `'+plug_misc.basename+'`.`mldownloaddata`.bookid=`'+plug_misc.basename+'`.`mlbook`.bookid)';
 try
  q.Open;
 except
 end;
 i:=q.RecordCount;
 if i>0
  then
   if MessageBox(handle, PChar(pl_fl4_0038), PChar('MultiLib'), MB_ICONINFORMATION + MB_YESNO + MB_DEFBUTTON2) = IDYES
    then
     begin
      q.SQL.Text:='DELETE IGNORE FROM `'+plug_misc.basename+'`.`mldownloaddata` WHERE NOT EXISTS (SELECT 1 FROM `'+plug_misc.basename+'`.`mlbook` WHERE `'+plug_misc.basename+'`.`mldownloaddata`.bookid=`'+plug_misc.basename+'`.`mlbook`.bookid)';
      try
       q.execute;
      except
      end;
     end;
 if Q_SCAN.active then Q_SCAN.Refresh;
 PSTATUS.Caption:=' '+pl_fl4_0010+': '+inttostr(Q_SCAN.RecordCount);
 q.Free;
 BtnClose1.Enabled:=true;
 BtnScan.Enabled:=true;
 BtnActual.Enabled:=true;
 AddLog(pl_fl4_0014,true);
end;

procedure TACTUALFORM.BtnScanClick(Sender: TObject);
var
 aform: TCONFIRMACTUALFORM;
begin
 if wrk
  then
   begin
    brk:=true;
   end
  else
   begin
    aform:=TCONFIRMACTUALFORM.Create(self);
    aform.left:=Left+Width div 2 - aform.Width div 2;
    aform.top:=Top+Height div 2 - aform.height div 2;
    aform.CBDELETED.Caption:=pl_fl4_0015;
    aform.CBADD.Caption:=pl_fl4_0016;
    if aform.ShowModal<>mrOk then exit;
    ISADD:=aform.CBADD.Checked;
    ISDELETED:=aform.CBDELETED.Checked;
    aform.Free;
    brk:=false;
    wrk:=true;
    BtnScan.Caption:=pl_fl4_0017;
    BtnClose1.Enabled:=false;
    BtnActual.Enabled:=false;
    Scan;
    BtnScan.Caption:=pl_fl4_0001;
    BtnClose1.Enabled:=true;
    wrk:=false;
    BtnActual.Enabled:=ScanGrid.DataController.RowCount>0;
    PSTATUS.Caption:=' '+pl_fl4_0010+': '+inttostr(Q_SCAN.RecordCount);
   end;
end;

function TACTUALFORM.ExistsBook(fn,bookid,md5: string; var l:tstringlist): Boolean;
var
 i: integer;
 cutmd5: string;
begin
 result:=false;
 md5:=ansilowercase(md5);
 fn:=ansilowercase(fn);
 cutmd5:=md5;
 if length(cutmd5)>=26
  then delete(cutmd5,1,6)
  else cutmd5:='';
 for i:=0 to l.Count-1 do
  begin
   l[i]:=ansilowercase(extractfilename(l[i]));
   if (pos(bookid+'.',l[i])=1)and ff1
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (pos(md5,l[i])=1)and ff6
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (pos(cutmd5,l[i])=1)and ff7
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (pos(bookid+' ',l[i])=1)and ff2
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (pos('.'+bookid+'.',l[i])>0)and ff3
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (fn=l[i])and ff5
    then
     begin
      Result:=True;
      Break;
     end
    else
   if (pos(fn+'.',l[i])>0)and ff4
    then
     begin
      Result:=True;
      Break;
     end;
  end;
end;

function ExistsBookStuct(md5: string; bl: tstringlist): boolean;
var
 i: integer;
begin
 result:=false;
 for i:=0 to bl.Count-1 do
  if fileexists(bl[i]+copy(md5,1,2)+'\'+copy(md5,3,2)+'\'+md5+'.zip')
   then
    begin
     result:=true;
     exit;
    end;
end;

procedure TACTUALFORM.Scan;
var
 q,q1,q2: TMyQuery;
 s,bookid,filetype,author,title,filename,md5,ss: string;
 i,j,nn,nn1,k: integer;
 x,n1,n2,n3: integer;
 BookLocation,l,ll,ll1: TStringList;
 f: tinifile;
 b,bb: boolean;
begin
 // если включен режим только структура
 bb:=  not(ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7) and ff8;
 wrk:=true;
 q:=TMyQuery.Create(nil);
 q.Connection:=db;
 q1:=TMyQuery.Create(nil);
 q1.Connection:=db;
 q2:=TMyQuery.Create(nil);
 q2.Connection:=db;
 q.SQL.Text:='TRUNCATE TABLE `'+plug_misc.basename+'`.`mlactual` ';
 try
  q.Execute;
 except
 end;
 Q_SCAN.Refresh;
 MLOG.Clear;
 if ISDELETED
  then
   begin
    AddLog(pl_fl4_0018,true);
    q.SQL.Text:='SELECT * FROM `'+plug_misc.basename+'`.`mldownloaddata` WHERE NOT EXISTS (SELECT 1 FROM `'+plug_misc.basename+'`.`mlbook` WHERE `'+plug_misc.basename+'`.`mldownloaddata`.bookid=`'+plug_misc.basename+'`.`mlbook`.bookid)';
    try
     q.Open;
    except
    end;
    nn:=q.RecordCount;
    q.SQL.Text:='SELECT * FROM `'+plug_misc.basename+'`.`mldownloaddata`';
    try
     q.Open;
    except
    end;
    nn1:=q.RecordCount;
    q.SQL.Text:='select * from `'+plug_misc.basename+'`.`mlbook` join `'+plug_misc.basename+'`.`mldownloaddata` using (bookid)';
    try
     q.Open;
    except
    end;
    AddLog(pl_fl4_0019+': '+inttostr(nn1));
    AddLog(pl_fl4_0020+': '+inttostr(nn));
    AddLog(pl_fl4_0021+': '+inttostr(q.RecordCount));
    pb1.Percent:=0;
    pb1.Visible:=true;
    GridScan.BeginUpdate;
    //-----------------------------------------------------------------------------
    l:=TStringList.Create;
    BookLocation:=TStringList.Create;
    f:=TIniFile.Create(plug_misc.INIFile);
    try
     s:=f.ReadString('main','dlpath','');
     if s<>''
      then
       begin
        s:=abs_path(IncludeTrailingBackslash(s));
        BookLocation.Add(s);
       end;
     f.ReadSectionValues('BookLocation',l);
     for i:=0 to l.Count - 1 do
      begin
       j:=pos('=',l[i]);
       if j>0
        then
         begin
          s:=l[i];
          delete(s,1,j);
          if s='' then continue;
          if s[1]<>'*'
           then
            begin
             s:=abs_path(IncludeTrailingBackslash(s));
             BookLocation.Add(s);
            end;
         end;
      end;
    except
    end;
    f.Free;
    if not bb
     then
      try
       for j:=0 to BookLocation.Count-1 do FindFiles(BookLocation[j],'*',l,true);
      except
      end;
    for i:=0 to q.RecordCount-1 do
     begin
      pb1.Percent:=(i*100) div q.RecordCount;
      Application.ProcessMessages;
      if brk then break;
      filename:=q.FieldByName('filename').AsString;
      bookid:=q.FieldByName('bookid').AsString;
      title:=q.FieldByName('title').AsString;
      filetype:=q.FieldByName('ext').AsString;
      md5:=q.FieldByName('md5').AsString;
      q2.SQL.Text:='select fullname from `'+plug_misc.basename+'`.`mlauthor` left join `'+plug_misc.basename+'`.`mlauthorname` using(authorid) where bookid='+bookid;
      try
       q2.Open;
      except
      end;
      if q2.RecordCount>0
       then author:=q2.FieldByName('fullname').AsString
       else author:='';
      PSTATUS.Caption:=filename;
      PSTATUS.Refresh;
      if bb
       then  b:=ExistsBookStuct(md5,BookLocation)
       else  b:=ExistsBook(filename,bookid,md5,l);
      if not b
       then
        begin
         q1.SQL.Text:='INSERT IGNORE INTO `'+plug_misc.basename+'`.`mlactual` ';
         q1.SQL.add('VALUES ('+bookid+',"'+filetype+'","'+ecran(author)+'","'+ecran(title)+'","'+ecran(filename)+'","",1)');
         try
          q1.Execute;
         except
         end;
        end;
      q.Next;
     end;
    l.Free;
    BookLocation.Free;
    pb1.Percent:=0;
    pb1.Visible:=false;
    Q_SCAN.Refresh;
    GridScan.EndUpdate;
    FormActivate(self);
    if brk
     then AddLog(pl_fl4_0022,true)
     else AddLog(pl_fl4_0023,true);
    AddLog(pl_fl4_0024+': '+inttostr(Q_SCAN.RecordCount));
   end;
 if ISADD
  then
   begin
    GridScan.BeginUpdate;
    AddLog(pl_fl4_0025,true);
    ll:=TStringList.Create;
    ll1:=TStringList.Create;
    pb1.Percent:=0;
    pb1.Visible:=true;
    AddLog(pl_fl4_0026,true);
    x:=scanbook(ll,ll1);
    pb1.Percent:=0;
    q.SQL.Text:='drop table if exists `'+plug_misc.BaseName+'`.`tmpactual`;';
    try
     q.Execute;
    except
    end;
    q.SQL.Text:='create table `'+plug_misc.BaseName+'`.`tmpactual` (';
    q.SQL.Add('`bookid` int(11) NOT NULL,');
    q.SQL.Add('PRIMARY KEY  (`bookid`)');
    q.SQL.Add(')ENGINE=MyISAM;');
    try
     q.Execute;
    except
    end;
    n1:=ll.Count div 10000;
    n2:=n1*10000;
    n3:=ll.Count-n2;
    AddLog(pl_fl4_0027+': '+inttostr(ll.Count),true);
    AddLog(pl_fl4_0028,true);
    For i:=0 to n1-1 do
     begin
      ss:='';
      For j:=0 to 9999 do ss:=ss+',('+ll[i*10000+j]+')';
      delete(ss,1,1);
      ss:=ss+';';
      q.SQL.Text:='insert ignore into `'+plug_misc.BaseName+'`.`tmpactual` VALUES '+ss;
      try
       q.Execute;
      except
      end;
     end;
    ss:='';
    For j:=0 to n3-1 do ss:=ss+',('+ll[n2+j]+')';
    if ss<>''
     then
      begin
       delete(ss,1,1);
       ss:=ss+';';
       q.SQL.Text:='insert ignore into `'+plug_misc.BaseName+'`.`tmpactual` VALUES '+ss;
       try
        q.Execute;
       except
       end;
      end;
    pb1.Percent:=0;
    AddLog(pl_fl4_0029,true);
    if ll.Count>0
     then
      begin
       q.SQL.Clear;
       q.SQL.Add('select bookid,title,filename,ext,fullname,date_dl');
       q.SQL.Add('from `'+plug_misc.basename+'`.`mlbook`');
       q.SQL.Add('left JOIN `'+plug_misc.basename+'`.`mlauthor` USING(BookId)');
       q.SQL.Add('left JOIN `'+plug_misc.basename+'`.`mlauthorname` USING(authorid)');
       q.SQL.Add('left JOIN `'+plug_misc.basename+'`.`mldownloaddata` USING(bookid)');
       q.SQL.Add('where bookid in (select bookid from `'+plug_misc.basename+'`.`tmpactual`) and date_dl is NULL');
       q.SQL.Add('group by bookid');
       q.Open;
       for i:=0 to q.RecordCount-1 do
        begin
         if brk
          then
           begin
            GridScan.EndUpdate;
            AddLog(pl_fl4_0030,true);
            Q_SCAN.Refresh;
            q.Free;
            q1.Free;
            q2.Free;
            ll.Free;
            ll1.Free;
            Exit;
           end;
         pb1.Percent:=(i*100) div q.RecordCount;
         filename:=q.FieldByName('filename').AsString;
         bookid:=q.FieldByName('bookid').AsString;
         title:=q.FieldByName('title').AsString;
         filetype:=q.FieldByName('ext').AsString;
         author:=q.FieldByName('fullname').AsString;
         PSTATUS.Caption:=filename;
         PSTATUS.Refresh;
         inc(k); // новая книга
         q1.SQL.Text:='INSERT IGNORE INTO `'+plug_misc.basename+'`.`mlactual` ';
         q1.SQL.add('VALUES ('+bookid+',"'+filetype+'","'+ecran(author)+'","'+ecran(title)+'","'+ecran(filename)+'","",2)');
         try
          q1.Execute;
         except
         end;
         q.Next;
        end;
      end;
    pb1.Percent:=100;
    Q_SCAN.Refresh;
    AddLog(pl_fl4_0031+': '+inttostr(k));
    if ll1.Count>0
     then
      begin
       AddLog(pl_fl4_0032+': '+inttostr(ll1.Count)+'.');
       for i:=0 to ll1.Count-1 do AddLog(ll1[i]);
      end;
    AddLog(pl_fl4_0033,true);
    ll.Free;
    ll1.Free;
    q.SQL.Text:='drop table if exists `'+plug_misc.BaseName+'`.`tmpactual`;';
    try
     q.Execute;
    except
    end;
   end;
 AddLog(pl_fl4_0034+': '+inttostr(Q_SCAN.RecordCount));
 AddLog(pl_fl4_0035,true);
 if Q_SCAN.RecordCount>0
  then
   AddLog(pl_fl4_0036)
  else
   AddLog(pl_fl4_0037);
 q.Free;
 q1.Free;
 q2.Free;
 GridScan.EndUpdate;
 pb1.Visible:=true;
end;

function extractbookid(fn: string):string;
var
 s,ss: string;
 i,n: integer;
begin
 result:='';
 s:=ChangeFileExt(fn,'');
 if ACTUALFORM.ff1
  then
   begin
    if TryStrToInt(s,i)
     then
      begin
       Result:=s;                         // имя файла = BookId.ext ff1
       exit;
      end;
   end;
 if ACTUALFORM.ff2
  then
   begin
    n:=Pos(' ',s);
    if n>0
     then
      begin
       s:=Trim(Copy(s,1,n));
       if TryStrToInt(s,i)
        then
         begin
          Result:=s;                      // имя файла = BookId *.ext ff2
          exit;
         end;
      end;
   end;
 if ACTUALFORM.ff3
  then
   begin
    s:=fn;
    s:=ChangeFileExt(fn,'');
    ss:=s;
    s:=extractfileext(s);
    if length(s)>0 then delete(s,1,1);
    if TryStrToInt(s,i)
     then
      begin
       Result:=s;                         // *.BookId.ext ff3
       exit;
      end;
    s:=ss;
    s:=ChangeFileExt(fn,'');
    s:=extractfileext(s);
    if length(s)>0 then delete(s,1,1);
    if TryStrToInt(s,i)
     then
      begin
       Result:=s;                         // *.BookId.ext.ext ff3
       exit;
      end;
   end;
end;


procedure parce_bookid(subject,Regex,Replacement:widestring);
var
 PR: TPerlRegEx;
begin
 MyRegExp:=TMyRegExp.Create;
 PR := TPerlRegEx.create(nil);
 PR.OnReplace:=MyRegExp.PRReplace;
 PR.Subject:=subject;
 PR.RegEx:=RegEx;
 PR.Replacement:=Replacement;
 PR.ReplaceAll;
 PR.Free;
 MyRegExp.Free;
end;

function split_bookid(s: string; var bookid_start,bookid_end: string): boolean;
var
 i: integer;
begin
 for i:=1 to length(s) do
  if not (s[i] in ['0'..'9']) then
   begin
    bookid_start:=copy(s,1,i-1);
    bookid_end:=copy(s,i+1,length(s)-i);
    break;
   end;
end;

function TACTUALFORM.ismultiarc(filename: string): boolean;
var
 s1,s2: string;
 x1,x2: integer;
begin
 result:=false;
 otu_bookid_range:='';
 filename:=ansilowercase(extractfilename(filename));
 parce_bookid(filename,Regex,Replacement);
 s1:='';
 s2:='';
 split_bookid(otu_bookid_range,s1,s2);
 if TryStrToInt(s1,x1)and(TryStrToInt(s2,x2))
  then
   if (x2>=x1)
    then
     if filename='f.'+s1+'-'+s2+'.zip' then result:=true else
     if filename='f.fb2.'+s1+'-'+s2+'.zip' then result:=true else
     if filename='f.n.'+s1+'-'+s2+'.zip' then result:=true else
     if filename=s1+'-'+s2+'.zip' then result:=true else
     if filename='usr'+s1+'-'+s2+'.zip' then result:=true;
end;

function TACTUALFORM.scanbook(var ll,lnotfnd: TStringList):integer;
var
 BookLocation,l:TStringList;
 i,j: integer;
 path,s: string;
 q: TMyQuery;
 b: Boolean;
 f: tinifile;
 n: double;

function isHex(s: string):boolean;
var
 i: integer;
begin
 result:=true;
 for i:=1 to length(s) do
  if not (s[i] in ['0'..'9','a'..'f'])
   then
    begin
     result:=false;
     exit;
    end;
end;

begin
 result:=0;
 if brk then Exit;
 l:=TStringList.Create;
 BookLocation:=TStringList.Create;
 ll.Clear;
 lnotfnd.Clear;
 q:=TMyQuery.Create(self);
 q.Connection:=DB;
 f:=TIniFile.Create(inifile);
 f.ReadSectionValues('BookLocation',l);
 for i:=0 to l.Count - 1 do
  begin
   j:=pos('=',l[i]);
   if j>0
    then
     begin
      s:=l[i];
      delete(s,1,j);
      if s='' then continue;
      if s[1]<>'*'
       then
        begin
         s:=abs_path(IncludeTrailingBackslash(s));
         BookLocation.Add(s);
        end;
     end;
  end;
 s:=f.ReadString('main','dlpath','');
 if s<>''
  then
   begin
    s:=abs_path(IncludeTrailingBackslash(s));
    BookLocation.Add(s);
   end;
 f.Free;
 l.Clear;
 for j:=0 to BookLocation.Count-1 do FindFiles(BookLocation[j],'*',l,true);
 BookLocation.Free;
 // l - список файлов в каталоге
 // ll - список найденных файлов
 // lnotfnd - список отсутствующих файлов
 // Ищем книгу в базе
 result:=l.Count;
 for i:=0 to l.Count-1 do
  begin
   if brk
    then
     begin
      q.Free;
      l.Free;
      Exit;
     end;
   Application.ProcessMessages;
   b:=False;
   if ismultiarc(l[i]) or (extractfilename(l[i])='') then continue;
   // \xx\yy\md5.zip
   if (not b) and ff8
    then
     begin
      s:=ansilowercase(ExtractFileName(l[i]));
      if ExtractFileExt(s)='.zip'
       then
        begin
         s:=changefileext(s,'');
         if length(s)=32
          then
           begin
            if pos(copy(s,1,2)+'\'+copy(s,3,2)+'\'+s,l[i])>0
             then
              begin
               q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
               q.SQL.Add('where md5 like "'+s+'"');
               try
                q.open;
               except
               end;
               if q.RecordCount<>0
                then
                 begin
                  ll.Add(q.fieldbyname('BookId').AsString);
                  b:=true;
                 end;
              end;
           end;
        end;
     end;
   // Bookid
   if (not b) and (ff1 or ff2 or ff3)
    then
     begin
      s:=ExtractFileName(l[i]);
      ReplaceText(s,'.fb2.zip','');
      ReplaceText(s,'.fb2','');
      if AnsiLowerCase(ExtractFileExt(s))='.zip' then s:=changefileext(s,'');
      s:=extractbookid(s);
      if s<>''
       then
        begin
         q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
         q.SQL.Add('where BookId="'+s+'"');
         try
          q.open;
         except
         end;
         if q.RecordCount<>0
          then
           begin
            ll.Add(q.fieldbyname('BookId').AsString);
            b:=true;
           end;
        end;
     end;
   // FileName
   if (not b) and ff5
    then
     begin
      s:=ExtractFileName(l[i]);
      q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
      q.SQL.Add('where FileName="'+s+'"');
      try
       q.open;
      except
      end;
      if q.RecordCount<>0
       then
        begin
         ll.Add(q.fieldbyname('BookId').AsString);
         b:=true;
        end;
     end;
   // FileName -ext
   if (not b) and ff4
    then
     begin
      s:=ExtractFileName(l[i]);
      s:=changefileext(s,'');
      q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
      q.SQL.Add('where FileName="'+s+'"');
      try
       q.open;
      except
      end;
      if q.RecordCount<>0
       then
        begin
         ll.Add(q.fieldbyname('BookId').AsString);
         b:=true;
        end;
     end;
   // FileName -ext -ext
   if (not b) and ff4
    then
     begin
      s:=changefileext(s,'');
      q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
      q.SQL.Add('where FileName="'+s+'"');
      try
       q.open;
      except
      end;
      if q.RecordCount<>0
       then
        begin
         ll.Add(q.fieldbyname('BookId').AsString);
         b:=true;
        end;
     end;
   // md5
   if (not b) and ff6
    then
     begin
      s:=ansilowercase(ExtractFileName(l[i]));
      s:=changefileext(s,'');
      s:=changefileext(s,'');
      if length(s)>=32
       then
        begin
         q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
         q.SQL.Add('where md5 like "'+s+'"');
         try
          q.open;
         except
         end;
         if q.RecordCount<>0
          then
           begin
            ll.Add(q.fieldbyname('BookId').AsString);
            b:=true;
           end;
        end;
     end;
   // обрезанный MD5
   if (not b) and ff7
    then
     begin
      s:=ExtractFileName(l[i]);
      s:=changefileext(s,'');
      if (length(s)=26)
       then
        if ishex(s)
         then
          begin
           q.SQL.Text:='select BookId, FileName from `'+plug_misc.basename+'`.`mlbook`';
           q.SQL.Add('where md5 like "%'+s+'"');
           try
            q.open;
           except
           end;
           if q.RecordCount<>0
            then
             begin
              ll.Add(q.fieldbyname('BookId').AsString);
              b:=true;
             end;
          end;
     end;
   if not b
    then lnotfnd.Add(l[i]);
   pb1.Percent:=(i*100) div l.Count;
  end;
 l.Free;
 q.free;
end;

procedure TACTUALFORM.SetLang(lng:string);
var
 f: TMemIniFile;
 cp: word;
begin
 f:=TMemIniFile.Create(lng,TEncoding.GetEncoding(1251));
 cp:=f.ReadInteger('Custom Resource Strings','codepage',1251);
 f.free;
 f:=TMemIniFile.Create(lng,TEncoding.GetEncoding(cp));
 pl_fl4_0000:=f.ReadString('Custom Resource Strings','pl_fl4_0000','Актуализация библиотеки');
 pl_fl4_0001:=f.ReadString('Custom Resource Strings','pl_fl4_0001','Сканировать');
 pl_fl4_0002:=f.ReadString('Custom Resource Strings','pl_fl4_0002','Актуализировать');
 pl_fl4_0003:=f.ReadString('Custom Resource Strings','pl_fl4_0003','Выйти');
 pl_fl4_0004:=f.ReadString('Custom Resource Strings','pl_fl4_0004','bookid');
 pl_fl4_0005:=f.ReadString('Custom Resource Strings','pl_fl4_0005','Автор');
 pl_fl4_0006:=f.ReadString('Custom Resource Strings','pl_fl4_0006','Название');
 pl_fl4_0007:=f.ReadString('Custom Resource Strings','pl_fl4_0007','Имя файла в базе');
 pl_fl4_0008:=f.ReadString('Custom Resource Strings','pl_fl4_0008','Тип');
 pl_fl4_0009:=f.ReadString('Custom Resource Strings','pl_fl4_0009','Статус');
 pl_fl4_0010:=f.ReadString('Custom Resource Strings','pl_fl4_0010','Всего книг');
 pl_fl4_0011:=f.ReadString('Custom Resource Strings','pl_fl4_0011','Удалить отсутствующие книги');
 pl_fl4_0012:=f.ReadString('Custom Resource Strings','pl_fl4_0012','Добавить новые книги');
 pl_fl4_0013:=f.ReadString('Custom Resource Strings','pl_fl4_0013','Производится актуализация базы...');
 pl_fl4_0014:=f.ReadString('Custom Resource Strings','pl_fl4_0014','Актуализация базы завершена.');
 pl_fl4_0015:=f.ReadString('Custom Resource Strings','pl_fl4_0015','Искать отсутствующие книги');
 pl_fl4_0016:=f.ReadString('Custom Resource Strings','pl_fl4_0016','Искать новые книги');
 pl_fl4_0017:=f.ReadString('Custom Resource Strings','pl_fl4_0017','Прервать');
 pl_fl4_0018:=f.ReadString('Custom Resource Strings','pl_fl4_0018','Производится поиск отсутствующих книг...');
 pl_fl4_0019:=f.ReadString('Custom Resource Strings','pl_fl4_0019','В базе отмечено книг');
 pl_fl4_0020:=f.ReadString('Custom Resource Strings','pl_fl4_0020','Отсутствует информация о книгах');
 pl_fl4_0021:=f.ReadString('Custom Resource Strings','pl_fl4_0021','Книг к обработке');
 pl_fl4_0022:=f.ReadString('Custom Resource Strings','pl_fl4_0022','Поиск прерван.');
 pl_fl4_0023:=f.ReadString('Custom Resource Strings','pl_fl4_0023','Поиск отсутствующих книг завершен.');
 pl_fl4_0024:=f.ReadString('Custom Resource Strings','pl_fl4_0024','Не найдено книг');
 pl_fl4_0025:=f.ReadString('Custom Resource Strings','pl_fl4_0025','Производится поиск новых книг...');
 pl_fl4_0026:=f.ReadString('Custom Resource Strings','pl_fl4_0026','Сканируем каталоги...');
 pl_fl4_0027:=f.ReadString('Custom Resource Strings','pl_fl4_0027','Найдено уникальных книг');
 pl_fl4_0028:=f.ReadString('Custom Resource Strings','pl_fl4_0028','Добавляем записи во временную таблицу...');
 pl_fl4_0029:=f.ReadString('Custom Resource Strings','pl_fl4_0029','Производим обработку данных...');
 pl_fl4_0030:=f.ReadString('Custom Resource Strings','pl_fl4_0030','Сканирование прервано пользователем.');
 pl_fl4_0031:=f.ReadString('Custom Resource Strings','pl_fl4_0031','Найдено новых книг');
 pl_fl4_0032:=f.ReadString('Custom Resource Strings','pl_fl4_0032','Найдено неопознанных книг');
 pl_fl4_0033:=f.ReadString('Custom Resource Strings','pl_fl4_0033','Поиск новых книг завершен.');
 pl_fl4_0034:=f.ReadString('Custom Resource Strings','pl_fl4_0034','Найдено несоответствий');
 pl_fl4_0035:=f.ReadString('Custom Resource Strings','pl_fl4_0035','Сканирование завершено.');
 pl_fl4_0036:=f.ReadString('Custom Resource Strings','pl_fl4_0036','Для удаления несоответствий нажмите кнопку "Актуализировать базу".');
 pl_fl4_0037:=f.ReadString('Custom Resource Strings','pl_fl4_0037','Расхождений не найдено.');
 pl_fl4_0038:=f.ReadString('Custom Resource Strings','pl_fl4_0038','Удалить данные об отсутствующих в базе книгах из таблицы закачки?');
 BtnScan.Caption:=pl_fl4_0001;
 BtnActual.Caption:=pl_fl4_0002;
 BtnClose1.Caption:=pl_fl4_0003;
 cl_bookid.Caption:=pl_fl4_0004;
 cl_author.Caption:=pl_fl4_0005;
 cl_title.Caption:=pl_fl4_0006;
 cl_filename.Caption:=pl_fl4_0007;
 cl_ext.Caption:=pl_fl4_0008;
 cl_status.Caption:=pl_fl4_0009;
 PSTATUS.Caption:=' '+pl_fl4_0010+': 0';
 f.Free;
end;


initialization
//  dxInitializeGDIPlus;
 dxInitialize;
finalization
//  dxFinalizeGDIPlus;
 dxFinalize;
end.





