Subversion Repositories autosfx

Rev

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

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