Refactor build updater once again:

* convert to console application without window, so the user sees all status messages in the order of appearance
* include SysUtils unit, which increases file size to ~2MB, but enhances maintainability
* restrict compilation and resource inclusion to 64bit mode - the build updater is disabled in 32bit mode anyway
This commit is contained in:
Ansgar Becker
2022-12-31 15:06:23 +01:00
parent b767340ba7
commit d77485160c
11 changed files with 987 additions and 306 deletions

View File

@@ -2,8 +2,10 @@ brcc32 res\version.rc
cgrc res\icon.rc
brcc32 res\icon-question.rc
brcc32 res\manifest.rc
brcc32 -fores\updater.res res\updater64.rc
brcc32 res\updater.rc
cgrc.exe res\styles.rc
brcc32.exe source\vcl-styles-utils\AwesomeFont.rc
brcc32.exe source\vcl-styles-utils\AwesomeFont_zip.rc
brcc32.exe res\updater\manifest.rc
pause

View File

@@ -62,7 +62,7 @@ uses
{$R ..\..\res\icon-question.RES}
{$R ..\..\res\version.RES}
{$R ..\..\res\manifest.RES}
{$R ..\..\res\updater.RES}
{$IFDEF CPUX64}{$R ..\..\res\updater.RES}{$ENDIF}
{$R ..\..\res\styles.RES}
var

1
res/updater.rc Normal file
View File

@@ -0,0 +1 @@
updater exe "updater\updater.exe"

View File

@@ -6,7 +6,7 @@
processorArchitecture="*"
name="HeidiSQL"
type="win32" />
<description>A lightweight, fast and flexible interface to MySQL</description>
<description>HeidiSQL build updater</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>

View File

