feat(windows): Replace http downloader and bug fixes

Uses THTTPClient standard library instead of (very old) THTTPUploader,
and prepare to remove UfrmDownloadProgress in a future PR.
This commit is contained in:
Marc Durdin 2020-07-06 09:15:03 +10:00
parent 994ab02292
commit 967f8df353
7 changed files with 117 additions and 105 deletions

View file

@ -13,8 +13,11 @@ type
implementation
uses
System.Net.HttpClient,
System.Net.URLClient,
System.SysUtils,
GlobalProxySettings,
httpuploader,
Keyman.System.UpdateCheckResponse,
KeymanVersion,
PackageInfo,
@ -23,7 +26,7 @@ uses
class procedure TOnlineResourceCheck.QueryServer(ASilent: Boolean; AInstallInfo: TInstallInfo);
var
http: THTTPUploader;
http: THTTPClient;
pack: TInstallInfoPackage;
ucr: TUpdateCheckResponse;
ucrpack: TUpdateCheckResponsePackage;
@ -32,71 +35,65 @@ var
currentVersion: string;
lang: TUpdateCheckResponseLanguage;
iipl: TInstallInfoPackageLanguage;
url: TURI;
response: IHTTPResponse;
u: AnsiString;
begin
currentVersion := AInstallInfo.MsiLocations.LatestVersion(SKeymanVersion_Min_Evergreen);
http := THTTPUploader.Create(nil);
url := TURI.Create(MakeAPIURL(API_Path_UpdateCheck_Windows));
url.AddParameter('version', currentVersion);
url.AddParameter('tier', KeymanVersion.CKeymanVersionInfo.Tier);
for pack in AInstallInfo.Packages do
url.AddParameter('package_'+pack.ID, pack.Locations.LatestVersion);
http := THTTPClient.Create;
try
http.ShowUI := not ASilent;
http.Fields.Add('version', ansistring(currentVersion));
// TODO: allow override of this tier with a command line parameter or filename rename
http.Fields.Add('tier', ansistring(KeymanVersion.CKeymanVersionInfo.Tier));
for pack in AInstallInfo.Packages do
http.Fields.Add(ansistring('package_'+pack.ID), ansistring(pack.Locations.LatestVersion));
http.Proxy.Server := GetProxySettings.Server;
http.Proxy.Port := GetProxySettings.Port;
http.Proxy.Username := GetProxySettings.Username;
http.Proxy.Password := GetProxySettings.Password;
http.Request.HostName := API_Server;
http.Request.Protocol := API_Protocol;
http.Request.UrlPath := API_Path_UpdateCheck_Windows;
http.Upload;
if http.Response.StatusCode <> 200 then
response := http.Get(url.ToString);
if response.StatusCode <> 200 then
begin
// TODO: log failed response
//raise EOnlineUpdateCheck.Create('Error '+IntToStr(Response.StatusCode));
Exit;
end;
if ucr.Parse(http.Response.MessageBodyAsString, 'msi', currentVersion) then
begin
for ucrpack in ucr.Packages do
begin
pack := AInstallInfo.Packages.FindById(ucrpack.ID, False);
if Assigned(pack) then
begin
packLocation := TInstallInfoPackageFileLocation.Create(iilOnline);
pack.Locations.Add(packLocation);
packLocation.Name := ucrpack.Name;
packLocation.Path := ucrpack.FileName;
packLocation.URL := ucrpack.DownloadURL;
packLocation.Version := ucrpack.NewVersion;
packLocation.Size := ucrpack.DownloadSize;
for lang in ucrpack.Languages do
begin
iipl := TInstallInfoPackageLanguage.Create(lang.ID, lang.displayName);
packLocation.Languages.Add(iipl);
end;
end;
end;
if ucr.Status = ucrsUpdateReady then
begin
location := TInstallInfoFileLocation.Create(iilOnline);
location.URL := ucr.InstallURL;
location.Path := ucr.FileName;
location.Version := ucr.NewVersion;
location.Size := ucr.InstallSize;
AInstallInfo.MsiLocations.Add(location);
end;
end;
u := UTF8Encode(Response.ContentAsString(TEncoding.UTF8));
finally
http.Free;
end;
if ucr.Parse(u, 'msi', currentVersion) then
begin
for ucrpack in ucr.Packages do
begin
pack := AInstallInfo.Packages.FindById(ucrpack.ID, False);
if Assigned(pack) then
begin
packLocation := TInstallInfoPackageFileLocation.Create(iilOnline);
pack.Locations.Add(packLocation);
packLocation.Name := ucrpack.Name;
packLocation.Path := ucrpack.FileName;
packLocation.URL := ucrpack.DownloadURL;
packLocation.Version := ucrpack.NewVersion;
packLocation.Size := ucrpack.DownloadSize;
for lang in ucrpack.Languages do
begin
iipl := TInstallInfoPackageLanguage.Create(lang.ID, lang.displayName);
packLocation.Languages.Add(iipl);
end;
end;
end;
if ucr.Status = ucrsUpdateReady then
begin
location := TInstallInfoFileLocation.Create(iilOnline);
location.URL := ucr.InstallURL;
location.Path := ucr.FileName;
location.Version := ucr.NewVersion;
location.Size := ucr.InstallSize;
AInstallInfo.MsiLocations.Add(location);
end;
end;
end;
end.

