Subversion Repositories autosfx

Rev

Rev 2 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit ExtractorMain;
  2.  
  3. {$DEFINE DEBUG_MODE}
  4.  
  5. {$DEFINE USE_DZIP_UNPACK}
  6.  
  7. // TODO: Implement ExtractionTarget switch
  8.  
  9. interface
  10.  
  11. uses
  12.   Windows, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ShellAPI,
  13.   ExtCtrls, ComCtrls, ZipMstr19, ZMMsgStr19, ZMMsg19, ZMDelZip19,
  14.   ZMCompat19, SFXBehavior;
  15.  
  16. type
  17.   TOverwriteDecision = (odUndefined, odOverwriteAll, odOverwriteNothing);
  18.  
  19.   TMainForm = class(TForm)
  20.     ProgressBar: TProgressBar;
  21.     WaitLabel: TLabel;
  22.     CancelBtn: TButton;
  23.     CurrentFileLabel: TLabel;
  24.     AutoTimer: TTimer;
  25.     itemBar: TProgressBar;
  26.     procedure CancelBtnClick(Sender: TObject);
  27.     procedure FormCreate(Sender: TObject);
  28.     procedure AutoTimerTimer(Sender: TObject);
  29.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  30.   private
  31.     RenamingOldPrefix: string;
  32.     RenamingNewPrefix: string;
  33.     zb: TZIPBehavior;
  34.     BaseDir: string;
  35.     AbortUnzip: boolean;
  36.     StopAskingPassword: boolean;
  37.     LastTriedPassword: string;
  38.     OverwriteDecision: TOverwriteDecision;
  39.     {$IFNDEF USE_DZIP_UNPACK}
  40.     procedure ExtractDllFromResource(ADirectory: string);
  41.     {$ENDIF}
  42.     procedure ExtractZipHere(AZipfile: string);
  43.     procedure ArcExtFNChange(Sender: TObject; var FileName: TZMString; const BaseDir: TZMString; var IsChanged: Boolean);
  44.     procedure ArcProzess(Sender: TObject; details: TZMProgressDetails);
  45.     procedure ArcTick(Sender: TObject);
  46.     procedure ArcCheckTerminate(Sender: TObject; var abort: Boolean);
  47.     procedure ConfirmOverwrite(Sender: TObject; const ForFile: TZMString;
  48.       IsOlder: Boolean; var DoOverwrite: Boolean; DirIndex: Integer);
  49.     procedure ArcPassword(Sender: TObject; IsZipAction: Boolean;
  50.       var NewPassword: String; const ForFile: TZMString; var RepeatCount: Longword;
  51.       var Action: TMsgDlgBtn);
  52.     procedure SkipEvent(Sender: TObject; const ForFile: TZMString;
  53.       SkipType: TZMSkipTypes; var ExtError: Integer);
  54.     function StripBaseDir(const s: string): string;
  55.   end;
  56.  
  57. var
  58.   MainForm: TMainForm;
  59.  
  60. implementation
  61.  
  62. uses
  63.   ExtractorPassword, ExtractorError, Functions, SFXAutoRun, ExtractorComment;
  64.  
  65. const
  66.   MaxTries = 15;
  67.  
  68. {$R *.dfm}
  69.  
  70. {$R zmstr1900102\DLL\ResDLL-Maker\ZMRes19_dll.res}
  71.  
  72. function IsRootFile(s: string): boolean;
  73. var
  74.   i: integer;
  75. begin
  76.   if s = '' then
  77.   begin
  78.     result := false;
  79.     Exit;
  80.   end;
  81.   if LooksLikeDir(s) then
  82.   begin
  83.     s := Copy(s, 1, Length(s)-1);
  84.   end;
  85.   for i := 1 to Length(s) do
  86.   begin
  87.     if s[i] = PathDelim then
  88.     begin
  89.       result := false;
  90.       Exit;
  91.     end;
  92.   end;
  93.   result := true;
  94. end;
  95.  
  96. function FEListAll(theRec: TZMDirEntry; var Data): Integer;
  97. var
  98.   l: TStringList absolute Data;
  99.   s: string;
  100. begin
  101.   Result := 0;
  102.  
  103.   s := theRec.FileName;
  104.   l.Add(s);
  105.  
  106.   s := ExtractFilePath(s);
  107.   if l.IndexOf(s) = -1 then l.Add(s);
  108. end;
  109.  
  110. procedure ListRootFiles(uz: TZipMaster19; List: TStringList);
  111. var
  112.   s: string;
  113.   l: TStringList;
  114.   i: Integer;
  115. resourcestring
  116.   Lng_ForeachFailed = 'Dateiiteration fehlgeschlagen!';
  117. begin
  118.   List.Clear;
  119.  
  120.   l := TStringList.Create;
  121.   try
  122.     // Fill list (inclusive Directories)
  123.  
  124.     uz.FSpecArgs.Add('*');
  125.     if uz.ForEach(FEListAll, l) <> 0 then
  126.     begin
  127.       MessageDlg(Lng_ForeachFailed, mtError, [mbOk], 0);
  128.     end;
  129.  
  130.     // Now look for root files
  131.  
  132.     for i := 0 to l.Count - 1 do
  133.     begin
  134.       s := l.Strings[i];
  135.  
  136.       if IsRootFile(s) then
  137.       begin
  138.         List.Add(s);
  139.       end;
  140.     end;
  141.   finally
  142.     l.Free;
  143.   end;
  144. end;
  145.  
  146. procedure TMainForm.ExtractZipHere(AZipfile: string);
  147. var
  148.   uz: TZipMaster19;
  149.   l: TStringList;
  150.   s: string;
  151.   ec: Integer;
  152.   ar: TExecuteSFXAutoRunResult;
  153. resourcestring
  154.   Lng_Aborted = 'Der laufende Prozess wurde abgebrochen. Das extrahierten Dateien sind somit unvollständig.';
  155.   Lng_Zip_Error = 'ZIP-Master Fehler "%s" (%d)';
  156.   Lng_AutoRunFailed = 'SFX-AutoRun fehlgeschlagen. Die entpackten Inhalte werden nun angezeigt.';
  157.   Lng_Unknown_Error = 'Unbekannter Fehler: Dateien sind nicht aufzufinden!';
  158. begin
  159.   AZipfile := ExpandUNCFileName(AZipfile);
  160.   RenamingOldPrefix := '';
  161.   RenamingNewPrefix := '';
  162.  
  163.   uz := TZipMaster19.Create(nil);
  164.   try
  165.     {$IFNDEF USE_DZIP_UNPACK}
  166.     uz.DLLDirectory := GetTempDirectory + DelZipDLL_Name;
  167.     {$ENDIF}
  168.     uz.DLL_Load := true;
  169.  
  170.     uz.ZipFileName := AZipFile;
  171.     uz.Active := true;
  172.     zb := ReadBehavior(uz.ZipComment);
  173.  
  174.     uz.Unattended := true;
  175.     uz.ExtrOptions := [ExtrDirNames, ExtrOverWrite, ExtrFreshen, ExtrUpdate,
  176.       ExtrForceDirs, ExtrNTFS];
  177.  
  178.     if zb.ConflictBehavior <> cbAvoid then
  179.     begin
  180.       uz.OnExtractOverwrite := ConfirmOverwrite;
  181.     end;
  182.     uz.OnProgress := ArcProzess;
  183.     uz.OnTick := ArcTick;
  184.     uz.OnCheckTerminate := ArcCheckTerminate;
  185.     uz.OnPasswordError := ArcPassword;
  186.     uz.PasswordReqCount := MaxTries;
  187.     // TODO: Mehr events?
  188.     uz.OnSkipped := SkipEvent;
  189.     uz.OnSetExtName := ArcExtFNChange;
  190.  
  191.     l := TStringList.Create;
  192.     try
  193.       // Count the root objects (files OR dirs) in the ZIP
  194.  
  195.       ListRootFiles(uz, l);
  196.  
  197.       if l.Count = 0 then
  198.       begin
  199.         // Empty ZIP or Extractor.exe was called without ZIP attached
  200.         Exit;
  201.       end
  202.       else if l.Count = 1 then
  203.       begin
  204.         // 1 Object = Extract it right here!
  205.         s := ExtractFilePath(AZipfile) + l.Strings[0];
  206.         BaseDir := ExtractFilePath(AZipfile);
  207.         RenamingOldPrefix := StripBaseDir(S);
  208.         if zb.ConflictBehavior = cbAvoid then
  209.         begin
  210.           s := SearchNextFreeName(s);
  211.         end;
  212.         // TODO: helloworld.exe schlägt fehl!
  213.         RenamingNewPrefix := StripBaseDir(S); // We need to change the name!
  214.       end
  215.       else
  216.       begin
  217.         // 2+ Objects = Extract them in a separate folder
  218.         s := ChangeFileExt(AZipfile, '');
  219.         if zb.ConflictBehavior = cbAvoid then
  220.         begin
  221.           s := SearchNextFreeName(s);
  222.           MkDir(s);
  223.         end
  224.         else
  225.         begin
  226.           if not DirectoryExists(s) then MkDir(s);
  227.         end;
  228.         BaseDir := s;
  229.       end;
  230.       BaseDir := IncludeTrailingPathDelimiter(BaseDir);
  231.  
  232.       uz.ExtrBaseDir := BaseDir; // TODO: andere ordner erlauben
  233.  
  234.       // Pre-Extract-Dialog
  235.  
  236.       if zb.CommentPresentation = cpBeforeExtracting then
  237.       begin
  238.         if not CommentForm.ShowCommentModal(uz.ZipComment) then exit;
  239.       end;
  240.  
  241.       // Extract
  242.  
  243.       ec := uz.Extract;
  244.  
  245.       if ec <> 0 then
  246.       begin
  247.         if ec = DS_Canceled then
  248.         begin
  249.           MessageDlg(Lng_Aborted, mtWarning, [mbOk], 0);
  250.         end
  251.         else
  252.         begin
  253.           MessageDlg(Format(Lng_Zip_Error, [uz.ErrMessage, ec]), mtError, [mbOk], 0);
  254.         end;
  255.       end;
  256.  
  257.       // Errors?
  258.  
  259.       if ErrorForm.ErrorsAvailable then
  260.       begin
  261.         ErrorForm.ShowModal;
  262.       end;
  263.  
  264.       // Show After-Extracting comment?
  265.  
  266.       if zb.CommentPresentation = cpAfterExtracting then
  267.       begin
  268.         if not CommentForm.ShowCommentModal(uz.ZipComment) then exit;
  269.       end;
  270.  
  271.       // Now search for an AutoRun.inf
  272.  
  273.       ar := ExecuteSFXAutoRun(BaseDir);
  274.  
  275.       if ar.AutoRunSectionAvailable and not ar.ExecutionSucceed then
  276.       begin
  277.         MessageDlg(Lng_AutoRunFailed, mtError, [mbOk], 0);
  278.         ar.OpenUnzippedContent := true;
  279.       end;
  280.  
  281.       // Now open the file for the user
  282.  
  283.       if not ar.AutoRunSectionAvailable or ar.OpenUnzippedContent then
  284.       begin
  285.         if DirectoryExists(s) then
  286.         begin
  287.           // If it is a folder, open it
  288.           ShellExecute(0, 'open', 'explorer',
  289.             PChar('"'+s+'"'), '', SW_NORMAL);
  290.         end
  291.         else if FileExists(s) then
  292.         begin
  293.           // If it is a file, then only select it
  294.  
  295.           // Que: Funktioniert das auch ohne "/n"?
  296.           // Im Moment wird bei einem BESTEHENDEN Fenster
  297.           // die Selektion nicht durchgeführt.
  298.  
  299.           ShellExecute(0, 'open', 'explorer',
  300.             PChar('/n,/select,"'+s+'"'), '', SW_NORMAL);
  301.         end
  302.         else
  303.         begin
  304.           MessageDlg(Lng_Unknown_Error, mtError, [mbOk], 0);
  305.         end;
  306.       end;
  307.     finally
  308.       l.Free;
  309.     end;
  310.   finally
  311.     uz.Free;
  312.   end;
  313. end;
  314.  
  315. procedure TMainForm.ArcProzess(Sender: TObject; details: TZMProgressDetails);
  316. begin
  317.   CurrentFileLabel.Caption := details.ItemName;
  318.  
  319.   progressBar.Position := details.TotalPosition;
  320.   progressBar.Max := details.TotalSize;
  321.  
  322.   itemBar.Position := details.ItemPosition;
  323.   itemBar.Max := details.ItemSize;
  324.  
  325.   Application.ProcessMessages;
  326. end;
  327.  
  328. procedure TMainForm.ArcExtFNChange(Sender: TObject;
  329.   var FileName: TZMString; const BaseDir: TZMString;
  330.   var IsChanged: Boolean);
  331. begin
  332.   if RenamingOldPrefix = RenamingOldPrefix then Exit;
  333.  
  334.   FileName := RenamingNewPrefix + Copy(FileName, 1+Length(RenamingOldPrefix), Length(FileName)-Length(RenamingOldPrefix));
  335.   IsChanged := true;
  336. end;
  337.  
  338. procedure TMainForm.ArcTick(Sender: TObject);
  339. begin
  340.   Application.ProcessMessages;
  341. end;
  342.  
  343. procedure TMainForm.ArcCheckTerminate(Sender: TObject; var abort: Boolean);
  344. begin
  345.   abort := AbortUnzip;
  346. end;
  347.  
  348. procedure TMainForm.ConfirmOverwrite(Sender: TObject; const ForFile: TZMString;
  349.   IsOlder: Boolean; var DoOverwrite: Boolean; DirIndex: Integer);
  350. resourcestring
  351.   Lng_Overwrite = 'Bestehende Datei "%s" überschreiben?';
  352. var
  353.   res: Integer;
  354. begin
  355.   if zb.ConflictBehavior = cbOverwrite then
  356.   begin
  357.     DoOverwrite := true;
  358.     Exit;
  359.   end
  360.   else if zb.ConflictBehavior = cbNewer then
  361.   begin
  362.     DoOverwrite := IsOlder; // If file on DISK is older, then overwrite.
  363.     Exit;
  364.   end
  365.   else if zb.ConflictBehavior = cbAsk then
  366.   begin
  367.     // Workaround: Verzeichnisse brauchen das nicht,
  368.     // denn es wird bei den Dateien nochmal nachgefragt
  369.     if LooksLikeDir(ForFile) then
  370.     begin
  371.       DoOverwrite := true;
  372.       Exit;
  373.     end;
  374.  
  375.     if OverwriteDecision = odUndefined then
  376.     begin
  377.       res := MessageDlg(Format(Lng_Overwrite, [ForFile]), mtConfirmation, [mbYes, mbNo, mbYesToAll, mbNoToAll], 0);
  378.       DoOverwrite := (res = mrYes) or (res = mrYesToAll);
  379.       if res = mrNoToAll then OverwriteDecision := odOverwriteNothing;
  380.       if res = mrYesToAll then OverwriteDecision := odOverwriteAll;
  381.     end
  382.     else
  383.     begin
  384.       DoOverwrite := OverwriteDecision = odOverwriteAll;
  385.     end;
  386.   end;
  387. end;
  388.  
  389. procedure TMainForm.ArcPassword(Sender: TObject; IsZipAction: Boolean;
  390.   var NewPassword: String; const ForFile: TZMString; var RepeatCount: Longword;
  391.   var Action: TMsgDlgBtn);
  392. var
  393.   repc: integer;
  394. begin
  395.   repc := MaxTries - RepeatCount + 1;
  396.  
  397.   // Eine Passworteingabe wurde abgebrochen. Frage nicht mehr nach.
  398.   if StopAskingPassword then Exit;
  399.  
  400.   // Wurde schonmal ein Passwort eingegeben?
  401.   if LastTriedPassword <> '' then
  402.   begin
  403.     // Schauen, ob das letzte Passwort auch mit dieser Datei geht.
  404.     if repc = 1 then
  405.     begin
  406.       // Ja, geht
  407.       NewPassword := LastTriedPassword;
  408.       Exit;
  409.     end
  410.     else
  411.     begin
  412.       // Leider nein
  413.       LastTriedPassword := '';
  414.     end;
  415.   end;
  416.  
  417.   if PasswordDlg.ShowModal(StripBaseDir(ForFile), repc, MaxTries) = mrOk then
  418.   begin
  419.     NewPassword := PasswordDlg.Password.Text;
  420.     if NewPassword = '' then NewPassword := ' '; // Neue Eingabe erzwingen.
  421.     LastTriedPassword := NewPassword;
  422.   end
  423.   else
  424.   begin
  425.     StopAskingPassword := true;
  426.     Action := mbCancel;
  427.   end;
  428. end;
  429.  
  430. procedure TMainForm.CancelBtnClick(Sender: TObject);
  431. resourcestring
  432.   Lng_AbortExtract = 'Extrahieren abbrechen?';
  433. begin
  434.   if MessageDlg(Lng_AbortExtract, mtConfirmation, mbYesNoCancel, 0) = mrYes then
  435.   begin
  436.     CancelBtn.Enabled := false;
  437.     AbortUnzip := true;
  438.   end;
  439. end;
  440.  
  441. procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  442. begin
  443.   if not AbortUnzip then
  444.   begin
  445.     CancelBtn.Click;
  446.     CanClose := false;
  447.   end;
  448. end;
  449.  
  450. procedure TMainForm.FormCreate(Sender: TObject);
  451. resourcestring
  452.   Lng_Extracting = 'Extrahiere Dateien. Bitte warten...';
  453. begin
  454.   {$IFDEF DEBUG_MODE}
  455.   Caption := Caption + ' (Debug)';
  456.   {$ENDIF}
  457.   WaitLabel.Caption := Lng_Extracting;
  458.   WaitLabel.Left := progressBar.Width div 2 - WaitLabel.Width div 2;
  459.   CurrentFileLabel.Caption := '';
  460. end;
  461.  
  462. {$IFNDEF USE_DZIP_UNPACK}
  463. procedure TMainForm.ExtractDllFromResource(ADirectory: string);
  464. var
  465.   s: TResourceStream;
  466.   o: TFileStream;
  467.   AOutFile: string;
  468. begin
  469.   AOutFile := IncludeTrailingPathDelimiter(ADirectory) + DelZipDLL_Name;
  470.   s := TResourceStream.Create(0, DZRES_Dll, RT_RCDATA);
  471.   try
  472.     try
  473.       s.Seek(SizeOf(Integer), soFromBeginning); // Ref: ZMDllLoad19.pas:427
  474.       if FileExists(AOutFile) then
  475.         o := TFileStream.Create(AOutFile, fmOpenWrite or fmShareDenyNone)
  476.       else
  477.         o := TFileStream.Create(AOutFile, fmCreate or fmShareDenyNone);
  478.       try
  479.         o.CopyFrom(s, s.Size-s.Position);
  480.       finally
  481.         o.Free;
  482.       end;
  483.     except
  484.       if FileExists(AOutFile) then
  485.       begin
  486.         // Probably the file is write-locked (maybe some other Extractor is
  487.         // using it right now? Even if we run into danger that the target DLL
  488.         // is a write-protected old/incompatible version of DelZip190.dll,
  489.         // we do count this as success, since the file exists.
  490.       end
  491.       else
  492.       begin
  493.         raise;
  494.       end;
  495.     end;
  496.   finally
  497.     s.Free;
  498.   end;
  499. end;
  500. {$ENDIF}
  501.  
  502. procedure TMainForm.AutoTimerTimer(Sender: TObject);
  503. begin
  504.   AutoTimer.Enabled := false;
  505.  
  506.   {$IFNDEF USE_DZIP_UNPACK}
  507.   ExtractDllFromResource(GetTempDirectory);
  508.   {$ENDIF}
  509.  
  510.   try
  511.     {$IFDEF DEBUG_MODE}
  512.     if FileExists(ParamStr(1)) then
  513.     begin
  514.       ExtractZipHere(ParamStr(1));
  515.     end
  516.     else
  517.     begin
  518.     {$ENDIF}
  519.  
  520.     ExtractZipHere(ParamStr(0));
  521.  
  522.     {$IFDEF DEBUG_MODE}
  523.     end;
  524.     {$ENDIF}
  525.   finally
  526.     AbortUnzip := true; // Damit es zu keiner Abfrage kommt
  527.     Close;
  528.   end;
  529. end;
  530.  
  531. function TMainForm.StripBaseDir(const s: string): string;
  532. begin
  533.   // Warnung: Es wird nicht überprüft, ob der String auch
  534.   // wirklich mit dem BaseDir beginnt!
  535.   result := Copy(s, Length(BaseDir)+1, Length(s)-Length(BaseDir));
  536. end;
  537.  
  538. procedure TMainForm.SkipEvent(Sender: TObject; const ForFile: TZMString;
  539.   SkipType: TZMSkipTypes; var ExtError: Integer);
  540. resourcestring
  541.   Lng_PasswordWrong = 'Das Passwort wurde zu oft falsch eingegeben. Die Datei "%s" wird nicht extrahiert.';
  542. begin
  543.   if (SkipType = stBadPassword) and not StopAskingPassword then
  544.   begin
  545.     MessageDlg(Format(Lng_PasswordWrong, [ForFile]), mtError, [mbOk], 0);
  546.     LastTriedPassword := '';
  547.   end;
  548.   ErrorForm.NewError(StripBaseDir(ForFile));
  549. end;
  550.  
  551. end.
  552.