@@ -1,119 +1,25 @@
program updater;
{ A window which terminates running HeidiSQL instances and moves the downloaded update file to
its desired directory. Avoids to use any VCL unit, to keep the executable small. }
{ A console window which terminates running HeidiSQL instances and moves the downloaded update file to
its desired directory. }
{$APPTYPE CONSOLE}
{$R manifest.RES}
// (un)comment the following resource inclusion to vary the binary size. Update checker trusts the same file size before overwriting the old one.
{$R ..\icon.RES}
uses
Windows, Messages, Tlhelp32, psapi, Winapi.ShellAPI;
Winapi.Windows, Winapi.Messages, Winapi.TlHelp32, Winapi.PsAPI, Winapi.ShellAPI, System.SysUtils;
var
WClass: TWndClass;
BackupPath, AppPath, DownloadPath: String;
MainWin, HLabel: HWND;
AppMsg: TMsg;
FormShowing: Boolean = False;
RestartTaskName, RestartCmd, RestartParams: String;
const
AppName = 'HeidiSQL';
WindowPadding = 10;
WindowWidth = 600;
WindowHeight = 80;
QuitTimeout = 20000; // We long we're gracefully waiting for a window to be gone, in milliseconds
TerminatedCheck = 200; // Interval between checks if host application is gone
PathDelim = '\';
{ We don't include SysUtils unit, so we need to implement our own versions of some basic functions here }
function FileExists(Filename: String): Boolean;
var
Find: THandle;
Data: TWin32FindData;
begin
Find := FindFirstFile(PChar(Filename), Data);
Result := Find <> INVALID_HANDLE_VALUE;
end;
function IntToStr(Value: Int64): String;
var
Minus : Boolean;
begin
Result := '';
if Value = 0 then
Result := '0';
Minus := Value < 0;
if Minus then
Value := -Value;
while Value > 0 do begin
Result := Char((Value mod 10) + Integer('0')) + Result;
Value := Value div 10;
end;
if Minus then
Result := '-' + Result;
end;
function ExtractFileName(const FileName: string): string;
var
i: Integer;
begin
for i:=Length(Filename) downto 0 do
if Filename[i] = PathDelim then
break;
Result := Copy(FileName, i+1, MaxInt);
end;
function ExtractFilePath(const FileName: string): string;
var
i: Integer;
begin
for i:=Length(Filename) downto 0 do
if Filename[i] = PathDelim then
break;
Result := Copy(FileName, 1, i);
end;
{ Version of ValidFilename in main project, without usage of large IOutils SysUtils units
function ValidFilename(Str: String): String;
var
InvChar: Char;
i: Integer;
InvalidFileNameChars: TArray<Char>;
begin
Result := Str;
InvalidFileNameChars := TArray<Char>.Create(
#0, #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12,
#13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24,
#25, #26, #27, #28, #29, #30, #31,
'"', '*', '/', ':', '<', '>', '?', '\', '|');
for InvChar in InvalidFileNameChars do begin
for i := 1 to Length(Str) do begin
if Result[i] = InvChar then
Result[i] := '_';
end;
end;
end; }
function Trim(const S: string): string;
var
I, L: Integer;
begin
L := Length(S);
I := 1;
if (L > 0) and (S[I] > ' ') and (S[L] > ' ') then Exit(S);
while (I <= L) and (S[I] <= ' ') do Inc(I);
if I > L then Exit('');
while S[L] <= ' ' do Dec(L);
Result := Copy(S, I, L - I + 1);
end;
TerminatedCheck = 500; // Interval between checks if host application is gone
function GetEXEFromHandle(const wnd: HWND) : string;
var
@@ -169,12 +75,14 @@ end;
procedure Status(Text: String; IsError: Boolean=False);
const
SleepSecondsOnError: Integer=10;
begin
// Display status message on label
SendMessage(HLabel, WM_SETTEXT, 0, LPARAM(PChar(Text)) );
UpdateWindow(hLabel);
// Display status message
WriteLn(Text);
if IsError then begin
Sleep(4000);
Writeln('Exiting in '+SleepSecondsOnError.ToString+' seconds...');
Sleep(SleepSecondsOnError * 1000);
Halt(1);
end;
end;
@@ -189,7 +97,7 @@ begin
// EnumWindows will stop processing if we return false
Result := True;
WndPath := GetEXEFromHandle(Wnd);
if (WndPath <> AppPath) and (WndPath <> ExtractFilename(AppPath)) then
if (LowerCase(WndPath) <> LowerCase(AppPath)) and (LowerCase(WndPath) <> LowerCase(ExtractFilename(AppPath))) then
Exit;
SetLength(WndTitle, 256);
@@ -216,146 +124,54 @@ begin
end;
end;
// Callback function for Timer
procedure FormShow(wnd: HWND; uMsg: UINT; idEvent: UINT; dwTime: DWORD); stdcall;
var
RestartTaskName, RestartCmd, RestartParams: String;
// Main program code
begin
FormShowing := True;
KillTimer(MainWin, 0);
AppPath := Paramstr(1);
DownloadPath := ParamStr(2);
RestartTaskName := ParamStr(3);
try
AppPath := Paramstr(1);
DownloadPath := ParamStr(2);
RestartTaskName := ParamStr(3);
// Paremeter syntax check
if (AppPath = '') or (DownloadPath = '') or (RestartTaskName = '') then begin
Status('Syntax: '+ExtractFilename(Paramstr(0))+' OldFile.exe NewFile.exe RestartTaskName'+#13#10+
'Please don''t execute this file directly.', True);
end;
if (not FileExists(AppPath)) or (not FileExists(DownloadPath)) then
Status('Error: Either target file "'+AppPath+'" or download file "'+DownloadPath+'" does not exist.', True);
// Parameter syntax check
if (AppPath = '') or (DownloadPath = '') or (RestartTaskName = '') then begin
Status('Syntax: '+ExtractFilename(Paramstr(0))+' OldFile.exe NewFile.exe RestartTaskName'+#13#10+
'Please don''t execute this file directly.', True);
end;
if (not FileExists(AppPath)) or (not FileExists(DownloadPath)) then
Status('Error: Either target file "'+AppPath+'" or download file "'+DownloadPath+'" does not exist.', True);
// Terminate running instances
Status('Close running '+AppName+' instances ...');
EnumWindows(@EnumAllInstances, 0);
// Terminate running instances
Status('Close running '+AppName+' instances ...');
EnumWindows(@EnumAllInstances, 0);
// Backup old .exe to working directory
Status('Creating backup of old file ...');
BackupPath := ExtractFilepath(Paramstr(0))+ExtractFilename(AppPath)+'.backup.exe';
if FileExists(BackupPath) then
DeleteFile(PChar(BackupPath));
if not CopyFile(PChar(AppPath), PChar(BackupPath), False) then
Status('Failed to create backup file "'+BackupPath+'" from "'+AppPath+'"', True)
else begin
DeleteFile(PChar(AppPath));
Status('Success.');
end;
// Move update file to final path
Status('Moving downloaded file to desired directory ...');
// Issue #1616: MoveFile() does not work when target directory is a symlink, so we prefer CopyFile + DeleteFile
if not CopyFile(PChar(DownloadPath), PChar(AppPath), False) then
Status('Failed to copy file "'+DownloadPath+'" to "'+AppPath+'"', True)
else begin
DeleteFile(PChar(DownloadPath));
Status('Success. Restarting '+AppName+' through task "'+RestartTaskName+'" now ...');
RestartCmd := 'schtasks';
RestartParams := '/Run /TN ' + RestartTaskName;
ShellExecute(0, 'open', PChar(RestartCmd), PChar(RestartParams), '', SW_HIDE);
end;
PostQuitMessage(0);
end;
function WindowProc(hWnd: HWND; msg: UINT; wpr: WPARAM; lpr: LPARAM): LRESULT; stdcall;
var
x, y: integer;
Font: HFont;
begin
// Custom window procedure
case msg of
// WM_NCCREATE: Non-client part of the window is being created
WM_CREATE: begin
// Center window
x := GetSystemMetrics(SM_CXSCREEN);
y := GetSystemMetrics(SM_CYSCREEN);
MoveWindow(hWnd,
(x div 2) - (WindowWidth div 2),
(y div 2) - (WindowHeight div 2),
WindowWidth,
WindowHeight,
true);
// Create status label
HLabel := CreateWindow(
'STATIC', // Class name
'Status:', // Label's text
WS_VISIBLE or WS_CHILD or SS_LEFT, // Styles
WindowPadding, // X pos
WindowPadding, // Y pos
WindowWidth - 2*WindowPadding, // Width
WindowHeight - 2*WindowPadding, // Height
hWnd, // Parent hwnd
0, // ID
MainWin, // HInstance of program
nil // Params for main window
);
// Cosmetics
Font := Createfont(-11, 0, 0, 0, 0, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, 'Tahoma');
SendMessage(HLabel, WM_SETFONT, Font, 1);
SetBkColor(hwnd, COLOR_BTNFACE+1);
// Backup old .exe to working directory
Status('Creating backup of old file ...');
BackupPath := ExtractFilepath(Paramstr(0))+ExtractFilename(AppPath)+'.backup.exe';
if FileExists(BackupPath) then
DeleteFile(PChar(BackupPath));
if not CopyFile(PChar(AppPath), PChar(BackupPath), False) then
Status('Failed to create backup file "'+BackupPath+'" from "'+AppPath+'"', True)
else begin
DeleteFile(PChar(AppPath));
Status('Success.');
end;
WM_SHOWWINDOW: begin
if not FormShowing then
SetTimer(MainWin, 0, 200, @FormShow);
end;
WM_DESTROY: begin
PostQuitMessage(0);
// Move update file to final path
Status('Moving downloaded file to desired directory ...');
// Issue #1616: MoveFile() does not work when target directory is a symlink, so we prefer CopyFile + DeleteFile
if not CopyFile(PChar(DownloadPath), PChar(AppPath), False) then
Status('Failed to copy file "'+DownloadPath+'" to "'+AppPath+'"', True)
else begin
DeleteFile(PChar(DownloadPath));
Status('Success. Restarting '+AppName+' through task "'+RestartTaskName+'" now ...');
RestartCmd := 'schtasks';
RestartParams := '/Run /TN ' + RestartTaskName;
ShellExecute(0, 'open', PChar(RestartCmd), PChar(RestartParams), '', SW_HIDE);
end;
Status('Exiting in 10 seconds...');
Sleep(10000);
except
on E: Exception do
Status(E.ClassName + ': ' + E.Message, True);
end;
Result := DefWindowProc(hWnd, msg, wpr, lpr);
end;
// Main program goes here
begin
// Define window class
WClass.hInstance := hInstance;
WClass.lpszClassName := 'WndClass';
WClass.style := CS_HREDRAW or CS_VREDRAW;
WClass.hIcon := LoadIcon(hInstance, IDI_WINLOGO);
WClass.lpfnWndProc := @WindowProc;
WClass.hbrBackground := COLOR_BTNFACE+1;
WClass.hCursor := LoadCursor(0, IDC_ARROW);
WClass.cbClsExtra := 0;
WClass.cbWndExtra := 0;
WClass.lpszMenuName := '';
RegisterClass(WClass);
// Create form
MainWin := CreateWindow(
WClass.lpszClassName,
AppName+' Updater',
WS_POPUPWINDOW or WS_CAPTION or WS_VISIBLE,
100, // Default x + y coordinates, will be centered in WM_CREATE
100,
WindowWidth, // nWidth
WindowHeight, // nHeight
0, // hWndParent
0, // hMenu
hInstance, // hInstance
nil // lpParam
);
// Message loop
while GetMessage(AppMsg, 0, 0, 0) do begin
TranslateMessage(AppMsg);
DispatchMessage(AppMsg);
end;
ExitCode := AppMsg.wParam;
end.

View File

File diff suppressed because it is too large Load Diff

BIN
res/updater/updater.exe Normal file
View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1 +0,0 @@
updater exe "updater32.exe"

View File

Binary file not shown.

View File

@@ -1 +0,0 @@
updater exe "updater64.exe"