Subversion Repositories autosfx

Rev

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

Rev Author Line No. Line
1 daniel-mar 1
unit ExtractorMain;
2
 
3
{$DEFINE USE_DZIP_UNPACK}
4
 
2 daniel-mar 5
// todo: compilerswitch, der auch selectdirectory() anzeigt (ohne foldercreate)
1 daniel-mar 6
 
7
interface
8
 
9
uses
10
  Windows, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ShellAPI,
11
  ExtCtrls, ComCtrls, ZipMstr19, ZMMsgStr19, ZMMsg19, ZMDelZip19,
2 daniel-mar 12
  ZMCompat19, SFXBehavior, ShlObj;
1 daniel-mar 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;
2 daniel-mar 151
  GeneralBaseDir: string;
1 daniel-mar 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!';
2 daniel-mar 157
  Lng_SelectDir = 'Bitte wählen Sie ein Verzeichnis zum Extrahieren aus. Es wird maximal 1 Datei bzw. Ordner erstellt!';
1 daniel-mar 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;
2 daniel-mar 189
    // TODO: Mehr events?
1 daniel-mar 190
 
2 daniel-mar 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
 
1 daniel-mar 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!
2 daniel-mar 229
        BaseDir := GeneralBaseDir;
230
        s := BaseDir + l.Strings[0];
231
 
232
        RenamingOldPrefix := l.Strings[0]; // = StripBaseDir(S);
233
 
1 daniel-mar 234
        if zb.ConflictBehavior = cbAvoid then
235
        begin
2 daniel-mar 236
          s := SearchNextFreeName(s, LooksLikeDir(s));
1 daniel-mar 237
        end;
2 daniel-mar 238
 
239
        RenamingNewPrefix := StripBaseDir(s);
1 daniel-mar 240
      end
241
      else
242
      begin
243
        // 2+ Objects = Extract them in a separate folder
2 daniel-mar 244
        s := GeneralBaseDir + ExtractFileNameWithoutExt(AZipfile) + PathDelim;
1 daniel-mar 245
        if zb.ConflictBehavior = cbAvoid then
246
        begin
2 daniel-mar 247
          s := SearchNextFreeName(s, true);
1 daniel-mar 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
 
2 daniel-mar 258
      uz.ExtrBaseDir := BaseDir;
1 daniel-mar 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
2 daniel-mar 358
  if RenamingOldPrefix = RenamingNewPrefix then Exit;
1 daniel-mar 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);
2 daniel-mar 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!';
1 daniel-mar 529
begin
530
  AutoTimer.Enabled := false;
531
 
532
  {$IFNDEF USE_DZIP_UNPACK}
533
  ExtractDllFromResource(GetTempDirectory);
534
  {$ENDIF}
535
 
536
  try
2 daniel-mar 537
    if IsExtractable(ParamStr(0)) then
1 daniel-mar 538
    begin
2 daniel-mar 539
      ExtractZipHere(ParamStr(0));
1 daniel-mar 540
    end
541
    else
542
    begin
2 daniel-mar 543
      // Der Extractor ist "nackt" oder das SFX beschädigt
1 daniel-mar 544
 
2 daniel-mar 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;
1 daniel-mar 566
    end;
567
  finally
2 daniel-mar 568
    AbortUnzip := true; // Damit es zu keiner Abfrage in OnCloseQuery kommt
1 daniel-mar 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.