View file

@ -12,11 +12,14 @@ type
FDownloadURL: string;
FDownloadFilename: string;
FInstallInfo: TInstallInfo;
frmDownloadProgress: TfrmDownloadProgress;
function DownloadFile(const ADownloadURL,
ADownloadFilename: string): Boolean;
procedure DownloadFileCallback(AOwner: TfrmDownloadProgress;
var Result: Boolean);
constructor Create(AInstallInfo: TInstallInfo);
procedure HttpReceiveData(const Sender: TObject; AContentLength,
AReadCount: Int64; var Abort: Boolean);
public
class function Execute(AInstallInfo: TInstallInfo; ALocation: TInstallInfoFileLocation): Boolean;
end;
@ -25,10 +28,11 @@ implementation
uses
System.Classes,
System.Net.HttpClient,
System.SysUtils,
Vcl.Controls,
Winapi.Windows,
httpuploader,
RunTools,
SetupStrings,
Upload_Settings;
@ -68,54 +72,57 @@ begin
FDownloadFilename := ADownloadFilename;
{ Download the redistributable }
with TfrmDownloadProgress.Create(nil) do
frmDownloadProgress := TfrmDownloadProgress.Create(nil);
try
Caption := 'Downloading '+ExtractFileName(ADownloadFilename); // TODO: localize
Callback := DownloadFileCallback;
Result := ShowModal = mrOk;
frmDownloadProgress.Caption := 'Downloading '+ExtractFileName(ADownloadFilename); // TODO: localize
frmDownloadProgress.Callback := DownloadFileCallback;
Result := frmDownloadProgress.ShowModal = mrOk;
finally
Free;
frmDownloadProgress.Free;
end;
end;
procedure TResourceDownloader.HttpReceiveData(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
begin
// TODO: stop using this form and report back to main form instead
frmDownloadProgress.HTTPStatus(nil, 'Downloading '+ExtractFileName(FDownloadFilename), AReadCount, AContentLength);
frmDownloadProgress.HTTPCheckCancel(nil, Abort);
end;
procedure TResourceDownloader.DownloadFileCallback(AOwner: TfrmDownloadProgress; var Result: Boolean);
var
Client: THTTPClient;
Stream: TStream;
Response: IHTTPResponse;
FTempFilename: string;
begin
Result := False;
FTempFilename := FDownloadFilename + '.download';
Client := THTTPClient.Create;
try
with THTTPUploader.Create(AOwner) do
Client.OnReceiveData := HttpReceiveData;
Stream := TFileStream.Create(FTempFilename, fmCreate);
try
OnCheckCancel := AOwner.HTTPCheckCancel;
OnStatus := AOwner.HTTPStatus;
Request.Agent := API_UserAgent;
//Request.Protocol := Upload_Protocol;
//Request.HostName := Upload_Server;
Request.SetURL(FDownloadURL);// UrlPath := URL;
Upload;
if Response.StatusCode = 200 then
Response := Client.Get(FDownloadURL, Stream);
Result := Response.StatusCode = 200;
finally
Stream.Free;
end;
if FileExists(FTempFilename) then
begin
if Result then
begin
with TFileStream.Create(FDownloadFilename, fmCreate) do // I3476
try
Write(Response.PMessageBody^, Response.MessageBodyLength);
finally
Free;
end;
Result := True;
if FileExists(FDownloadFilename) then
System.SysUtils.DeleteFile(FDownloadFilename);
RenameFile(FTempFilename, FDownloadFilename);
end
else
// TODO: Deal with circular dependency
GetRunTools.LogError(FInstallInfo.Text(ssErrorDownloadingUpdate, [Response.StatusCode]));
finally
Free;
end;
except
on E:EHTTPUploader do
begin
// TODO: Deal with circular dependency
if (E.ErrorCode = 12007) or (E.ErrorCode = 12029)
then GetRunTools.LogError(FInstallInfo.Text(ssErrorUnableToContactServer))
else GetRunTools.LogError(FInstallInfo.Text(ssErrorUnableToContactServerDetailed, [E.Message]));
Result := False;
System.SysUtils.DeleteFile(FTempFilename);
end;
finally
Client.Free;
end;
end;

View file

@ -92,7 +92,8 @@ type
procedure RunVersion10Upgrade(const KMShellPath: WideString);
procedure CloseKeymanApplications; // I2740
procedure DeleteBackupPath; // I2747
procedure WaitFor(hProcess: THandle; var Waiting, Cancelled: Boolean); // I3349
procedure WaitFor(hProcess: THandle; var Waiting, Cancelled: Boolean);
procedure WriteToLog(const msg: string); // I3349
public
destructor Destroy; override;
procedure CheckInternetConnectedState;
@ -100,6 +101,7 @@ type
StartAfterInstall, StartWithWindows, CheckForUpdates, StartDisabled,
StartWithConfiguration, AutomaticallyReportUsage: Boolean): Boolean;
procedure LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean = True);
procedure LogInfo(const msg: string);
class procedure CheckInstalledVersion(msiLocation: TInstallInfoFileLocation);
@ -223,13 +225,18 @@ begin
end;
procedure TRunTools.LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean = True);
begin
WriteToLog('ERROR: '+msg);
if not FSilent and ShowDialogIfNotSilent then
ShowMessageW(msg);
end;
procedure TRunTools.WriteToLog(const msg: string);
const
nl: WideString = #13#10;
var
path: WideString;
begin
if not FSilent and ShowDialogIfNotSilent then
ShowMessageW(msg);
if not Assigned(FErrorLog) then
begin
path := TKeymanPaths.ErrorLogPath + 'setup.log'; // I2314
@ -246,6 +253,11 @@ begin
FErrorLog.Write(PWideChar(msg+nl)^, Length(msg+nl)*2);
end;
procedure TRunTools.LogInfo(const msg: string);
begin
WriteToLog('INFO: '+msg);
end;
function TRunTools.IsNewerVersionInstalled(const NewVersion: WideString): Boolean;
begin
Result := (FInstallInfo.InstalledVersion.Version <> '') and
@ -451,7 +463,7 @@ var
begin
if not Assigned(msiLocation) or (msiLocation.LocationType = iilOnline) then
begin
UpgradeCode := '{c70af17c-8b9e-47a1-a099-b65aee3dc8b4}'; // Keyman 11+
UpgradeCode := '{c70af17c-8b9e-47a1-a099-b65aee3dc8b4}'; // Keyman 11+ // TODO: move constant somewhere else
end
else
begin
@ -595,6 +607,7 @@ begin
if Assigned(packLocation) then
begin
// TODO: need to make sure remote file is downloaded before this
LogInfo('Downloading '+packLocation.Url);
if packLocation.LocationType = iilOnline then
if not TResourceDownloader.Execute(FInstallInfo, packLocation) then
begin

View file

@ -26,7 +26,7 @@ interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, httpuploader;
Dialogs, StdCtrls, ComCtrls;
type
TfrmDownloadProgress = class;
@ -46,9 +46,8 @@ type
procedure WMUserFormShown(var Message: TMessage); message WM_USER;
public
procedure HTTPCheckCancel(Sender: THTTPUploader; var Cancel: Boolean);
//procedure HTTPFileProgress(Sender: THTTPUploader; const FileName: string; dwFileBytesSent, dwLocalFileSize, dwSecondsToFileCompletion, dwOverallBytesSent, dwOverallBytesTotal, dwSecondsToOverallCompletion, dwBytesPerSecond: DWord);
procedure HTTPStatus(Sender: THTTPUploader; const Message: string; Position, Total: Int64); // I2855
procedure HTTPCheckCancel(Sender: TObject; var Cancel: Boolean);
procedure HTTPStatus(Sender: TObject; const Message: string; Position, Total: Int64); // I2855
property Callback: TDownloadProgressCallback read FCallback write FCallback;
property Cancel: Boolean read FCancel;
end;
@ -74,12 +73,12 @@ begin
PostMessage(Handle, WM_USER, 0, 0); // Starts process after form displays
end;
procedure TfrmDownloadProgress.HTTPCheckCancel(Sender: THTTPUploader; var Cancel: Boolean);
procedure TfrmDownloadProgress.HTTPCheckCancel(Sender: TObject; var Cancel: Boolean);
begin
Cancel := FCancel;
end;
procedure TfrmDownloadProgress.HTTPStatus(Sender: THTTPUploader;
procedure TfrmDownloadProgress.HTTPStatus(Sender: TObject;
const Message: string; Position, Total: Int64); // I2855
begin
if Total = 0
@ -105,8 +104,7 @@ begin
then ModalResult := mrOk
else ModalResult := mrCancel;
except
on EHTTPUploaderCancel do ModalResult := mrCancel;
on E:EHTTPUploader do
on E:Exception do
begin
ShowMessage(E.Message);
ModalResult := mrCancel;

View file

@ -156,6 +156,7 @@ end;
procedure TfrmRunDesktop.cmdExitClick(Sender: TObject);
begin
// TODO: we should use a boolean flag here rather than comparing caption
if cmdExit.Caption = FInstallInfo.Text(ssCancelButton) then // I2644
begin
if MessageDlg(FInstallInfo.Text(ssCancelQuery), mtConfirmation, mbOkCancel, 0) = mrCancel then Exit;

View file

@ -5,14 +5,12 @@ uses
UfrmDownloadProgress in 'UfrmDownloadProgress.pas',
CommonControls in 'CommonControls.pas',
VersionInfo in '..\..\global\delphi\general\VersionInfo.pas',
httpuploader in '..\..\global\delphi\general\httpuploader.pas',
RegistryKeys in '..\..\global\delphi\general\RegistryKeys.pas',
klog in '..\..\global\delphi\general\klog.pas',
Upload_Settings in '..\..\global\delphi\general\Upload_Settings.pas',
DebugPaths in '..\..\global\delphi\general\DebugPaths.pas',
GetOsVersion in '..\..\global\delphi\general\GetOsVersion.pas',
TntDialogHelp in 'TntDialogHelp.pas',
httpuploader_messageprocessor_windows in 'httpuploader_messageprocessor_windows.pas',
SetupForm in 'SetupForm.pas',
resource in 'resource.pas',
SetupStrings in 'SetupStrings.pas',

View file

@ -100,14 +100,12 @@
<DCCReference Include="UfrmDownloadProgress.pas"/>
<DCCReference Include="CommonControls.pas"/>
<DCCReference Include="..\..\global\delphi\general\VersionInfo.pas"/>
<DCCReference Include="..\..\global\delphi\general\httpuploader.pas"/>
<DCCReference Include="..\..\global\delphi\general\RegistryKeys.pas"/>
<DCCReference Include="..\..\global\delphi\general\klog.pas"/>
<DCCReference Include="..\..\global\delphi\general\Upload_Settings.pas"/>
<DCCReference Include="..\..\global\delphi\general\DebugPaths.pas"/>
<DCCReference Include="..\..\global\delphi\general\GetOsVersion.pas"/>
<DCCReference Include="TntDialogHelp.pas"/>
<DCCReference Include="httpuploader_messageprocessor_windows.pas"/>
<DCCReference Include="SetupForm.pas"/>
<DCCReference Include="resource.pas"/>
<DCCReference Include="SetupStrings.pas"/>