⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 undo.pas

📁 源代码
💻 PAS
📖 第 1 页 / 共 3 页
字号:
unit Undo;

{
  Inno Setup
  Copyright (C) 1997-2004 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  Uninstallation Procedures

  $jrsoftware: issrc/Projects/Undo.pas,v 1.64 2004/10/27 22:21:31 jr Exp $
}

{ Note: This unit is shared by both the 'Setup' and 'Uninst' projects }

interface

{$I VERSION.INC}

uses
  Windows, SysUtils, FileClass;

const
  HighestSupportedVersion = 33;
  { Each time the format of the uninstall log changes (usually a new entry type
    is added), I increment HighestSupportedVersion and the version info in
    Uninst.res to match (51.x). Do NOT do this yourself; doing so could cause
    incompatibilies with future Inno Setup releases. It's recommended that you
    use the "utUserDefined" log entry type if you wish to implement your own
    custom uninstall log entries; see below for more information. }

type
  TUninstallRecTyp = type Word;
const
  { Values for TUninstallRecTyp.
    If you wish to define your own custom uninstall entry type, you should use
    "utUserDefined". (Do NOT define your own ut* constants; this could cause
    incompatibilies with future Inno Setup releases.) The first field in a
    utUserDefined record must be a string which specifies a unique name for
    the record type. Example:
    UninstLog.Add(utUserDefined, ['MyRecordType', ... ], 0);
 }
  utUserDefined          = $01;
  utStartInstall         = $10;
  utEndInstall           = $11;
  utCompiledCode         = $20;
  utRun                  = $80;
  utDeleteDirOrFiles     = $81;
  utDeleteFile           = $82;
  utDeleteGroupOrItem    = $83;
  utIniDeleteEntry       = $84;
  utIniDeleteSection     = $85;
  utRegDeleteEntireKey   = $86;
  utRegClearValue        = $87;
  utRegDeleteKeyIfEmpty  = $88;
  utRegDeleteValue       = $89;
  utDecrementSharedCount = $8A;
  utRefreshFileAssoc     = $8B;
  utMutexCheck           = $8C;

  ValidUninstallRecTypes: array[0..13] of TUninstallRecTyp = (
    utUserDefined, utStartInstall, utEndInstall, utRun, utDeleteDirOrFiles,
    utDeleteFile, utDeleteGroupOrItem, utIniDeleteEntry, utIniDeleteSection,
    utRegDeleteEntireKey, utRegClearValue, utRegDeleteKeyIfEmpty,
    utRegDeleteValue, utDecrementSharedCount);

  { Flags on ExtraData }
  utRun_NoWait = 1;
  utRun_WaitUntilIdle = 2;
  utRun_ShellExec = 4;
  utRun_RunMinimized = 8;
  utRun_RunMaximized = 16;
  utRun_SkipIfDoesntExist = 32;
  utRun_RunHidden = 64;
  utRun_ShellExecRespectWaitFlags = 128;
  utDeleteFile_ExistedBeforeInstall = 1;
  utDeleteFile_Extra = 2;
  utDeleteFile_IsFont = 4;
  utDeleteFile_SharedFile = 8;
  utDeleteFile_RegisteredServer = 16;
  utDeleteFile_CallChangeNotify = 32;
  utDeleteFile_RegisteredTypeLib = 64;
  utDeleteFile_RestartDelete = 128;
  utDeleteFile_RemoveReadOnly = 256;
  utDeleteFile_NoSharedFilePrompt = 512;
  utDeleteDirOrFiles_Extra = 1;
  utDeleteDirOrFiles_IsDir = 2;
  utDeleteDirOrFiles_DeleteFiles = 4;
  utDeleteDirOrFiles_DeleteSubdirsAlso = 8;
  utDeleteDirOrFiles_CallChangeNotify = 16;
  utIniDeleteSection_OnlyIfEmpty = 1;

const
  UninstallRecNonDataSize =
    (SizeOf(Pointer) * 2) + SizeOf(Longint) + SizeOf(Cardinal) + SizeOf(TUninstallRecTyp);
