library plug;

uses
  ShareMem,
  Windows,
  Forms,
  controls,
  MyEmbConnection,
  DBAccess,
  MyAccess,
  SysUtils,
  Classes,
  Dialogs,
  clHttpRequest,
  clTcpClient,
  clHttp,
  ZipForge,
  clHttpHeader,
  IniFiles,
  plug_misc,
  menus,
  PerlRegEx,
  dxCore,
  masks,
  strutils,
  Unit_PlugIportForm in 'Unit_PlugIportForm.pas' {IMPORTFORM},
  Unit_PlugSetupForm in 'Unit_PlugSetupForm.pas' {SETUPFORM},
  Unit_PlugActualForm in 'Unit_PlugActualForm.pas' {ACTUALFORM},
  Unit_PluginConfirmActualForm in 'Unit_PluginConfirmActualForm.pas' {CONFIRMACTUALFORM},
  Unit_PluginBackUpForm in 'Unit_PluginBackUpForm.pas' {BACKUPFORM};

//  Unit_PlugSetupForm in 'Unit_PlugSetupForm.pas' {SETUPFORM};

type
  tbuf = array[0..3] of byte;

  TMyHttp = class
   procedure HttpRedirect(Sender: TObject; ARequestHeader: TStrings;
   AStatusCode: Integer; AResponseHeader: TclHttpResponseHeader;
   AResponseText: TStrings; var AMethod: String; var CanRedirect,
   Handled: Boolean);
  end;
  TMyRegExp = class
   procedure PRReplace(Sender: TObject; var ReplaceWith: utf8String);
  end;

{  $R plug.RES}


var
 DA : THandle;
 otu_bookid_range,book_url,contenttype:widestring;
 MyHttp: TMyHttp;
 MyRegExp: TMyRegExp;

function GetValidFileName(s: string): string;
begin
 s:=ReplaceText(s,'*','_');
 s:=ReplaceText(s,'|','_');
 s:=ReplaceText(s,'\','_');
 s:=ReplaceText(s,'"','_');
 s:=ReplaceText(s,':','_');
 s:=ReplaceText(s,'<','_');
 s:=ReplaceText(s,'>','_');
 s:=ReplaceText(s,'?','_');
 s:=ReplaceText(s,'/','_');
 result:=s;
end;

function WinToDos(ASource: String): AnsiString;
 var
   Ch: PAnsiChar;
 begin
   Ch := AnsiStrAlloc(Length(ASource) + 1);
   CharToOem(PChar(ASource), Ch);
   Result := StrPas(Ch);
   StrDispose(Ch)
 end;

function DosToWin(ASource: AnsiString): AnsiString;
 var
   Ch: PAnsiChar;
 begin
   Ch := AnsiStrAlloc(Length(ASource) + 1);
   OemToAnsi(PAnsiChar(ASource), Ch);
   Result := StrPas(Ch);
   StrDispose(Ch)
 end;

procedure GetFileSignature(filename: string; var buf: tbuf);
var
 f: file of byte;
 i: Integer;
begin
 for i:=0 to 3 do buf[i]:=0;
 assignfile(f,filename);
 reset(f);
 blockread(f,buf,4);
 closefile(f);
end;

function PluginType : pwidechar;
begin
  //Мы указали реакцию на этот тип
  Plugintype := pwidechar('importlibdb');
end;

function PluginName:pwidechar;
begin
  //название плагина. Эта строчка будет в менюшке
// PluginName := pwidechar('LibrusPro');
 PluginName := pwidechar('Librus');
// PluginName := pwidechar('Flibusta');
end;

function PluginOnline : Boolean;
begin
  // Online - Имеет возможность скачивать и обновляться по сети
  PluginOnline := true;
end;

function PluginBackUpData(App,frm:THandle; dbname,INIFile,DBPath:widestring):Boolean;
var
 BACKUPFORM : TBACKUPFORM;
 b: boolean;
 r: trect;
begin
 b:=false;
 plug_misc.INIFile:=INIFile;
 plug_misc.basename:=dbname;
 DA := Application.Handle;
 Application.Handle := App;
 try
  GetWindowRect(frm,r);
  BACKUPFORM:=TBACKUPFORM.Create(Application);
  BACKUPFORM.CallForm:=frm;
  BACKUPFORM.backup:=true;
//  BACKUPFORM.Caption:='Экспорт личных данных '+dbname;
  BACKUPFORM.left:=  r.Left+(r.Right-r.Left) div 2 - BACKUPFORM.Width div 2;
  BACKUPFORM.top:=r.Top+ (r.Bottom-r.Top) div 2 - BACKUPFORM.height div 2;
  if BACKUPFORM.ShowModal=mrOk then b:=true;
 finally
  FreeAndNil(BACKUPFORM);
 end;
 Application.Handle := DA;
 PluginBackUpData:=b;
end;

function PluginRestoreData(App,frm:THandle; dbname,INIFile,DBPath:widestring):Boolean;
var
 BACKUPFORM : TBACKUPFORM;
 b: boolean;
 r: trect;
begin
 b:=false;
 plug_misc.INIFile:=INIFile;
 plug_misc.basename:=dbname;
 DA := Application.Handle;
 Application.Handle := App;
 try
  GetWindowRect(frm,r);
  BACKUPFORM:=TBACKUPFORM.Create(Application);
  BACKUPFORM.CallForm:=frm;
  BACKUPFORM.backup:=false;
//  BACKUPFORM.Caption:='Импорт личных данных '+dbname;
  BACKUPFORM.left:=  r.Left+(r.Right-r.Left) div 2 - BACKUPFORM.Width div 2;
  BACKUPFORM.top:=r.Top+ (r.Bottom-r.Top) div 2 - BACKUPFORM.height div 2;
  if BACKUPFORM.ShowModal=mrOk then b:=true;
 finally
  FreeAndNil(BACKUPFORM);
 end;
 Application.Handle := DA;
 PluginRestoreData:=b;
end;


function PluginImportBase(App,frm:THandle; dbname,INIFile,DBPath:widestring; autorun: boolean=false):Boolean;
var
 IMPORTFORM : TIMPORTFORM;
 b: boolean;
 r: trect;
begin
 b:=false;
 plug_misc.INIFile:=INIFile;
 plug_misc.basename:=dbname;
 DA := Application.Handle;
 Application.Handle := App;
 try
  if frm<>0
   then GetWindowRect(frm,r)
   else GetWindowRect(GetDesktopWindow(),r);

  IMPORTFORM:=TIMPORTFORM.Create(Application);

  // Application.CreateForm(TIMPORTFORM, IMPORTFORM);

  IMPORTFORM.autorun:=autorun;
  IMPORTFORM.savelog:=autorun;
  IMPORTFORM.CallForm:=frm;
//  IMPORTFORM.Caption:='Импорт библиотеки '+dbname;
  IMPORTFORM.left:=  r.Left+(r.Right-r.Left) div 2 - IMPORTFORM.Width div 2;
  IMPORTFORM.top:=r.Top+ (r.Bottom-r.Top) div 2 - IMPORTFORM.height div 2;

  if IMPORTFORM.ShowModal=mrOk then b:=true;
 finally
  FreeAndNil(IMPORTFORM);
 end;
 Application.Handle := DA;
 PluginImportBase:=b;
end;

function PluginSetup(App,frm:THandle; dbname,INIFile:widestring):Boolean; //stdcall;
var
 Form : TSETUPFORM;
 b: boolean;
 r: trect;
begin
 DA := Application.Handle;
 Application.Handle := App;
 plug_misc.INIFile:=INIFile;

 try
  GetWindowRect(frm,r);
  Form:=TSETUPFORM.Create(application);
  //Form.CallForm:=frm;
  Form.basename:=dbname;
  Form.RzPageControl1.ActivePageIndex:=0;
  Form.left:=  r.Left+(r.Right-r.Left) div 2 - Form.Width div 2;
  Form.top:=r.Top+ (r.Bottom-r.Top) div 2 - Form.height div 2;

  if Form.ShowModal=1 then b:=true;
 finally
  FreeAndNil(Form);
 end;

 Application.Handle := DA;
 PluginSetup:=b;
end;

function PluginActual(App,frm:THandle; dbname,INIFile,DBPath:widestring):Boolean;
var
 ACTUALFORM : TACTUALFORM;
 b: boolean;
 r: trect;
 f: tinifile;
 ff1,ff2,ff3,ff4,ff5,ff6,ff7,ff8,ff9: boolean;
 pl_fl_0000,pl_fl_0001,storefile,lng,path: string;
begin
  // app - handle application
  // frm - handle MAINFORM
  storefile:=ChangeFileExt(Application.ExeName, '.ini');
  path:=ExtractFilePath(Application.ExeName);
  f:=TIniFile.Create(storefile);
  lng:=f.ReadString('main','lang','Russian');
  f.Free;
  f:=TIniFile.Create(path+lng+'.lng');
  pl_fl_0000:=f.ReadString('Custom Resource Strings','pl_fl_0000','Не выбран ни один формат имен файлов в настройках текущей библиотеки.');
  pl_fl_0001:=f.ReadString('Custom Resource Strings','pl_fl_0001','Актуализация многофайловых архивов не поддерживается.');
  f.Free;
  f:=TIniFile.Create(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;
  f.free;
  if not (ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7 or ff8)
   then
    begin
     if not ff9
      then MessageBox(Application.Handle, PChar(pl_fl_0000), PChar('MultiLib'), MB_ICONWARNING + MB_OK + MB_DEFBUTTON2)
      else MessageBox(Application.Handle, PChar(pl_fl_0001), PChar('MultiLib'), MB_ICONWARNING + MB_OK + MB_DEFBUTTON2);
      exit;
    end;
 b:=false;
 plug_misc.INIFile:=INIFile;
 plug_misc.basename:=dbname;
 DA := Application.Handle;
 Application.Handle := App;
 try
  GetWindowRect(frm,r);
  ACTUALFORM:=TACTUALFORM.Create(Application);
  ACTUALFORM.CallForm:=frm;
  ACTUALFORM.Parent:=Application.MainForm;
//  ACTUALFORM.Caption:='Актуализация библиотеки '+dbname;
  ACTUALFORM.left:=  r.Left+(r.Right-r.Left) div 2 - ACTUALFORM.Width div 2;
  ACTUALFORM.top:=r.Top+ (r.Bottom-r.Top) div 2 - ACTUALFORM.height div 2;
  if ACTUALFORM.ShowModal=mrOk then b:=true;

 finally
  FreeAndNil(ACTUALFORM);
  application.ProcessMessages;
 end;
 Application.Handle := DA;
 PluginActual:=b;
end;

procedure TMyHttp.HttpRedirect(Sender: TObject; ARequestHeader: TStrings;
  AStatusCode: Integer; AResponseHeader: TclHttpResponseHeader;
  AResponseText: TStrings; var AMethod: String; var CanRedirect,
  Handled: Boolean);
begin
 Handled:=true;
 CanRedirect:=false;
 book_url:=AResponseHeader.Location;
 contenttype:=AResponseHeader.ContentType;
 contenttype:=AResponseHeader.ContentDisposition;
end;


function ifexists(mask: string; l1,l: tstringlist):boolean;
var
 i: integer;
 name: string;
begin
 result:=false;
 for i:=0 to l1.Count-1 do
  begin
   name:=extractfilename(l1[i]);
   if MatchesMask(AnsiLowerCase(Name), AnsiLowerCase(mask))
    then
     begin
      l.Add(l1[i]);
      result:=true;
     end;
  end;
end;

// Поиск в каталоге скачаного для получения информации
function FindFromLocalFile(bookid,md5,filename,INIFile: widestring; var arcname,fbname:widestring): widestring;
var
 BookLocation,l,l1: tstringlist;
 s,ss: widestring;
 i,j: integer;
 zip: TZipForge;
 arc:TZFArchiveItem;
 cutmd5: string;
 b,bz: boolean;
 buf: tbuf;
 f: tinifile;

 ff1,ff2,ff3,ff4,ff5,ff6,ff7,ff8,ff9: boolean;


begin
 b:=false;
 arcname:='';
 result:='';
 l:=tstringlist.Create;
 try
  BookLocation:=TStringList.Create;
  f:=TIniFile.Create(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);

   if not (ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7 or ff8)
    then
     begin
      result:='';
      BookLocation.Free;
      l.Free;
      f.Free;
      exit;
     end;

   // Определяем все пути к файлам коллекции
   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;

   if ff8 then
    for j:=0 to BookLocation.Count-1 do
     begin
      s:=IncludeTrailingBackslash(BookLocation[j])+copy(md5,1,2)+'\'+copy(md5,3,2)+'\'+md5+'.zip';
      if fileexists(s)
       then
        begin

         zip:=TZipForge.Create(nil);

         try
          zip.FileName:=s;
          zip.SpanningOptions.AdvancedNaming := False;
          try
           zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
          except
           zip.OpenCorruptedArchives := true;//Try to open corrupted archive
           zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
          end;
          result:='';
          arcname:=s;
(*
          for i := 0 to zip.FileCount - 1 do
           begin
            ss:=ansilowercase(extractfileext(zip[i].FileName));
            if ss='.fbd'
             then fbname:=zip[i].FileName
             else result:=zip[i].FileName;
           end;
*)
          if (zip.FindFirst('*.*',arc,faAnyFile-faDirectory)) then
           repeat
            ss:=ansilowercase(extractfileext(arc.StoredPath+arc.FileName));
            if ss='.fbd'
             then fbname:=arc.StoredPath+arc.FileName
             else result:=arc.StoredPath+arc.FileName;
           until (not zip.FindNext(arc));
         except
         end;
         zip.Free;
         BookLocation.Free;
         l.Free;
         f.Free;
         exit;
        end;
     end;
  except
  end;
  f.Free;

  if ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7
   then
    begin
     l1:=tstringlist.Create;
     try
      for j:=0 to BookLocation.Count-1 do FindFiles(BookLocation[j],'*',l1,true);
      l.clear;
      cutmd5:=md5;
      if length(cutmd5)>=26
       then delete(cutmd5,1,6)
       else cutmd5:='';
      b:=false;
      if (not b)and ff1 then b:=ifexists(bookid+'.*',l1,l);
      if (not b)and ff2 then b:=ifexists(bookid+' *.*',l1,l);
      if (not b)and ff3 then b:=ifexists('*.'+bookid+'.*',l1,l);
      if (not b)and ff4 then b:=ifexists(filename+'.*',l1,l);
      if (not b)and ff5 then b:=ifexists(filename,l1,l);
      if (not b)and ff6 then b:=ifexists(md5+'*',l1,l);
      if (not b)and ff7 and (cutmd5<>'')  then b:=ifexists('*'+cutmd5+'*',l1,l);
     except
     end;
     l1.Free;
     fbname:='';
     if l.Count>0
      then
       begin
        for i:=0 to l.Count-1 do
         begin
          s:=AnsiLowerCase(l[i]);
          if ansilowercase(extractfileext(l[i]))='.fbd'
           then fbname:=s
           else ss:=s;
         end;
      s:=ss;
      bz:=false;
      if (ExtractFileExt(s)<>'.zip')and(ExtractFileExt(s)<>'.epub')and(s<>'')
       then
        begin
         GetFileSignature(s,buf);
         if (buf[0]=$50)and(buf[1]=$4B)and(buf[2]=$03)and(buf[3]=$04) then bz:=true;
        end;

       if (fbname='') and ((ExtractFileExt(s)='.zip')or bz)
        then
         begin
          zip:=TZipForge.Create(nil);
          try
           zip.FileName:=s;
           zip.SpanningOptions.AdvancedNaming := False;
           try
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           except
            zip.OpenCorruptedArchives := true;//Try to open corrupted archive
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           end;
          result:='';
          arcname:=s;
(*
          for i := 0 to zip.Count - 1 do
           begin
            ss:=ansilowercase(extractfileext(zip[i].FileName));
            if ss='.fbd'
             then fbname:=zip[i].FileName
             else result:=zip[i].FileName;
           end;
*)
          if (zip.FindFirst('*.*',arc,faAnyFile-faDirectory)) then
           repeat
            ss:=ansilowercase(extractfileext(arc.StoredPath+arc.FileName));
            if ss='.fbd'
             then fbname:=arc.StoredPath+arc.FileName
             else result:=arc.StoredPath+arc.FileName;
           until (not zip.FindNext(arc));
         except
         end;


          zip.Free;
         end
        else
         result:=s;
      end;
    end;
 except
 end;
 l.Free;
 BookLocation.Free;
end;

// Поиск в каталоге скачаного
// Распаковываем всё кроме FBD

function FindAndExtractFromLocalFile(bookid,md5,filename,INIFile,extractpath: widestring): widestring;
var
 BookLocation,l,l1: tstringlist;
 s,ss: widestring;
 i,j,n: integer;
 zip: TZipForge;
 arc:TZFArchiveItem;
 cutmd5: string;
 b,bz: boolean;
 buf: tbuf;
 f: tinifile;
 ff1,ff2,ff3,ff4,ff5,ff6,ff7,ff8,ff9: boolean;

begin
 result:='';
 if not DirectoryExists(extractpath) then ForceDirectories(extractpath);

 l:=tstringlist.Create;
 try
  BookLocation:=TStringList.Create;
  f:=TIniFile.Create(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);

   if not (ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7 or ff8)
    then
     begin
      result:='';
      BookLocation.Free;
      l.Free;
      f.Free;
      exit;
     end;

   // Определяем все пути к файлам коллекции
   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;

   // .....................................
   if ff8 then
    for j:=0 to BookLocation.Count-1 do
     begin
      s:=IncludeTrailingBackslash(BookLocation[j])+copy(md5,1,2)+'\'+copy(md5,3,2)+'\'+md5+'.zip';
      if fileexists(s)
       then
        begin
         zip:=TZipForge.Create(nil);
         try
          zip.FileName:=s;
          zip.SpanningOptions.AdvancedNaming := False;
          try
           zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
          except
           zip.OpenCorruptedArchives := true;//Try to open corrupted archive
           zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
          end;
          result:='';
          if (zip.FindFirst('*.*',arc,faAnyFile-faDirectory)) then
           repeat
            ss:=ansilowercase(extractfileext(arc.StoredPath+arc.FileName));
            if ss<>'.fbd'
             then
              begin
               zip.BaseDir:=extractpath;
               zip.ExtractFiles(arc.StoredPath+arc.FileName);
               zip.CloseArchive;
               Result:=extractpath+arc.FileName;
              end;
           until (not zip.FindNext(arc));

(*
          for i := 0 to zip.Count - 1 do
           begin
            ss:=ansilowercase(extractfileext(zip[i].FileName));
            if (ss<>'.fbd')
             then
              begin
               zip.FSpecArgs.clear;
               zip.FSpecArgs.Add(zip[i].FileName);
               zip.ExtrBaseDir:=extractpath;
               n:=zip.Extract;
               if n=0 then result:=extractpath+zip[i].FileName;
              end;
           end;
*)
         except
         end;
         zip.Free;
         BookLocation.Free;
         l.Free;
         f.Free;
         exit;
        end;
     end;
  except
  end;
  f.Free;

  if ff1 or ff2 or ff3 or ff4 or ff5 or ff6 or ff7
   then
    begin
     l1:=tstringlist.Create;
     try
      for j:=0 to BookLocation.Count-1 do FindFiles(BookLocation[j],'*',l1,true);
      BookLocation.Free;
      l.Clear;
      cutmd5:=md5;
      if length(cutmd5)>=26
       then delete(cutmd5,1,6)
       else cutmd5:='';
      b:=false;
      if (not b)and ff1 then b:=ifexists(bookid+'.*',l1,l);
      if (not b)and ff2 then b:=ifexists(bookid+' *.*',l1,l);
      if (not b)and ff3 then b:=ifexists('*.'+bookid+'.*',l1,l);
      if (not b)and ff4 then b:=ifexists(filename+'.*',l1,l);
      if (not b)and ff5 then b:=ifexists(filename,l1,l);
      if (not b)and ff6 then b:=ifexists(md5+'*',l1,l);
      if (not b)and ff7 and (cutmd5<>'')  then b:=ifexists('*'+cutmd5+'*',l1,l);
     except
     end;
     l1.Free;
     if l.Count>0
      then
       begin
        for i:=0 to l.Count-1 do
         begin
          s:=AnsiLowerCase(l[i]);
          ss:=ansilowercase(extractfileext(l[i]));
          if ss<>'.fbd' then break;
         end;
        bz:=false;
        if (ansilowercase(ExtractFileExt(s))<>'.zip')and(ansilowercase(ExtractFileExt(s))<>'.epub')and(s<>'')
         then
          begin
           GetFileSignature(s,buf);
           if (buf[0]=$50)and(buf[1]=$4B)and(buf[2]=$03)and(buf[3]=$04) then bz:=true;
          end;
        if (ExtractFileExt(s)='.zip')or bz
         then
          begin
           zip:=TZipForge.Create(nil);
           zip.FileName:=s;
           zip.SpanningOptions.AdvancedNaming := False;
           try
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           except
            zip.OpenCorruptedArchives := true;//Try to open corrupted archive
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           end;

          if (zip.FindFirst('*.*',arc,faAnyFile-faDirectory)) then
           repeat
            ss:=ansilowercase(extractfileext(arc.StoredPath+arc.FileName));
            if ss<>'.fbd'
             then
              begin
               zip.BaseDir:=extractpath;
               zip.ExtractFiles(arc.StoredPath+arc.FileName);
               zip.CloseArchive;
               Result:=extractpath+arc.FileName;
              end;
           until (not zip.FindNext(arc));
(*
           for i:=0 to zip.Count-1 do
            begin
             ss:=ansilowercase(extractfileext(zip[i].FileName));
             if (ss<>'.fbd')
              then
               begin
                zip.FSpecArgs.clear;
                zip.FSpecArgs.Add(zip[i].FileName);
                zip.ExtrBaseDir:=extractpath;
                j:=zip.Extract;
                if j=0 then result:=extractpath+zip[i].FileName;
               end;
            end;
*)

           zip.Free;
          end
         else
          begin
           ss:=extractpath+extractfilename(s);
           CopyFile(PWideChar(s),PWideChar(ss),false);
           result:=ss;
          end;
       end;
    end;
 except
 end;
 l.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;
 result:=true;
end;

procedure TMyRegExp.PRReplace(Sender: TObject; var ReplaceWith: utf8String);
begin
 otu_bookid_range:=ReplaceWith;
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;


// Поиск в коллекции (xxxx-xxxx) для получения информации из FB2
// Ищем только fb*, если не fb* - результат возвращает "+"
function FindFromArc(bookid,fn,ft,inifile:widestring; fs: int64; var arcname,fbname: widestring):widestring;
var
 n,i,j,k,o,x1,x2,x: Integer;
 l,BookLocation: tstringlist;
 Replacement,Regex,s,s1,s2: string;
 f: tinifile;
 zip: TZipForge;
 arc:TZFArchiveItem;
 fnn: widestring;
 ff9: boolean;
begin
 result:='';
 arcname:='';
 fbname:='';
 f:=TIniFile.Create(inifile);
 ff9:=f.ReadBool('FomatFile','format9',true);
 if not ff9
  then
   begin
    f.Free;
    exit;
   end;
 //if ansilowercase(ft)<>'fb2' then exit;
 ft:=ansilowercase(ft);
 fnn:=fn;
 if ft<>'fb2' then fn:=ChangeFileExt(fn,'.fbd');
 x:=strtoint(bookid);
 l:=tstringlist.Create;
 BookLocation:=tstringlist.Create;

 zip:=TZipForge.Create(nil);

 f.ReadSectionValues('BookLocation',l);
 for i := 0 to l.Count - 1 do
  begin
   n:=pos('=',l[i]);
   if n>0
    then
     begin
      s:=l[i];
      delete(s,1,n);
      if s='' then continue;
      if s[1]<>'*'
       then
        begin
         s:=abs_path(IncludeTrailingBackslash(s));
         BookLocation.Add(s);
        end;
     end;
  end;
 Regex:=f.ReadString('Main','Regex','fb2[-\.](\d+[-\.]\d+)|(\d+[-\.]\d+)');
 Replacement:=f.ReadString('Main','Replacement','\1\2');
 f.Free;
 l.Clear;
 for j:=0 to BookLocation.Count - 1 do
  begin
   FindFiles(BookLocation[j],'*.zip',l,true);
   for i:=0 to l.Count-1 do
    begin
     otu_bookid_range:='';
     parce_bookid(l[i],Regex,Replacement);
     s1:='';
     s2:='';
     split_bookid(otu_bookid_range,s1,s2);
     if TryStrToInt(s1,x1)and(TryStrToInt(s2,x2))
      then
       if (x>=x1)and(x<=x2)
        then
         begin
          try
           zip.FileName:=l[i];
           zip.SpanningOptions.AdvancedNaming := False;
           try
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           except
            zip.OpenCorruptedArchives := true;//Try to open corrupted archive
            zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
           end;
           for k:=0 to 8 do
            begin
             case k of
              // fbx
              0: s:=bookid;
              1: s:=fn;
              2: s:=fnn+'.zip';
              3: s:=fnn+'.rar';
              4: s:='_'+fn;
              5: s:='__'+fn;
              // not fbx
              6: s:=fnn;
              7: s:='_'+fnn;
              8: s:='__'+fnn;

             end;

             if ft='fb2' then s:=s+'.fb2';

             //s1:=(UTF8Decode((zip[0].FileName)));
             if (zip.FindFirst(s,arc,faAnyFile-faDirectory))
              then
               begin
                arcname:=zip.FileName;
                result:=arc.FileName;
                zip.Free;
                l.Free;
                BookLocation.Free;
                exit;
               end
              else
               begin
                if (zip.FindFirst(UTF8Encode(s),arc,faAnyFile-faDirectory))
                 // if UTF8Decode(zip[o].FileName)=s
                 then
                  begin
                   arcname:=zip.FileName;
                   result:=arc.FileName;
                   zip.Free;
                   l.Free;
                   BookLocation.Free;
                   exit;
                  end;
               end;
            end;
          except
          end;
         end;
    end;
  end;
 l.Free;
 BookLocation.Free;
 zip.Free;
end;


function FindAndExtractFromArc(bookid,extractpath,fn,ft,arcname,inifile:widestring; fs: int64):widestring;
var
 n,i,j,k,o,x1,x2,x: Integer;
 l,BookLocation: tstringlist;
 oldname,Replacement,Regex,s,s1,s2: string;
 f: tinifile;
 zip: TZipForge;
 arc:TZFArchiveItem;
 ff9: boolean;
 fst: TStream;
 st: TMemoryStream;
begin
 result:='';
 f:=TIniFile.Create(inifile);
 ff9:=f.ReadBool('FomatFile','format9',true);
 if not ff9
  then
   begin
    f.Free;
    exit;
   end;
 if not DirectoryExists(extractpath) then ForceDirectories(extractpath);
 // Проверяем наличие файла во временном каталоге
 for k:=0 to 3 do
  begin
   case k of
    0: s:=fn;
    1: s:=bookid;
    2: s:='_'+fn;
    3: s:='__'+fn;
   end;
   if ft='fb2' then s:=s+'.fb2';
   s:=GetValidFileName(s);
   if FileExists(extractpath+s)
    then
     begin
      result:=extractfilename(s);
      exit;
     end;
  end;

 x:=strtoint(bookid);
 l:=tstringlist.Create;
 BookLocation:=tstringlist.Create;

 zip:=TZipForge.Create(nil);
 zip.Options.OverwriteMode := omAlways;


 f.ReadSectionValues('BookLocation',l);
 for i := 0 to l.Count - 1 do
  begin
   n:=pos('=',l[i]);
   if n>0
    then
     begin
      s:=l[i];
      delete(s,1,n);
      if s='' then continue;
      if s[1]<>'*'
       then
        begin
         s:=abs_path(IncludeTrailingBackslash(s));
         BookLocation.Add(s);
        end;
     end;
  end;
 Regex:=f.ReadString('Main','Regex','fb2[-\.](\d+[-\.]\d+)|(\d+[-\.]\d+)');
 Replacement:=f.ReadString('Main','Replacement','\1\2');
 f.Free;
 l.Clear;


 for j:=0 to BookLocation.Count - 1 do
  begin
   FindFiles(BookLocation[j],'*.zip',l,true);
   for i:=0 to l.Count-1 do
    begin
     otu_bookid_range:='';
     parce_bookid(l[i],Regex,Replacement);
     s1:='';
     s2:='';
     split_bookid(otu_bookid_range,s1,s2);
     if TryStrToInt(s1,x1)and(TryStrToInt(s2,x2))
      then
       if (x>=x1)and(x<=x2)
        then
         begin
          for k:=0 to 5 do
           begin
            case k of
             0: s:=fn;
             1: s:=fn+'.zip';
             2: s:=fn+'.rar';
             3: s:=bookid;
             4: s:='_'+fn;
             5: s:='__'+fn;
            end;
            if ft='fb2' then s:=s+'.fb2';
            try

             zip.FileName:=l[i];
             zip.SpanningOptions.AdvancedNaming := False;
             try
              zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
             except
              zip.OpenCorruptedArchives := true;//Try to open corrupted archive
              zip.OpenArchive(fmOpenRead+fmShareDenyWrite);
             end;


             if (zip.FindFirst(s,arc,faAnyFile-faDirectory))
              then
               begin
                zip.BaseDir:=extractpath;
                oldname:=s;
                s:=GetValidFileName(s);
                try
                 st:=TMemoryStream.Create;
                finally
                 try
                  zip.ExtractToStream(oldname,st);
                 finally
                  try
                   fst := tfilestream.create(extractpath+s, fmCreate or fmOpenWrite or fmShareDenyNone);
                  finally
                   try
                    st.position := 0;
                    fst.copyfrom(st, 0 {0 - copy all});
                   finally
                     result:=extractfilename(s);
                   end;
                   fst.Destroy;
                  end;
                 end;
                 st.Free;
                end;

                zip.CloseArchive();
                zip.Free;
                l.Free;
                BookLocation.Free;
                exit;
               end
              else
               begin
                if (zip.FindFirst(UTF8Encode(s),arc,faAnyFile-faDirectory))
                 then
                  begin
                   zip.BaseDir:=extractpath;
                   oldname:=s;
                   s:=GetValidFileName(s);
                   try
                    st:=TMemoryStream.Create;
                   finally
                    try
                     zip.ExtractToStream(oldname,st);
                    finally
                     try
                      fst := tfilestream.create(extractpath+s, fmCreate);
                     finally
                      try
                       st.position := 0;
                       fst.copyfrom(st, 0 {0 - copy all});
                      finally
                       result:=extractfilename(s);
                      end;
                      fst.free;
                     end;
                    end;
                    st.Free;
                   end;
                   zip.Free;
                   l.Free;
                   BookLocation.Free;
                   exit;
                  end
               end;
            except
            end;
           end;
         end;
    end;
  end;

 l.Free;
 BookLocation.Free;
 zip.CloseArchive();
 zip.Free;
end;


function  GetBookPageUrl(bookid,INIFile: widestring): widestring;
var
 f: TIniFile;
 liburl: widestring;
begin
 f:=TIniFile.Create(INIFile);
 liburl:=(f.ReadString('net','http_liburl',''));
 f.Free;
 if (liburl<>'')and(bookid<>'')
  then GetBookPageUrl:=liburl+'b/'+bookid
  else GetBookPageUrl:='';

end;

procedure BreakGetBookUrl;
var
 i: integer;
begin
 for i:=0 to application.ComponentCount - 1 do
  if application.Components[i] is TclHttp
   then
    if (application.Components[i]as TclHttp).Name='geturlhttp'
     then
      begin
       if (application.Components[i]as TclHttp).Active
        then
         try
          (application.Components[i]as TclHttp).TimeOut:=1;
//          (application.Components[i]as TclHttp).Connection.TimeOut:=1;
//          (application.Components[i]as TclHttp).Connection.close(false);
          (application.Components[i]as TclHttp).Close;
         except
         end;
      end;
end;

function GetBookUrl(bookid,INIFile:widestring;var res:widestring): widestring; //stdcall;
var
 f: TIniFile;
 data: TStringList;
 http: TclHttp;
 clHttpRequest: TclHttpRequest;
 http_liburl: widestring;
 http_libport: Integer;
 http_liblogin,
 http_libpasswd,
 http_proxyurl:widestring;
 http_proxyport: Integer;
 http_proxylogin,
 http_proxypasswd: widestring;
 enable_proxylib:Boolean;
 http_timeout: Integer;
 s: widestring;
 ss: string;
begin
 f:=TIniFile.Create(INIFile);
 http_liburl:=(f.ReadString('net','http_liburl',''));
 http_libport:=f.Readinteger('net','http_libport',0);
 http_liblogin:=(f.ReadString('net','http_liblogin',''));
 http_libpasswd:=(decodepass(f.ReadString('net','http_libpasswd','')));
 http_proxyurl:=(f.ReadString('net','http_proxyurl',''));
 http_proxyport:=f.ReadInteger('net','http_proxyport',0);
 http_proxylogin:=(f.ReadString('net','http_proxylogin',''));
 http_proxypasswd:=(decodepass(f.ReadString('net','http_proxypasswd','')));
 enable_proxylib:=f.ReadBool('net','enable_proxylib',False);
 http_timeout:=f.ReadInteger('net','http_timeout',1000);
 f.Free;
 GetBookUrl:='';
 data:=TStringList.Create;
 MyHttp:=TMyHttp.Create;
 http:=TclHttp.Create(application);
 http.Name:='geturlhttp';
 clHttpRequest:=TclHttpRequest.Create(nil);
 try
  clHttpRequest.Header.Accept:='text/html, */*';
  clHttpRequest.Header.ContentType:='application/x-www-form-urlencoded';
  clHttpRequest.AddFormField('name',http_liblogin);
  clHttpRequest.AddFormField('pass',http_libpasswd);
  clHttpRequest.AddFormField('persistent_login','1');
  clHttpRequest.AddFormField('form_id','user_login');
  http.Request:=clHttpRequest;
  http.OnRedirect:=MyHttp.HttpRedirect;
  if enable_proxylib
   then
    begin
     Http.ProxySettings.UserName:=http_proxylogin;
     Http.ProxySettings.Password:=http_proxypasswd;
     Http.ProxySettings.Server:=http_proxyurl;
     Http.ProxySettings.Port:=http_proxyport;
    end
   else
    begin
     Http.ProxySettings.UserName:='';
     Http.ProxySettings.Password:='';
     Http.ProxySettings.Server:='';
     Http.ProxySettings.Port:=0;
    end;
  Http.TimeOut:=http_timeout;
  Http.Port:=http_libport;
  s:=http_liburl+'user/login';
  res:='';
  try
   Http.Post(s,clHttpRequest, data);
  except
   on E : Exception do res:=(E.Message);
  end;
  book_url:='';
  s:=addbackslash(http_liburl)+'b/'+bookid+'/get';
  res:='';
  try
   Http.Post(s,clHttpRequest, data);
  except
   on E : Exception do res:=(E.Message);
  end;
  ss:=book_url;
  if pos('user/',ss)>0 then book_url:='';
  GetBookUrl:=book_url;
 except
 end;
 data.Free;
 clHttpRequest.Free;
 http.Free;
 MyHttp.Free;

end;

exports
  PluginType, PluginName, PluginOnline,PluginImportBase,GetBookUrl,PluginSetup,PluginActual,
  GetBookPageUrl, PluginBackUpData,PluginRestoreData, FindAndExtractFromLocalFile,
  FindAndExtractFromArc,FindFromLocalFile, FindFromArc,BreakGetBookUrl;
begin
 IsMultiThread := True;
end.
