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