type
  PUninstallRec = ^TUninstallRec;
  TUninstallRec = packed record
    {if this is modified, you must to update UninstallRecNonDataSize above}
    Prev, Next: PUninstallRec;
    ExtraData: Longint;
    DataSize: Cardinal;
    Typ: TUninstallRecTyp;
    Data: array[0..$6FFFFFFF] of Char;
  end;

  TDeleteUninstallDataFilesProc = procedure;

  TUninstallLogFlags = set of (ufAdminInstalled, ufDontCheckRecCRCs,
    ufModernStyle, ufAlwaysRestart, ufChangesEnvironment);

  TUninstallLog = class
  private
    FList, FLastList: PUninstallRec;
    FCount: Integer;
    function Delete(const Rec: PUninstallRec): PUninstallRec;
    procedure InternalAdd(const Typ: TUninstallRecTyp; const Data: Pointer;
      const Size: Cardinal; const ExtraData: Longint);
  protected
    procedure HandleException; virtual; abstract;
    function ShouldRemoveSharedFile(const Filename: String): Boolean; virtual;
    procedure StatusUpdate(StartingCount, CurCount: Integer); virtual;
  public
    AppId, AppName: String;
    NeedRestart: Boolean;
    Flags: TUninstallLogFlags;
    Version: Integer;
    constructor Create;
    destructor Destroy; override;
    procedure Add(const Typ: TUninstallRecTyp; const Data: array of String;
      const ExtraData: Longint);
    function CheckMutexes: Boolean;
    procedure Clear;
    class function ExtractRecData(const Rec: PUninstallRec;
      var Data: array of String): Integer;
    function ExtractLatestRecData(const Typ: TUninstallRecTyp;
      const ExtraData: Longint; var Data: array of String): Boolean;
    procedure Load(const F: TFile; const Filename: String);
    function PerformUninstall(const CallFromUninstaller: Boolean;
      const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
    procedure Save(const Filename: String;
      const Append, UpdateUninstallLogAppName: Boolean);
    function Test(const Filename, AAppId: String): Boolean;
    property List: PUninstallRec read FList;
    property LastList: PUninstallRec read FLastList;
  end;

implementation

uses
  Messages, ShlObj,
  PathFunc, CmnFunc2, Struct, Msgs, MsgIDs, InstFunc, InstFnc2, Compress,
  Logging;

type
  { Note: TUninstallLogHeader should stay <= 512 bytes in size, so that it
    fits into a single disk sector and can be written atomically }
  TUninstallLogHeader = packed record
    ID: TUninstallLogID;
    AppId: array[0..127] of Char;
    AppName: array[0..127] of Char;
    Version, NumRecs: Integer;
    EndOffset: Cardinal;
    Flags: Longint;
    Reserved: array[0..26] of Longint;  { reserved for future use }
    CRC: Longint;
  end;
  TUninstallCrcHeader = packed record
    Size, NotSize: Cardinal;
    CRC: Longint;
  end;
  TUninstallFileRec = packed record
    Typ: TUninstallRecTyp;
    ExtraData: Longint;
    DataSize: Cardinal;
  end;

{ Misc. uninstallation functions }

function ListContainsPathOrSubdir(const List: TSimpleStringList;
  const Path: String): Boolean;
{ Returns True if List contains Path or a subdirectory of Path }
var
  SlashPath: String;
  SlashPathLen, I: Integer;
begin
  SlashPath := AddBackslash(Path);
  SlashPathLen := Length(SlashPath);
  if SlashPathLen > 0 then begin   { ...sanity check }
    for I := 0 to List.Count-1 do begin
      if List[I] = Path then begin
        Result := True;
        Exit;
      end;
      if (Length(List[I]) > SlashPathLen) and
         CompareMem(Pointer(List[I]), Pointer(SlashPath), SlashPathLen * SizeOf(Char)) then begin
        Result := True;
        Exit;
      end;
    end;
  end;
  Result := False;
end;

function DeleteDir(const DirName: String;
  const DirsNotRemoved, RestartDeleteDirList: TSimpleStringList): Boolean;
var
  Attribs, LastError: DWORD;
begin
  Attribs := GetFileAttributes(PChar(DirName));
  { Does the directory exist? }
  if (Attribs <> $FFFFFFFF) and
     (Attribs and FILE_ATTRIBUTE_DIRECTORY <> 0) then begin
    LogFmt('Deleting directory: %s', [DirName]);
    { If the directory has the read-only attribute, strip it first }
    if Attribs and FILE_ATTRIBUTE_READONLY <> 0 then begin
      if IsDirEmpty(DirName) then begin
        if SetFileAttributes(PChar(DirName), Attribs and not FILE_ATTRIBUTE_READONLY) then
          Log('Stripped read-only attribute.')
        else
          Log('Failed to strip read-only attribute.');
      end
      else
        Log('Not stripping read-only attribute because the directory ' +
          'does not appear to be empty.');
    end;
    Result := RemoveDirectory(PChar(DirName));
    if not Result then begin
      LastError := GetLastError;
      if Assigned(DirsNotRemoved) then begin
        LogFmt('Failed to delete directory (%d). Will retry later.', [LastError]);
        DirsNotRemoved.AddIfDoesntExist(DirName);
      end
      else if Assigned(RestartDeleteDirList) and
         ListContainsPathOrSubdir(RestartDeleteDirList, DirName) and
         (Win32Platform = VER_PLATFORM_WIN32_NT) then begin
        { Note: This code is NT-only; I don't think it's possible to
          restart-delete directories on Windows 9x. }
        LogFmt('Failed to delete directory (%d). Will delete on restart (if empty).',
          [LastError]);
        if not MoveFileEx(PChar(DirName), nil, MOVEFILE_DELAY_UNTIL_REBOOT) then
          LogFmt('MoveFileEx failed (%d).', [GetLastError]);
      end
      else
        LogFmt('Failed to delete directory (%d).', [LastError]);
    end;
  end
  else
    Result := True;
end;

function IsSectionEmpty(const Section, Filename: PChar): Boolean;
var
  Test: array[0..255] of Char;
begin
  Test[0] := #0;
  if Filename^ <> #0 then
    GetPrivateProfileString(Section, nil, '', Test, SizeOf(Test), Filename)
  else
    GetProfileString(Section, nil, '', Test, SizeOf(Test));
  Result := Test[0] = #0;
end;

{ TUninstallLog }

constructor TUninstallLog.Create;
begin
  inherited Create;
  Clear;
end;

destructor TUninstallLog.Destroy;
begin
  Clear;
  inherited Destroy;
end;

procedure TUninstallLog.InternalAdd(const Typ: TUninstallRecTyp;
  const Data: Pointer; const Size: Cardinal; const ExtraData: Longint);
{ Adds a new entry to the uninstall list }
var
  NewRec: PUninstallRec;
begin
  NewRec := AllocMem(UninstallRecNonDataSize + Size);
  NewRec^.ExtraData := ExtraData;
  NewRec^.Typ := Typ;
  NewRec^.DataSize := Size;
  Move(Data^, NewRec^.Data, Size);
  if List = nil then begin
    FList := NewRec;
    FLastList := List;
  end
  else begin
    LastList^.Next := NewRec;
    NewRec^.Prev := LastList;
    FLastList := NewRec;
  end;
  Inc(FCount);
end;

procedure TUninstallLog.Add(const Typ: TUninstallRecTyp; const Data: array of String;
  const ExtraData: Longint);
var
  I, L: Integer;
  S, X: String;
begin
  for I := 0 to High(Data) do begin
    L := Length(Data[I]);
    if L < $FD then
      S := S + Char(L)
    else if L <= $FFFF then begin
      SetLength(X, SizeOf(Byte) + SizeOf(Word));
      X[1] := #$FD;
      Word((@X[2])^) := Word(L);
      S := S + X;
    end
    else begin
      SetLength(X, SizeOf(Byte) + SizeOf(Integer));
      X[1] := #$FE;
      Integer((@X[2])^) := Integer(L);
      S := S + X;
    end;
    S := S + Data[I];
  end;
  S := S + #$FF;
  InternalAdd(Typ, PChar(S), Length(S), ExtraData);

  if Version < HighestSupportedVersion then
    Version := HighestSupportedVersion;
end;

function TUninstallLog.Delete(const Rec: PUninstallRec): PUninstallRec;
{ Removes Rec from the linked list, then frees it. Returns (what was) the
  previous record, or nil if there is none. }
begin
  Result := Rec.Prev;
  if Assigned(Rec.Prev) then
    Rec.Prev.Next := Rec.Next;
  if Assigned(Rec.Next) then
    Rec.Next.Prev := Rec.Prev;
  if FList = Rec then
    FList := Rec.Next;
  if FLastList = Rec then
    FLastList := Rec.Prev;
  Dec(FCount);
  FreeMem(Rec);
end;

procedure TUninstallLog.Clear;
{ Frees all entries in the uninstall list and clears AppName/AppDir }
begin
  while FLastList <> nil do
    Delete(FLastList);
  FCount := 0;
  AppId := '';
  AppName := '';
  Flags := [];
end;

type
  PDeleteDirData = ^TDeleteDirData;
  TDeleteDirData = record
    DirsNotRemoved: TSimpleStringList;
  end;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -