From 9a14039e8cb1f91e2d7c45e5cd54a9f315433472 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Nov 2023 14:16:32 +1000 Subject: [PATCH 01/11] =?UTF-8?q?chore:=20establish=20epic/windows-updates?= =?UTF-8?q?=20=F0=9F=92=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/src/TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/src/TODO.md b/windows/src/TODO.md index 2a0d49470e3..301a8409db2 100644 --- a/windows/src/TODO.md +++ b/windows/src/TODO.md @@ -3,3 +3,4 @@ 2. Suggestion: the 'repair profiles' pattern should look at the list of enabled Keyman keyboards vs the list of profiles in HKCU\Control Panel\International and fixup discrepancies there. This should probably be an automatic task. This will probably fix the issues that Makara noted around missing keyboards across multiple user profiles etc. 3. uninstall and reinstall of package in same ELEVATED session of kmshell causes error 4. Review Joshua's corrections document +5. Implement Windows Updates Stage 1 -- https://github.com/keymanapp/keyman/issues/10038 \ No newline at end of file From 8756e4016c163aa74f4a7d1f307a43372fa3d623 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Nov 2023 14:23:30 +1000 Subject: [PATCH 02/11] feat(windows): Online Update check stream update data Starts to refactor the online update check in Windows so that the results are stored to disk and can be checked at any time. This commit: * removes keepintouch frame * adds update frame * adds serialization of update check data * presents the serialized update check data in the update frame Online updates won't currently work. --- .../Keyman.System.UpdateCheckResponse.pas | 41 +++++- common/windows/delphi/general/KeymanPaths.pas | 9 ++ windows/src/desktop/kmshell/kmshell.dpr | 6 +- windows/src/desktop/kmshell/kmshell.dproj | 2 + .../main/Keyman.System.UpdateCheckStorage.pas | 52 ++++++++ .../kmshell/main/OnlineUpdateCheck.pas | 106 +++++++++------ windows/src/desktop/kmshell/main/UfrmMain.pas | 17 +++ .../kmshell/render/UpdateXMLRenderer.pas | 91 +++++++++++++ windows/src/desktop/kmshell/xml/config.css | 10 +- windows/src/desktop/kmshell/xml/config.js | 2 +- windows/src/desktop/kmshell/xml/keyman.xsl | 4 +- .../kmshell/xml/keyman_keepintouch.xsl | 23 ---- .../src/desktop/kmshell/xml/keyman_menu.xsl | 6 +- .../desktop/kmshell/xml/keyman_support.xsl | 1 - .../src/desktop/kmshell/xml/keyman_update.xsl | 125 ++++++++++++++++++ 15 files changed, 409 insertions(+), 86 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas create mode 100644 windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas delete mode 100644 windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl create mode 100644 windows/src/desktop/kmshell/xml/keyman_update.xsl diff --git a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas index 918c3964119..adc90ab1520 100644 --- a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas +++ b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas @@ -35,6 +35,7 @@ TUpdateCheckResponsePackage = record TUpdateCheckResponse = record private + FOriginalData: string; FInstallSize: Int64; FInstallURL: string; FNewVersion: string; @@ -46,9 +47,13 @@ TUpdateCheckResponse = record FFileName: string; function ParseKeyboards(nodes: TJSONObject): Boolean; function ParseLanguages(i: Integer; v: TJSONValue): Boolean; + function DoParse(const message, app, currentVersion: string): Boolean; public function Parse(const message: AnsiString; const app, currentVersion: string): Boolean; + procedure SaveToFile(const Filename: string); + function LoadFromFile(const Filename, app, currentVersion: string): Boolean; + property CurrentVersion: string read FCurrentVersion; property NewVersion: string read FNewVersion; property NewVersionWithTag: string read FNewVersionWithTag; @@ -58,25 +63,32 @@ TUpdateCheckResponse = record property ErrorMessage: string read FErrorMessage; property Status: TUpdateCheckResponseStatus read FStatus; property Packages: TUpdateCheckResponsePackages read FPackages; + property OriginalData: string read FOriginalData; end; implementation uses + System.Classes, System.Generics.Collections, versioninfo; { TUpdateCheckResponse } function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean; +begin + Result := DoParse(string(UTF8String(message)), app, currentVersion); +end; + +function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean; var node, doc: TJSONObject; begin + FOriginalData := message; FCurrentVersion := currentVersion; FStatus := ucrsNoUpdate; - // TODO: test with UTF8 characters in response - doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject; + doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject; if doc = nil then begin FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]); @@ -168,4 +180,29 @@ function TUpdateCheckResponse.ParseLanguages(i: Integer; v: TJSONValue): Boolean Result := True; end; +function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean; +var + ss: TStringStream; +begin + ss := TStringStream.Create('', TEncoding.UTF8); + try + ss.LoadFromFile(Filename); + Result := DoParse(ss.DataString, app, currentVersion); + finally + ss.Free; + end; +end; + +procedure TUpdateCheckResponse.SaveToFile(const Filename: string); +var + ss: TStringStream; +begin + ss := TStringStream.Create(FOriginalData, TEncoding.UTF8); + try + ss.SaveToFile(Filename); + finally + ss.Free; + end; +end; + end. diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index 88ef8282efe..76251c3b51b 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -16,6 +16,8 @@ TKeymanPaths = class const S_CEF_LibCef = 'libcef.dll'; const S_CEF_SubProcess = 'kmbrowserhost.exe'; const S_CustomisationFilename = 'desktop_pro.pxx'; + + const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\'; public const S_KMShell = 'kmshell.exe'; const S_TSysInfoExe = 'tsysinfo.exe'; @@ -26,8 +28,10 @@ TKeymanPaths = class const S_FallbackKeyboardPath = 'Keyboards\'; const S__Package = '_Package\'; const S_MCompileExe = 'mcompile.exe'; + const S_UpdateCache_Metadata = 'cache.json'; class function ErrorLogPath(const app: string = ''): string; static; class function KeymanHelpPath(const HelpFile: string): string; static; + class function KeymanUpdateCachePath(const filename: string = ''): string; static; class function KeymanDesktopInstallPath(const filename: string = ''): string; static; class function KeymanEngineInstallPath(const filename: string = ''): string; static; class function KeymanDesktopInstallDir: string; static; @@ -417,6 +421,11 @@ class function TKeymanPaths.KeymanHelpPath(const HelpFile: string): string; Result := ''; end; +class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string; +begin + Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename; +end; + class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean; begin // On developer machines, if we are running within the source repo, then use diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index befb47fcf92..1497982ccba 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -176,7 +176,9 @@ uses TaskScheduler_TLB in '..\..\global\delphi\winapi\TaskScheduler_TLB.pas', Keyman.Configuration.System.HttpServer.App.TextEditorFonts in 'startup\help\Keyman.Configuration.System.HttpServer.App.TextEditorFonts.pas', Keyman.Configuration.System.HttpServer.App.Locale in 'web\Keyman.Configuration.System.HttpServer.App.Locale.pas', - Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas'; + Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', + UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', + Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas'; {$R VERSION.RES} {$R manifest.res} @@ -198,7 +200,7 @@ begin Application.Initialize; Application.Title := 'Keyman Configuration'; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - try + try Run; finally FreeAndNil(modWebHttpServer); diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 5d716d8c4a5..71c91d9e567 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -353,6 +353,8 @@ + + Cfg_2 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas new file mode 100644 index 00000000000..f1a156bfa06 --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -0,0 +1,52 @@ +unit Keyman.System.UpdateCheckStorage; + +interface + +uses + Keyman.System.UpdateCheckResponse; + +type + TUpdateCheckStorage = class sealed + private + class function MetadataFilename: string; static; + public + class function HasUpdates: Boolean; static; + class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static; + class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static; + end; + +implementation + +uses + System.SysUtils, + + KeymanPaths, + KeymanVersion; + +{ TUpdateCheckStorage } + +class function TUpdateCheckStorage.MetadataFilename: string; +begin + Result := TKeymanPaths.KeymanUpdateCachePath(TKeymanPaths.S_UpdateCache_Metadata); +end; + +class procedure TUpdateCheckStorage.SaveUpdateCacheData( + const data: TUpdateCheckResponse); +begin + ForceDirectories(TKeymanPaths.KeymanUpdateCachePath); + data.SaveToFile(MetadataFilename); +end; + +class function TUpdateCheckStorage.HasUpdates: Boolean; +begin + Result := FileExists(MetadataFilename); +end; + +class function TUpdateCheckStorage.LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; +begin + Result := + HasUpdates and + data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version); +end; + +end. diff --git a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas index 15fb06f4e96..c202742813f 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -98,6 +98,7 @@ TOnlineUpdateCheck = class FShowErrors: Boolean; FDownload: TOnlineUpdateCheckDownloadParams; + FCheckOnly: Boolean; function DownloadUpdates: Boolean; procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean); @@ -112,7 +113,9 @@ TOnlineUpdateCheck = class public public - constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean); + function ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; + + constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean = False); destructor Destroy; override; function Run: TOnlineUpdateCheckResult; property ShowErrors: Boolean read FShowErrors write FShowErrors; @@ -146,6 +149,7 @@ implementation KLog, keymanapi_TLB, KeymanVersion, + Keyman.System.UpdateCheckStorage, kmint, ErrorControlledRegistry, RegistryKeys, @@ -165,7 +169,7 @@ implementation { TOnlineUpdateCheck } -constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean); +constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean); begin inherited Create; @@ -176,6 +180,7 @@ constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Bool FSilent := ASilent; FForce := AForce; + FCheckOnly := ACheckOnly; KL.Log('TOnlineUpdateCheck.Create'); end; @@ -477,10 +482,9 @@ procedure TOnlineUpdateCheck.ShutDown; function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; var flags: DWord; - i, n: Integer; - pkg: IKeymanPackage; - j: Integer; + i: Integer; ucr: TUpdateCheckResponse; + pkg: IKeymanPackage; begin {FProxyHost := ''; FProxyPort := 0;} @@ -567,45 +571,15 @@ function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; begin if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin - SetLength(FParams.Packages,0); - for i := Low(ucr.Packages) to High(ucr.Packages) do - begin - n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); - if n >= 0 then - begin - pkg := kmcom.Packages[n]; - j := Length(FParams.Packages); - SetLength(FParams.Packages, j+1); - FParams.Packages[j].NewID := ucr.Packages[i].NewID; - FParams.Packages[j].ID := ucr.Packages[i].ID; - FParams.Packages[j].Description := ucr.Packages[i].Name; - FParams.Packages[j].OldVersion := pkg.Version; - FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; - FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; - FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; - FParams.Packages[j].FileName := ucr.Packages[i].FileName; - pkg := nil; - end - else - FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; - end; + ResponseToParams(ucr); - case ucr.Status of - ucrsNoUpdate: - begin - FErrorMessage := ucr.ErrorMessage; - end; - ucrsUpdateReady: - begin - FParams.Keyman.OldVersion := ucr.CurrentVersion; - FParams.Keyman.NewVersion := ucr.NewVersion; - FParams.Keyman.DownloadURL := ucr.InstallURL; - FParams.Keyman.DownloadSize := ucr.InstallSize; - FParams.Keyman.FileName := ucr.FileName; - end; - end; - - if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + if FCheckOnly then + begin + // TODO: Refactor this + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := FParams.Result; + end + else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then begin if not FSilent then ShowUpdateForm @@ -651,6 +625,52 @@ function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; end; end; +function TOnlineUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; +var + i, j, n: Integer; + pkg: IKeymanPackage; +begin + SetLength(FParams.Packages,0); + for i := Low(ucr.Packages) to High(ucr.Packages) do + begin + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + begin + pkg := kmcom.Packages[n]; + j := Length(FParams.Packages); + SetLength(FParams.Packages, j+1); + FParams.Packages[j].NewID := ucr.Packages[i].NewID; + FParams.Packages[j].ID := ucr.Packages[i].ID; + FParams.Packages[j].Description := ucr.Packages[i].Name; + FParams.Packages[j].OldVersion := pkg.Version; + FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; + FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; + FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; + FParams.Packages[j].FileName := ucr.Packages[i].FileName; + pkg := nil; + end + else + FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; + end; + + case ucr.Status of + ucrsNoUpdate: + begin + FErrorMessage := ucr.ErrorMessage; + end; + ucrsUpdateReady: + begin + FParams.Keyman.OldVersion := ucr.CurrentVersion; + FParams.Keyman.NewVersion := ucr.NewVersion; + FParams.Keyman.DownloadURL := ucr.InstallURL; + FParams.Keyman.DownloadSize := ucr.InstallSize; + FParams.Keyman.FileName := ucr.FileName; + end; + end; + + Result := FParams; +end; + procedure OnlineUpdateAdmin(OwnerForm: TCustomForm; Path: string); var Package: TOnlineUpdateCheckParamsPackage; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index d9a6f8b4417..969e85d99c1 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -148,6 +148,7 @@ TfrmMain = class(TfrmWebContainer) procedure OpenSite(params: TStringList); procedure DoApply; procedure DoRefresh; + procedure Update_CheckNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -202,6 +203,7 @@ implementation UfrmTextEditor, uninstall, Upload_Settings, + UpdateXMLRenderer, utildir, utilexecute, utilkmshell, @@ -301,6 +303,7 @@ procedure TfrmMain.Do_Content_Render; FXMLRenderers.Add(TOptionsXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TLanguagesXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TSupportXMLRenderer.Create(FXMLRenderers)); + FXMLRenderers.Add(TUpdateXMLRenderer.Create(FXMLRenderers)); xml := FXMLRenderers.RenderToString(s); sharedData.Init( @@ -345,6 +348,9 @@ procedure TfrmMain.FireCommand(const command: WideString; params: TStringList); else if command = 'support_updatecheck' then Support_UpdateCheck else if command = 'support_proxyconfig' then Support_ProxyConfig + else if command = 'update_checknow' then Update_CheckNow + + else if command = 'contact_support' then Support_ContactSupport(params) // I4390 else if command = 'opensite' then OpenSite(params) @@ -815,6 +821,17 @@ procedure TfrmMain.Support_UpdateCheck; end; end; +procedure TfrmMain.Update_CheckNow; +begin + with TOnlineUpdateCheck.Create(Self, True, True, True) do + try + Run; + finally + Free; + end; + DoRefresh; +end; + procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited; diff --git a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas new file mode 100644 index 00000000000..0c102fdca7f --- /dev/null +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -0,0 +1,91 @@ +(* + Name: UpdateXMLRenderer + Copyright: Copyright (C) SIL International. +*) +unit UpdateXMLRenderer; + +interface + +uses + XMLRenderer, + Windows; + +type + TUpdateXMLRenderer = class(TXMLRenderer) + protected + function XMLData: WideString; override; + end; + +implementation + +uses + StrUtils, + SysUtils, + VersionInfo, + kmint, + KeymanVersion, + keymanapi_TLB, + Keyman.System.LocaleStrings, + Keyman.System.UpdateCheckResponse, + Keyman.System.UpdateCheckStorage, + MessageIdentifierConsts, + MessageIdentifiers, + OnlineUpdateCheck, + utilxml; + +{ TUpdateXMLRenderer } + +function TUpdateXMLRenderer.XMLData: WideString; +var + xml: string; + ucr: TUpdateCheckResponse; + ouc: TOnlineUpdateCheck; + params: TOnlineUpdateCheckParams; + i: Integer; +begin + xml := ''; + + if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then + begin + ouc := TOnlineUpdateCheck.Create(nil, False, True); + params := ouc.ResponseToParams(ucr); + + if (Params.Keyman.DownloadURL <> '') then + begin + xml := xml + + ''+ + '0'+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [Params.Keyman.NewVersion]))+''+ + ''+xmlencode(Params.Keyman.NewVersion)+''+ + ''+xmlencode(Params.Keyman.OldVersion)+''+ + ''+xmlencode(Format('%d', [Params.Keyman.DownloadSize div 1024]))+'KB'+ + ''+xmlencode(Params.Keyman.DownloadURL)+''+ + ''+ + ''; + end; + + for i := 0 to High(Params.Packages) do + begin + xml := xml + + ''+ + ''+IntToStr(i+1)+''+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText, + [Params.Packages[i].Description, Params.Packages[i].NewVersion]))+''+ + ''+xmlencode(Params.Packages[i].NewVersion)+''+ + ''+xmlencode(Params.Packages[i].OldVersion)+''+ + ''+xmlencode(Format('%d', [Params.Packages[i].DownloadSize div 1024]))+'KB'+ + ''+xmlencode(Params.Packages[i].DownloadURL)+''+ + ''+ + ''; + end; + end; + + Result := ''+xml+''; +end; + +end. + diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index 88c8d22b39c..704c4e6f926 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -1093,19 +1093,11 @@ th height: 16px; } -#keepintouch_content { +#update_content { height: 100%; overflow: hidden; } -#keepintouch_frame { - box-sizing: border-box; - width:100%; - height:100%; - border:none; - user-select: none; -} - /* QRCodes */ .qrcode { diff --git a/windows/src/desktop/kmshell/xml/config.js b/windows/src/desktop/kmshell/xml/config.js index cf761ce6c2c..47b66ca89c1 100644 --- a/windows/src/desktop/kmshell/xml/config.js +++ b/windows/src/desktop/kmshell/xml/config.js @@ -35,7 +35,7 @@ function windowResize() e = _$('subcontent_pro'); if(e) e.style.height = h; _$('subcontent_support').style.height = h; - _$('subcontent_keepintouch').style.height = h; + _$('subcontent_update').style.height = h; } } diff --git a/windows/src/desktop/kmshell/xml/keyman.xsl b/windows/src/desktop/kmshell/xml/keyman.xsl index 94c7ed7e955..a774e0aa9c6 100644 --- a/windows/src/desktop/kmshell/xml/keyman.xsl +++ b/windows/src/desktop/kmshell/xml/keyman.xsl @@ -12,7 +12,7 @@ - + @@ -50,7 +50,7 @@
-
+
diff --git a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl b/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl deleted file mode 100644 index 780166619f1..00000000000 --- a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -
- - -
- -
-
- -
-
-
-
\ No newline at end of file diff --git a/windows/src/desktop/kmshell/xml/keyman_menu.xsl b/windows/src/desktop/kmshell/xml/keyman_menu.xsl index 4fc1b0f2254..99d5e05facf 100644 --- a/windows/src/desktop/kmshell/xml/keyman_menu.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_menu.xsl @@ -9,20 +9,20 @@ - + - + diff --git a/windows/src/desktop/kmshell/xml/keyman_support.xsl b/windows/src/desktop/kmshell/xml/keyman_support.xsl index d472f88155d..e33d031a47a 100644 --- a/windows/src/desktop/kmshell/xml/keyman_support.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_support.xsl @@ -41,7 +41,6 @@
  • keyman:link?url=/keyman.com
  • -
  • keyman:link?url=/go//support
  • diff --git a/windows/src/desktop/kmshell/xml/keyman_update.xsl b/windows/src/desktop/kmshell/xml/keyman_update.xsl new file mode 100644 index 00000000000..9acfd28d3b8 --- /dev/null +++ b/windows/src/desktop/kmshell/xml/keyman_update.xsl @@ -0,0 +1,125 @@ + + + + + + + +
    + + +
    + +
    +
    + +
    + +
    + +
    +  
    +
    + +
    Updates are available which will be applied when Windows is next restarted:
    + +
    + + + + + + + + + +
    + +
    + +
    + + + keyman:update_applynow + 220px + + + + + keyman:update_checknow + 220px + +
    + + + +
    +
    +
    + + + + + + javascript:updateTick(""); + Update_ + checked + Update_ + + + Update__RequiresAdmin + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + + + +
    + + + + +
    +
    + +
    + +
    \ No newline at end of file From 2d2f9198c245318d2d25acb51f6be87037786fde Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:56:03 +1000 Subject: [PATCH 03/11] feat(windows): address review comments Co-authored-by: Eberhard Beilharz --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/main/UfrmMain.pas | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 1497982ccba..c4f21ea44d1 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -200,7 +200,7 @@ begin Application.Initialize; Application.Title := 'Keyman Configuration'; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - try + try Run; finally FreeAndNil(modWebHttpServer); diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 969e85d99c1..a779fdcdd2b 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -350,7 +350,6 @@ procedure TfrmMain.FireCommand(const command: WideString; params: TStringList); else if command = 'update_checknow' then Update_CheckNow - else if command = 'contact_support' then Support_ContactSupport(params) // I4390 else if command = 'opensite' then OpenSite(params) From cf8d6ea3337e94b43efaebc208823619313ea148 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:12:42 +1000 Subject: [PATCH 04/11] feat(windows): remove UI comment for downloading updates --- windows/src/desktop/kmshell/kmshell.dpr | 5 +- windows/src/desktop/kmshell/kmshell.dproj | 11 +- .../kmshell/main/RemoteUpdateCheck.pas | 460 ++++++++++++++++++ windows/src/desktop/kmshell/main/initprog.pas | 16 + 4 files changed, 485 insertions(+), 7 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index c4f21ea44d1..2b1e14b517b 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -178,7 +178,8 @@ uses Keyman.Configuration.System.HttpServer.App.Locale in 'web\Keyman.Configuration.System.HttpServer.App.Locale.pas', Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', - Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas'; + Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', + RemoteUpdateCheck in 'main\RemoteUpdateCheck.pas'; {$R VERSION.RES} {$R manifest.res} @@ -200,7 +201,7 @@ begin Application.Initialize; Application.Title := 'Keyman Configuration'; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - try + try Run; finally FreeAndNil(modWebHttpServer); diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 71c91d9e567..2e6d27e6cd9 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -355,6 +355,7 @@ + Cfg_2 @@ -416,21 +417,21 @@ False - + kmshell.rsm true - + - .\ + kmshell.exe true - + - kmshell.exe + .\ true diff --git a/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas new file mode 100644 index 00000000000..75c57315a96 --- /dev/null +++ b/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas @@ -0,0 +1,460 @@ +(* + Name: WebUpdateCheck + Copyright: Copyright (C) SIL International. + Documentation: + Description: + Create Date: 5 Dec 2023 + + Modified Date: + Authors: rcruickshank + Related Files: + Dependencies: + + Bugs: + Todo: + Notes: + History: +*) +unit RemoteUpdateCheck; // I3306 + +interface + +uses + System.Classes, + System.SysUtils, + System.UITypes, + System.IOUtils, + Vcl.Forms, + KeymanPaths, + + httpuploader, + Keyman.System.UpdateCheckResponse, + UfrmDownloadProgress, + OnlineUpdateCheck; + +type + ERemoteUpdateCheck = class(Exception); + + TRemoteUpdateCheckResult = (wucUnknown, wucSuccess, wucNoUpdates, wucFailure, wucOffline); + + TRemoteUpdateCheckParams = record + Keyman: TOnlineUpdateCheckParamsKeyman; + Packages: array of TOnlineUpdateCheckParamsPackage; + Result: TRemoteUpdateCheckResult; + end; + + TRemoteUpdateCheckDownloadParams = record + TotalSize: Integer; + TotalDownloads: Integer; + StartPosition: Integer; + end; + + TRemoteUpdateCheck = class + private + FForce: Boolean; + FParams: TRemoteUpdateCheckParams; + + FErrorMessage: string; + + FShowErrors: Boolean; + FDownload: TRemoteUpdateCheckDownloadParams; + FCheckOnly: Boolean; + + function DownloadUpdates: Boolean; + procedure DoDownloadUpdates(SavePath: string; var Result: Boolean); + function DoRun: TRemoteUpdateCheckResult; + public + function ResponseToParams(const ucr: TUpdateCheckResponse): TRemoteUpdateCheckParams; + + constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); + destructor Destroy; override; + function Run: TRemoteUpdateCheckResult; + property ShowErrors: Boolean read FShowErrors write FShowErrors; + end; + +procedure LogMessage(LogMessage: string); + +implementation + +uses + System.WideStrUtils, + Vcl.Dialogs, + Winapi.ShellApi, + Winapi.Windows, + Winapi.WinINet, + + GlobalProxySettings, + KLog, + keymanapi_TLB, + KeymanVersion, + Keyman.System.UpdateCheckStorage, + kmint, + ErrorControlledRegistry, + RegistryKeys, + Upload_Settings, + utildir, + utilexecute, + OnlineUpdateCheckMessages, + UfrmOnlineUpdateIcon, + UfrmOnlineUpdateNewVersion, + utilkmshell, + utilsystem, + utiluac, + versioninfo; + +{ TRemoteUpdateCheck } + +constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); +begin + inherited Create; + + FShowErrors := True; + FParams.Result := wucUnknown; + + FForce := AForce; + FCheckOnly := ACheckOnly; + + KL.Log('TRemoteUpdateCheck.Create'); +end; + +destructor TRemoteUpdateCheck.Destroy; +begin + if (FErrorMessage <> '') and FShowErrors then + LogMessage(FErrorMessage); + + KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = '+FErrorMessage); + KL.Log('TRemoteUpdateCheck.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + + inherited Destroy; +end; + +function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; +begin + Result := DoRun; + + if Result in [ wucSuccess] then + begin + kmcom.Keyboards.Refresh; + kmcom.Keyboards.Apply; + kmcom.Packages.Refresh; + end; + + FParams.Result := Result; +end; + + +procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; var Result: Boolean); +var + i, downloadCount: Integer; + + function DownloadFile(const url, savepath: string): Boolean; + begin + Result := False; + with THttpUploader.Create(nil) do + try + Proxy.Server := GetProxySettings.Server; + Proxy.Port := GetProxySettings.Port; + Proxy.Username := GetProxySettings.Username; + Proxy.Password := GetProxySettings.Password; + Request.Agent := API_UserAgent; + + Request.SetURL(url); + Upload; + if Response.StatusCode = 200 then + begin + with TFileStream.Create(savepath, fmCreate) do + try + Write(Response.PMessageBody^, Response.MessageBodyLength); + finally + Free; + end; + Result := True; + end + else // I2742 + // If it fails we set to false but will try the other files + Result := False; + Exit; + finally + Free; + end; + end; + + +begin + Result := False; + try + FDownload.TotalSize := 0; + FDownload.TotalDownloads := 0; + downloadCount := 0; + + for i := 0 to High(FParams.Packages) do + if FParams.Packages[i].Install then + begin + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, FParams.Packages[i].DownloadSize); + + FParams.Packages[i].SavePath := SavePath + FParams.Packages[i].FileName; + end; + + if FParams.Keyman.Install then + begin + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, FParams.Keyman.DownloadSize); + FParams.Keyman.SavePath := SavePath + FParams.Keyman.FileName; + end; + + FDownload.StartPosition := 0; + for i := 0 to High(FParams.Packages) do + if FParams.Packages[i].Install then + begin + if not DownloadFile(FParams.Packages[i].DownloadURL, FParams.Packages[i].SavePath) then // I2742 + begin + FParams.Packages[i].Install := False; // Download failed but install other files + end + else + Inc(downloadCount); + FDownload.StartPosition := FDownload.StartPosition + FParams.Packages[i].DownloadSize; + end; + + if FParams.Keyman.Install then + if not DownloadFile(FParams.Keyman.DownloadURL, FParams.Keyman.SavePath) then // I2742 + begin + FParams.Keyman.Install := False; // Download failed but user wants to install other files + end; + + // There needs to be at least one file successfully downloaded to return + // TRUE that files where downloaded + if downloadCount > 0 then + Result := True; + except + on E:EHTTPUploader do + begin + if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) + then LogMessage(S_OnlineUpdate_UnableToContact) + else LogMessage(WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message])); + Result := False; + end; + end; +end; + +function TRemoteUpdateCheck.DownloadUpdates: Boolean; +var + i: Integer; + DownloadBackGroundSavePath : String; + DownloadResult : Boolean; +begin + DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + { For now lets download all the updates. Set all as true } + + if FParams.Keyman.DownloadURL <> '' then + FParams.Keyman.Install := True; + + for i := 0 to High(FParams.Packages) do + FParams.Packages[i].Install := True; + + DoDownloadUpdates(DownloadBackGroundSavePath, DownloadResult); + KL.Log('TRemoteUpdateCheck.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; + +end; + +function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; +var + flags: DWord; + i: Integer; + ucr: TUpdateCheckResponse; + pkg: IKeymanPackage; + downloadResult: boolean; +begin + {FProxyHost := ''; + FProxyPort := 0;} + + { Check if user is currently online } + if not InternetGetConnectedState(@flags, 0) then + begin + Result := wucOffline; + Exit; + end; + + { Verify that it has been at least 7 days since last update check } + try + with TRegistryErrorControlled.Create do // I2890 + try + if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + begin + if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then + begin + Result := wucNoUpdates; + Exit; + end; + if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) and not FForce then + begin + Result := wucNoUpdates; + // TODO: This exit is just to remove the time check for testing. + //Exit; + end; + + {if ValueExists(SRegValue_UpdateCheck_UseProxy) and ReadBool(SRegValue_UpdateCheck_UseProxy) then + begin + FProxyHost := ReadString(SRegValue_UpdateCheck_ProxyHost); + FProxyPort := StrToIntDef(ReadString(SRegValue_UpdateCheck_ProxyPort), 80); + end;} + end; + finally + Free; + end; + except + { we will not run the check if an error occurs reading the settings } + on E:Exception do + begin + Result := wucFailure; + FErrorMessage := E.Message; + Exit; + end; + end; + + Result := wucNoUpdates; + + try + with THTTPUploader.Create(nil) do + try + Fields.Add('version', ansistring(CKeymanVersionInfo.Version)); + Fields.Add('tier', ansistring(CKeymanVersionInfo.Tier)); + if FForce + then Fields.Add('manual', '1') + else Fields.Add('manual', '0'); + + for i := 0 to kmcom.Packages.Count - 1 do + begin + pkg := kmcom.Packages[i]; + + // Due to limitations in PHP parsing of query string parameters names with + // space or period, we need to split the parameters up. The legacy pattern + // is still supported on the server side. Relates to #4886. + Fields.Add(AnsiString('packageid_'+IntToStr(i)), AnsiString(pkg.ID)); + Fields.Add(AnsiString('packageversion_'+IntToStr(i)), AnsiString(pkg.Version)); + pkg := nil; + end; + + Proxy.Server := GetProxySettings.Server; + Proxy.Port := GetProxySettings.Port; + Proxy.Username := GetProxySettings.Username; + Proxy.Password := GetProxySettings.Password; + + Request.HostName := API_Server; + Request.Protocol := API_Protocol; + Request.UrlPath := API_Path_UpdateCheck_Windows; + //OnStatus := + Upload; + if Response.StatusCode = 200 then + begin + if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then + begin + ResponseToParams(ucr); + + if FCheckOnly then + begin + // TODO: Refactor this + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := FParams.Result; + end + else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + begin + // TODO: Integrate in to the Background update state machine. + // for now just go straight to DownloadUpdates + downloadResult := DownloadUpdates; + if DownloadResult then + begin + Result := wucSuccess; + end; + end; + end + else + begin + FErrorMessage := ucr.ErrorMessage; + Result := wucFailure; + end; + end + else + raise ERemoteUpdateCheck.Create('Error '+IntToStr(Response.StatusCode)); + finally + Free; + end; + except + on E:EHTTPUploader do + begin + if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) + then FErrorMessage := S_OnlineUpdate_UnableToContact + else FErrorMessage := WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message]); + Result := wucFailure; + end; + on E:Exception do + begin + FErrorMessage := E.Message; + Result := wucFailure; + end; + end; + + with TRegistryErrorControlled.Create do // I2890 + try + if OpenKey(SRegKey_KeymanDesktop_CU, True) then + WriteDateTime(SRegValue_LastUpdateCheckTime, Now); + finally + Free; + end; +end; + +function TRemoteUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TRemoteUpdateCheckParams; +var + i, j, n: Integer; + pkg: IKeymanPackage; +begin + SetLength(FParams.Packages,0); + for i := Low(ucr.Packages) to High(ucr.Packages) do + begin + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + begin + pkg := kmcom.Packages[n]; + j := Length(FParams.Packages); + SetLength(FParams.Packages, j+1); + FParams.Packages[j].NewID := ucr.Packages[i].NewID; + FParams.Packages[j].ID := ucr.Packages[i].ID; + FParams.Packages[j].Description := ucr.Packages[i].Name; + FParams.Packages[j].OldVersion := pkg.Version; + FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; + FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; + FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; + FParams.Packages[j].FileName := ucr.Packages[i].FileName; + pkg := nil; + end + else + FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; + end; + + case ucr.Status of + ucrsNoUpdate: + begin + FErrorMessage := ucr.ErrorMessage; + end; + ucrsUpdateReady: + begin + FParams.Keyman.OldVersion := ucr.CurrentVersion; + FParams.Keyman.NewVersion := ucr.NewVersion; + FParams.Keyman.DownloadURL := ucr.InstallURL; + FParams.Keyman.DownloadSize := ucr.InstallSize; + FParams.Keyman.FileName := ucr.FileName; + end; + end; + + Result := FParams; +end; + + // temp wrapper for converting showmessage to logs don't know where + // if nt using klog + procedure LogMessage(LogMessage: string); + begin + KL.Log(LogMessage); + end; + +end. diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index c22591e79c9..29fe102d218 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -82,6 +82,7 @@ procedure Main(Owner: TComponent = nil); fmMigrate, fmSplash, fmStart, fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, + fmBackgroundUpdateCheck, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 @@ -120,6 +121,7 @@ implementation KMShellHints, KeymanMutex, OnlineUpdateCheck, + RemoteUpdateCheck, RegistryKeys, UfrmBaseKeyboard, UfrmKeymanBase, @@ -249,6 +251,7 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, else if s = '-h' then FMode := fmHelp else if s = '-t' then FMode := fmTextEditor else if s = '-ouc' then FMode := fmOnlineUpdateCheck + else if s = '-buc' then FMode := fmBackgroundUpdateCheck else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169 else if s = '-nowelcome' then FNoWelcome := True else if s = '-kw' then FMode := fmKeyboardWelcome // I2569 @@ -427,6 +430,19 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF ShowMessage(MsgFromId(SKOSNotSupported)); Exit; end; + // TODO: #10038 Will add this as part of the background update state machine + // for now just verifing the download happens via -buc switch. + with TRemoteUpdateCheck.Create(False, False) do + try + if (FMode = fmBackgroundUpdateCheck) then + begin + Run; + Exit; + end + finally + Free; + end; + if not FSilent or (FMode = fmUpgradeMnemonicLayout) then // I4553 begin From e1c2b6aa79f9e8a72f045eb99fc496f9377d0028 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:20:21 +1000 Subject: [PATCH 05/11] feat(windows): remove UI from uses list rename module --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 14 ++++---- ...as => Keyman.System.RemoteUpdateCheck.pas} | 34 ++++++++----------- windows/src/desktop/kmshell/main/initprog.pas | 2 +- 4 files changed, 24 insertions(+), 28 deletions(-) rename windows/src/desktop/kmshell/main/{RemoteUpdateCheck.pas => Keyman.System.RemoteUpdateCheck.pas} (96%) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 2b1e14b517b..71e434af915 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -179,7 +179,7 @@ uses Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', - RemoteUpdateCheck in 'main\RemoteUpdateCheck.pas'; + Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 2e6d27e6cd9..16f49e757d8 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -355,7 +355,7 @@ - + Cfg_2 @@ -417,12 +417,6 @@ False - - - kmshell.rsm - true - - kmshell.exe @@ -435,6 +429,12 @@ true + + + kmshell.rsm + true + + 1 diff --git a/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas similarity index 96% rename from windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas rename to windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 75c57315a96..310eca36f42 100644 --- a/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -15,21 +15,18 @@ Notes: History: *) -unit RemoteUpdateCheck; // I3306 +unit Keyman.System.RemoteUpdateCheck; // I3306 interface uses System.Classes, System.SysUtils, - System.UITypes, - System.IOUtils, - Vcl.Forms, + //System.UITypes, + //System.IOUtils, KeymanPaths, - httpuploader, Keyman.System.UpdateCheckResponse, - UfrmDownloadProgress, OnlineUpdateCheck; type @@ -78,8 +75,7 @@ implementation uses System.WideStrUtils, - Vcl.Dialogs, - Winapi.ShellApi, + //Winapi.ShellApi, Winapi.Windows, Winapi.WinINet, @@ -92,15 +88,13 @@ implementation ErrorControlledRegistry, RegistryKeys, Upload_Settings, - utildir, - utilexecute, - OnlineUpdateCheckMessages, - UfrmOnlineUpdateIcon, - UfrmOnlineUpdateNewVersion, - utilkmshell, - utilsystem, - utiluac, - versioninfo; + //utildir, + //utilexecute, + OnlineUpdateCheckMessages; + //utilkmshell, + // utilsystem, + //utiluac, + //versioninfo; { TRemoteUpdateCheck } @@ -358,10 +352,12 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; TUpdateCheckStorage.SaveUpdateCacheData(ucr); Result := FParams.Result; end + // TODO: #10038 + // Integerate into state machine. in the download state + // the process can call LoadUpdateCacheData if needed to get the + // response result. else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then begin - // TODO: Integrate in to the Background update state machine. - // for now just go straight to DownloadUpdates downloadResult := DownloadUpdates; if DownloadResult then begin diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 29fe102d218..5affe65cafa 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -121,7 +121,7 @@ implementation KMShellHints, KeymanMutex, OnlineUpdateCheck, - RemoteUpdateCheck, + Keyman.System.RemoteUpdateCheck, RegistryKeys, UfrmBaseKeyboard, UfrmKeymanBase, From 78f75aded977ce5dd889af5ced88361e41a83ea4 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:25:45 +1000 Subject: [PATCH 06/11] feat(windows): remove TRemoteUpdateCheckParams Just using the TUpdateCheckResponse gives us enough detail for now as we no longer have a UI set which packages to install --- .../main/Keyman.System.RemoteUpdateCheck.pas | 140 +++++------------- 1 file changed, 35 insertions(+), 105 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 310eca36f42..fc40f584cda 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -22,8 +22,6 @@ interface uses System.Classes, System.SysUtils, - //System.UITypes, - //System.IOUtils, KeymanPaths, httpuploader, Keyman.System.UpdateCheckResponse, @@ -34,12 +32,6 @@ ERemoteUpdateCheck = class(Exception); TRemoteUpdateCheckResult = (wucUnknown, wucSuccess, wucNoUpdates, wucFailure, wucOffline); - TRemoteUpdateCheckParams = record - Keyman: TOnlineUpdateCheckParamsKeyman; - Packages: array of TOnlineUpdateCheckParamsPackage; - Result: TRemoteUpdateCheckResult; - end; - TRemoteUpdateCheckDownloadParams = record TotalSize: Integer; TotalDownloads: Integer; @@ -49,7 +41,7 @@ TRemoteUpdateCheckDownloadParams = record TRemoteUpdateCheck = class private FForce: Boolean; - FParams: TRemoteUpdateCheckParams; + FRemoteResult: TRemoteUpdateCheckResult; FErrorMessage: string; @@ -57,11 +49,10 @@ TRemoteUpdateCheck = class FDownload: TRemoteUpdateCheckDownloadParams; FCheckOnly: Boolean; - function DownloadUpdates: Boolean; - procedure DoDownloadUpdates(SavePath: string; var Result: Boolean); + function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; + procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); function DoRun: TRemoteUpdateCheckResult; public - function ResponseToParams(const ucr: TUpdateCheckResponse): TRemoteUpdateCheckParams; constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); destructor Destroy; override; @@ -75,7 +66,6 @@ implementation uses System.WideStrUtils, - //Winapi.ShellApi, Winapi.Windows, Winapi.WinINet, @@ -88,13 +78,8 @@ implementation ErrorControlledRegistry, RegistryKeys, Upload_Settings, - //utildir, - //utilexecute, + OnlineUpdateCheckMessages; - //utilkmshell, - // utilsystem, - //utiluac, - //versioninfo; { TRemoteUpdateCheck } @@ -103,7 +88,7 @@ constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); inherited Create; FShowErrors := True; - FParams.Result := wucUnknown; + FRemoteResult := wucUnknown; FForce := AForce; FCheckOnly := ACheckOnly; @@ -117,7 +102,7 @@ destructor TRemoteUpdateCheck.Destroy; LogMessage(FErrorMessage); KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = '+FErrorMessage); - KL.Log('TRemoteUpdateCheck.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = '+IntToStr(Ord(FRemoteResult))); inherited Destroy; end; @@ -133,17 +118,16 @@ function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; kmcom.Packages.Refresh; end; - FParams.Result := Result; + FRemoteResult := Result; end; -procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; var Result: Boolean); +procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); var i, downloadCount: Integer; function DownloadFile(const url, savepath: string): Boolean; begin - Result := False; with THttpUploader.Create(nil) do try Proxy.Server := GetProxySettings.Server; @@ -181,40 +165,40 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; var Result: Boo FDownload.TotalDownloads := 0; downloadCount := 0; - for i := 0 to High(FParams.Packages) do - if FParams.Packages[i].Install then + // Keyboard Packages + for i := 0 to High(Params.Packages) do begin Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, FParams.Packages[i].DownloadSize); - - FParams.Packages[i].SavePath := SavePath + FParams.Packages[i].FileName; + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; end; - if FParams.Keyman.Install then - begin - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, FParams.Keyman.DownloadSize); - FParams.Keyman.SavePath := SavePath + FParams.Keyman.FileName; - end; + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); + // Keyboard Packages FDownload.StartPosition := 0; - for i := 0 to High(FParams.Packages) do - if FParams.Packages[i].Install then + for i := 0 to High(Params.Packages) do begin - if not DownloadFile(FParams.Packages[i].DownloadURL, FParams.Packages[i].SavePath) then // I2742 + if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742 begin - FParams.Packages[i].Install := False; // Download failed but install other files + Params.Packages[i].Install := False; // Download failed but install other files end else Inc(downloadCount); - FDownload.StartPosition := FDownload.StartPosition + FParams.Packages[i].DownloadSize; + FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; end; - if FParams.Keyman.Install then - if not DownloadFile(FParams.Keyman.DownloadURL, FParams.Keyman.SavePath) then // I2742 - begin - FParams.Keyman.Install := False; // Download failed but user wants to install other files - end; + // Keyamn Installer + if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 + begin + // TODO record fail? and log // Download failed but user wants to install other files + end + else + begin + Inc(downloadCount) + end; // There needs to be at least one file successfully downloaded to return // TRUE that files where downloaded @@ -231,22 +215,14 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; var Result: Boo end; end; -function TRemoteUpdateCheck.DownloadUpdates: Boolean; +function TRemoteUpdateCheck.DownloadUpdates(Params: TUpdateCheckResponse): Boolean; var - i: Integer; DownloadBackGroundSavePath : String; DownloadResult : Boolean; begin DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - { For now lets download all the updates. Set all as true } - - if FParams.Keyman.DownloadURL <> '' then - FParams.Keyman.Install := True; - - for i := 0 to High(FParams.Packages) do - FParams.Packages[i].Install := True; - DoDownloadUpdates(DownloadBackGroundSavePath, DownloadResult); + DoDownloadUpdates(DownloadBackGroundSavePath, Params, DownloadResult); KL.Log('TRemoteUpdateCheck.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); Result := DownloadResult; @@ -344,21 +320,21 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; begin if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin - ResponseToParams(ucr); + //ResponseToParams(ucr); if FCheckOnly then begin // TODO: Refactor this TUpdateCheckStorage.SaveUpdateCacheData(ucr); - Result := FParams.Result; + Result := FRemoteResult; end // TODO: #10038 // Integerate into state machine. in the download state // the process can call LoadUpdateCacheData if needed to get the // response result. - else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + else if (Length(ucr.Packages) > 0) or (ucr.InstallURL <> '') then begin - downloadResult := DownloadUpdates; + downloadResult := DownloadUpdates(ucr); if DownloadResult then begin Result := wucSuccess; @@ -400,52 +376,6 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; end; end; -function TRemoteUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TRemoteUpdateCheckParams; -var - i, j, n: Integer; - pkg: IKeymanPackage; -begin - SetLength(FParams.Packages,0); - for i := Low(ucr.Packages) to High(ucr.Packages) do - begin - n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); - if n >= 0 then - begin - pkg := kmcom.Packages[n]; - j := Length(FParams.Packages); - SetLength(FParams.Packages, j+1); - FParams.Packages[j].NewID := ucr.Packages[i].NewID; - FParams.Packages[j].ID := ucr.Packages[i].ID; - FParams.Packages[j].Description := ucr.Packages[i].Name; - FParams.Packages[j].OldVersion := pkg.Version; - FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; - FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; - FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; - FParams.Packages[j].FileName := ucr.Packages[i].FileName; - pkg := nil; - end - else - FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; - end; - - case ucr.Status of - ucrsNoUpdate: - begin - FErrorMessage := ucr.ErrorMessage; - end; - ucrsUpdateReady: - begin - FParams.Keyman.OldVersion := ucr.CurrentVersion; - FParams.Keyman.NewVersion := ucr.NewVersion; - FParams.Keyman.DownloadURL := ucr.InstallURL; - FParams.Keyman.DownloadSize := ucr.InstallSize; - FParams.Keyman.FileName := ucr.FileName; - end; - end; - - Result := FParams; -end; - // temp wrapper for converting showmessage to logs don't know where // if nt using klog procedure LogMessage(LogMessage: string); From ed762c07c97c93505ae3c4a2f7aeab924106e7b4 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:06:11 +1000 Subject: [PATCH 07/11] feat(windows): update XML render to use ucr --- .../kmshell/render/UpdateXMLRenderer.pas | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas index 0c102fdca7f..32fd7b2cf67 100644 --- a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -30,7 +30,6 @@ implementation Keyman.System.UpdateCheckStorage, MessageIdentifierConsts, MessageIdentifiers, - OnlineUpdateCheck, utilxml; { TUpdateXMLRenderer } @@ -39,48 +38,51 @@ function TUpdateXMLRenderer.XMLData: WideString; var xml: string; ucr: TUpdateCheckResponse; - ouc: TOnlineUpdateCheck; - params: TOnlineUpdateCheckParams; - i: Integer; + i, n : Integer; + pkg: IKeymanPackage; begin xml := ''; if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then begin - ouc := TOnlineUpdateCheck.Create(nil, False, True); - params := ouc.ResponseToParams(ucr); - - if (Params.Keyman.DownloadURL <> '') then + if (ucr.InstallURL <> '') then begin xml := xml + ''+ '0'+ IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ ''+ - ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [Params.Keyman.NewVersion]))+''+ - ''+xmlencode(Params.Keyman.NewVersion)+''+ - ''+xmlencode(Params.Keyman.OldVersion)+''+ - ''+xmlencode(Format('%d', [Params.Keyman.DownloadSize div 1024]))+'KB'+ - ''+xmlencode(Params.Keyman.DownloadURL)+''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [ucr.NewVersion]))+''+ + ''+xmlencode(ucr.NewVersion)+''+ + ''+xmlencode(ucr.CurrentVersion)+''+ + ''+xmlencode(Format('%d', [ucr.InstallSize div 1024]))+'KB'+ + ''+xmlencode(ucr.InstallURL)+''+ ''+ ''; end; - for i := 0 to High(Params.Packages) do + for i := 0 to High(ucr.Packages) do begin - xml := xml + - ''+ - ''+IntToStr(i+1)+''+ - IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ - ''+ - ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText, - [Params.Packages[i].Description, Params.Packages[i].NewVersion]))+''+ - ''+xmlencode(Params.Packages[i].NewVersion)+''+ - ''+xmlencode(Params.Packages[i].OldVersion)+''+ - ''+xmlencode(Format('%d', [Params.Packages[i].DownloadSize div 1024]))+'KB'+ - ''+xmlencode(Params.Packages[i].DownloadURL)+''+ - ''+ - ''; + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + pkg := kmcom.Packages[n]; + begin + xml := xml + + ''+ + ''+IntToStr(i+1)+''+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText, + [ucr.Packages[i].Name, ucr.Packages[i].NewVersion]))+''+ + ''+xmlencode(ucr.Packages[i].NewVersion)+''+ + ''+xmlencode(pkg.version)+''+ + ''+xmlencode(Format('%d', [ucr.Packages[i].DownloadSize div 1024]))+'KB'+ + ''+xmlencode(ucr.Packages[i].DownloadURL)+''+ + ''+ + ''; + pkg := nil; + end; + // else Package not found, skip end; end; From a945c08d7361fb1c7c82480480c9541e8665af00 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:23:39 +1000 Subject: [PATCH 08/11] feat(windows): address review comments Co-authored-by: Marc Durdin --- .../main/Keyman.System.RemoteUpdateCheck.pas | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index fc40f584cda..418f7206747 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -140,11 +140,11 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate Upload; if Response.StatusCode = 200 then begin - with TFileStream.Create(savepath, fmCreate) do + fs := TFileStream.Create(savepath, fmCreate); try - Write(Response.PMessageBody^, Response.MessageBodyLength); + fs.Write(Response.PMessageBody^, Response.MessageBodyLength); finally - Free; + fs.Free; end; Result := True; end @@ -167,11 +167,11 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate // Keyboard Packages for i := 0 to High(Params.Packages) do - begin - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); - Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; - end; + begin + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; + end; // Add the Keyman installer Inc(FDownload.TotalDownloads); @@ -190,7 +190,7 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; end; - // Keyamn Installer + // Keyman Installer if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 begin // TODO record fail? and log // Download failed but user wants to install other files @@ -201,8 +201,8 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate end; // There needs to be at least one file successfully downloaded to return - // TRUE that files where downloaded - if downloadCount > 0 then + // True that files were downloaded + if downloadCount > 0 then Result := True; except on E:EHTTPUploader do From 1ea9bd71a5df2deafcb1ef5424871961cac5fa51 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:35:43 +1000 Subject: [PATCH 09/11] feat(windows): Remove the with pattern --- .../main/Keyman.System.RemoteUpdateCheck.pas | 84 ++++++++++--------- windows/src/desktop/kmshell/main/initprog.pas | 7 +- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 418f7206747..077e4b42de0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -125,24 +125,26 @@ function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); var i, downloadCount: Integer; + http: THttpUploader; + fs: TFileStream; function DownloadFile(const url, savepath: string): Boolean; begin - with THttpUploader.Create(nil) do + http := THttpUploader.Create(nil); try - Proxy.Server := GetProxySettings.Server; - Proxy.Port := GetProxySettings.Port; - Proxy.Username := GetProxySettings.Username; - Proxy.Password := GetProxySettings.Password; - Request.Agent := API_UserAgent; - - Request.SetURL(url); - Upload; - if Response.StatusCode = 200 then + http.Proxy.Server := GetProxySettings.Server; + http.Proxy.Port := GetProxySettings.Port; + http.Proxy.Username := GetProxySettings.Username; + http.Proxy.Password := GetProxySettings.Password; + http.Request.Agent := API_UserAgent; + + http.Request.SetURL(url); + http.Upload; + if http.Response.StatusCode = 200 then begin fs := TFileStream.Create(savepath, fmCreate); try - fs.Write(Response.PMessageBody^, Response.MessageBodyLength); + fs.Write(http.Response.PMessageBody^, http.Response.MessageBodyLength); finally fs.Free; end; @@ -153,7 +155,7 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate Result := False; Exit; finally - Free; + http.Free; end; end; @@ -235,6 +237,8 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; ucr: TUpdateCheckResponse; pkg: IKeymanPackage; downloadResult: boolean; + registry: TRegistryErrorControlled; + http: THttpUploader; begin {FProxyHost := ''; FProxyPort := 0;} @@ -248,16 +252,16 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; { Verify that it has been at least 7 days since last update check } try - with TRegistryErrorControlled.Create do // I2890 + registry := TRegistryErrorControlled.Create; // I2890 try - if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then begin - if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then + if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) and not FForce then begin Result := wucNoUpdates; Exit; end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) and not FForce then + if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) and not FForce then begin Result := wucNoUpdates; // TODO: This exit is just to remove the time check for testing. @@ -271,7 +275,7 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; end;} end; finally - Free; + registry.Free; end; except { we will not run the check if an error occurs reading the settings } @@ -286,13 +290,13 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; Result := wucNoUpdates; try - with THTTPUploader.Create(nil) do + http := THTTPUploader.Create(nil); try - Fields.Add('version', ansistring(CKeymanVersionInfo.Version)); - Fields.Add('tier', ansistring(CKeymanVersionInfo.Tier)); + http.Fields.Add('version', ansistring(CKeymanVersionInfo.Version)); + http.Fields.Add('tier', ansistring(CKeymanVersionInfo.Tier)); if FForce - then Fields.Add('manual', '1') - else Fields.Add('manual', '0'); + then http.Fields.Add('manual', '1') + else http.Fields.Add('manual', '0'); for i := 0 to kmcom.Packages.Count - 1 do begin @@ -301,24 +305,24 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; // Due to limitations in PHP parsing of query string parameters names with // space or period, we need to split the parameters up. The legacy pattern // is still supported on the server side. Relates to #4886. - Fields.Add(AnsiString('packageid_'+IntToStr(i)), AnsiString(pkg.ID)); - Fields.Add(AnsiString('packageversion_'+IntToStr(i)), AnsiString(pkg.Version)); + http.Fields.Add(AnsiString('packageid_'+IntToStr(i)), AnsiString(pkg.ID)); + http.Fields.Add(AnsiString('packageversion_'+IntToStr(i)), AnsiString(pkg.Version)); pkg := nil; end; - Proxy.Server := GetProxySettings.Server; - Proxy.Port := GetProxySettings.Port; - Proxy.Username := GetProxySettings.Username; - Proxy.Password := GetProxySettings.Password; + http.Proxy.Server := GetProxySettings.Server; + http.Proxy.Port := GetProxySettings.Port; + http.Proxy.Username := GetProxySettings.Username; + http.Proxy.Password := GetProxySettings.Password; - Request.HostName := API_Server; - Request.Protocol := API_Protocol; - Request.UrlPath := API_Path_UpdateCheck_Windows; + http.Request.HostName := API_Server; + http.Request.Protocol := API_Protocol; + http.Request.UrlPath := API_Path_UpdateCheck_Windows; //OnStatus := - Upload; - if Response.StatusCode = 200 then + http.Upload; + if http.Response.StatusCode = 200 then begin - if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then + if ucr.Parse(http.Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin //ResponseToParams(ucr); @@ -348,9 +352,9 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; end; end else - raise ERemoteUpdateCheck.Create('Error '+IntToStr(Response.StatusCode)); + raise ERemoteUpdateCheck.Create('Error '+IntToStr(http.Response.StatusCode)); finally - Free; + http.Free; end; except on E:EHTTPUploader do @@ -367,12 +371,12 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; end; end; - with TRegistryErrorControlled.Create do // I2890 + registry := TRegistryErrorControlled.Create; // I2890 try - if OpenKey(SRegKey_KeymanDesktop_CU, True) then - WriteDateTime(SRegValue_LastUpdateCheckTime, Now); + if registry.OpenKey(SRegKey_KeymanDesktop_CU, True) then + registry.WriteDateTime(SRegValue_LastUpdateCheckTime, Now); finally - Free; + registry.Free; end; end; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 5affe65cafa..4076ab21015 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -384,6 +384,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 + RemoteUpdateCheck: TRemoteUpdateCheck; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -432,15 +433,15 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - with TRemoteUpdateCheck.Create(False, False) do + RemoteUpdateCheck := TRemoteUpdateCheck.Create(False, False); try if (FMode = fmBackgroundUpdateCheck) then begin - Run; + RemoteUpdateCheck.Run; Exit; end finally - Free; + RemoteUpdateCheck.Free; end; From 92ca7113fc599a1f5f0c5a490beab0aae41e9fa1 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:51:56 +1000 Subject: [PATCH 10/11] feat(windows): Marking TODOs to be handled in next PR --- .../main/Keyman.System.RemoteUpdateCheck.pas | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 077e4b42de0..d9fb1ab6c2b 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -1,20 +1,9 @@ -(* - Name: WebUpdateCheck - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 5 Dec 2023 - - Modified Date: - Authors: rcruickshank - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: -*) +{ + * Keyman is copyright (C) SIL International. MIT License. + * + * Keyman.System.RemoteUpdateCheck: Checks for keyboard package and Keyman + for Windows updates. +} unit Keyman.System.RemoteUpdateCheck; // I3306 interface @@ -195,7 +184,7 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate // Keyman Installer if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 begin - // TODO record fail? and log // Download failed but user wants to install other files + // TODO: #10210record fail? and log // Download failed but user wants to install other files end else begin @@ -264,7 +253,7 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) and not FForce then begin Result := wucNoUpdates; - // TODO: This exit is just to remove the time check for testing. + // TODO: #10210 This exit is just to remove the time check for testing. //Exit; end; @@ -328,11 +317,10 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; if FCheckOnly then begin - // TODO: Refactor this TUpdateCheckStorage.SaveUpdateCacheData(ucr); Result := FRemoteResult; end - // TODO: #10038 + // TODO: ##10210 // Integerate into state machine. in the download state // the process can call LoadUpdateCacheData if needed to get the // response result. From 9909eb759a0e0808810a1a9564f75c53fd88fcd0 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:36:07 +1000 Subject: [PATCH 11/11] feat(windows): move exception for each file download --- .../main/Keyman.System.RemoteUpdateCheck.pas | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index d9fb1ab6c2b..0eebd52ff93 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -119,91 +119,91 @@ procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdate function DownloadFile(const url, savepath: string): Boolean; begin - http := THttpUploader.Create(nil); try - http.Proxy.Server := GetProxySettings.Server; - http.Proxy.Port := GetProxySettings.Port; - http.Proxy.Username := GetProxySettings.Username; - http.Proxy.Password := GetProxySettings.Password; - http.Request.Agent := API_UserAgent; - - http.Request.SetURL(url); - http.Upload; - if http.Response.StatusCode = 200 then + http := THttpUploader.Create(nil); + try + http.Proxy.Server := GetProxySettings.Server; + http.Proxy.Port := GetProxySettings.Port; + http.Proxy.Username := GetProxySettings.Username; + http.Proxy.Password := GetProxySettings.Password; + http.Request.Agent := API_UserAgent; + + http.Request.SetURL(url); + http.Upload; + if http.Response.StatusCode = 200 then + begin + fs := TFileStream.Create(savepath, fmCreate); + try + fs.Write(http.Response.PMessageBody^, http.Response.MessageBodyLength); + finally + fs.Free; + end; + Result := True; + end + else // I2742 + // If it fails we set to false but will try the other files + Result := False; + Exit; + finally + http.Free; + end; + except + on E:EHTTPUploader do begin - fs := TFileStream.Create(savepath, fmCreate); - try - fs.Write(http.Response.PMessageBody^, http.Response.MessageBodyLength); - finally - fs.Free; - end; - Result := True; - end - else // I2742 - // If it fails we set to false but will try the other files + if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) + then LogMessage(S_OnlineUpdate_UnableToContact) + else LogMessage(WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message])); Result := False; - Exit; - finally - http.Free; + end; end; end; - begin Result := False; - try - FDownload.TotalSize := 0; - FDownload.TotalDownloads := 0; - downloadCount := 0; - // Keyboard Packages - for i := 0 to High(Params.Packages) do - begin - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); - Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; - end; + FDownload.TotalSize := 0; + FDownload.TotalDownloads := 0; + downloadCount := 0; - // Add the Keyman installer + // Keyboard Packages + for i := 0 to High(Params.Packages) do + begin Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.InstallSize); + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; + end; - // Keyboard Packages - FDownload.StartPosition := 0; - for i := 0 to High(Params.Packages) do - begin - if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742 - begin - Params.Packages[i].Install := False; // Download failed but install other files - end - else - Inc(downloadCount); - FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; - end; + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); - // Keyman Installer - if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 - begin - // TODO: #10210record fail? and log // Download failed but user wants to install other files - end - else + // Keyboard Packages + FDownload.StartPosition := 0; + for i := 0 to High(Params.Packages) do begin - Inc(downloadCount) + if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742 + begin + Params.Packages[i].Install := False; // Download failed but install other files + end + else + Inc(downloadCount); + FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; end; - // There needs to be at least one file successfully downloaded to return - // True that files were downloaded - if downloadCount > 0 then - Result := True; - except - on E:EHTTPUploader do - begin - if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) - then LogMessage(S_OnlineUpdate_UnableToContact) - else LogMessage(WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message])); - Result := False; - end; + // Keyman Installer + if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 + begin + // TODO: #10210record fail? and log // Download failed but user wants to install other files + end + else + begin + Inc(downloadCount) end; + + // There needs to be at least one file successfully downloaded to return + // True that files were downloaded + if downloadCount > 0 then + Result := True; end; function TRemoteUpdateCheck.DownloadUpdates(Params: TUpdateCheckResponse): Boolean;