From 9a14039e8cb1f91e2d7c45e5cd54a9f315433472 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Nov 2023 14:16:32 +1000 Subject: [PATCH 001/124] =?UTF-8?q?chore:=20establish=20epic/windows-updat?= =?UTF-8?q?es=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 2a0d49470e..301a8409db 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 002/124] 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 918c396411..adc90ab152 100644 --- a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas +++ b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas @@ -35,6 +35,7 @@ type TUpdateCheckResponse = record private + FOriginalData: string; FInstallSize: Int64; FInstallURL: string; FNewVersion: string; @@ -46,9 +47,13 @@ type 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 @@ type 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 @@ begin 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 88ef8282ef..76251c3b51 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -16,6 +16,8 @@ type 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 @@ type 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 @@ begin 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 befb47fcf9..1497982ccb 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 5d716d8c4a..71c91d9e56 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 0000000000..f1a156bfa0 --- /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 15fb06f4e9..c202742813 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -98,6 +98,7 @@ type FShowErrors: Boolean; FDownload: TOnlineUpdateCheckDownloadParams; + FCheckOnly: Boolean; function DownloadUpdates: Boolean; procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean); @@ -112,7 +113,9 @@ type 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 @@ uses KLog, keymanapi_TLB, KeymanVersion, + Keyman.System.UpdateCheckStorage, kmint, ErrorControlledRegistry, RegistryKeys, @@ -165,7 +169,7 @@ const { 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 @@ begin FSilent := ASilent; FForce := AForce; + FCheckOnly := ACheckOnly; KL.Log('TOnlineUpdateCheck.Create'); end; @@ -477,10 +482,9 @@ end; 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 @@ begin 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 + ResponseToParams(ucr); + + if FCheckOnly then 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; - - if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + // 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 @@ begin 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 d9a6f8b441..969e85d99c 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -148,6 +148,7 @@ type procedure OpenSite(params: TStringList); procedure DoApply; procedure DoRefresh; + procedure Update_CheckNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -202,6 +203,7 @@ uses UfrmTextEditor, uninstall, Upload_Settings, + UpdateXMLRenderer, utildir, utilexecute, utilkmshell, @@ -301,6 +303,7 @@ begin 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 @@ begin 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 @@ begin 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 0000000000..0c102fdca7 --- /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 88c8d22b39..704c4e6f92 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 cf761ce6c2..47b66ca89c 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 94c7ed7e95..a774e0aa9c 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 780166619f..0000000000 --- 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 4fc1b0f225..99d5e05fac 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 d472f88155..e33d031a47 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 0000000000..9acfd28d3b --- /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 003/124] 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 1497982ccb..c4f21ea44d 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 969e85d99c..a779fdcdd2 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -350,7 +350,6 @@ begin 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 004/124] feat(windows): remove UI comment for downloading updates --- windows/src/desktop/kmshell/kmshell.dpr | 5 +- windows/src/desktop/kmshell/kmshell.dproj | 15 +- .../kmshell/main/RemoteUpdateCheck.pas | 460 ++++++++++++++++++ windows/src/desktop/kmshell/main/initprog.pas | 16 + 4 files changed, 487 insertions(+), 9 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 c4f21ea44d..2b1e14b517 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 71c91d9e56..2e6d27e6cd 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -355,6 +355,7 @@ + Cfg_2 @@ -416,24 +417,24 @@ False - + kmshell.rsm true + + + kmshell.exe + true + + .\ true - - - kmshell.exe - true - - 1 diff --git a/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/RemoteUpdateCheck.pas new file mode 100644 index 0000000000..75c57315a9 --- /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 c22591e79c..29fe102d21 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -82,6 +82,7 @@ type fmMigrate, fmSplash, fmStart, fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, + fmBackgroundUpdateCheck, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 @@ -120,6 +121,7 @@ uses KMShellHints, KeymanMutex, OnlineUpdateCheck, + RemoteUpdateCheck, RegistryKeys, UfrmBaseKeyboard, UfrmKeymanBase, @@ -249,6 +251,7 @@ begin 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 @@ begin 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 005/124] 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 2b1e14b517..71e434af91 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 2e6d27e6cd..16f49e757d 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 75c57315a9..310eca36f4 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 @@ uses 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 @@ begin 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 29fe102d21..5affe65caf 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -121,7 +121,7 @@ uses 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 006/124] 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 310eca36f4..fc40f584cd 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 @@ type 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 @@ type TRemoteUpdateCheck = class private FForce: Boolean; - FParams: TRemoteUpdateCheckParams; + FRemoteResult: TRemoteUpdateCheckResult; FErrorMessage: string; @@ -57,11 +49,10 @@ type 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 @@ uses ErrorControlledRegistry, RegistryKeys, Upload_Settings, - //utildir, - //utilexecute, + OnlineUpdateCheckMessages; - //utilkmshell, - // utilsystem, - //utiluac, - //versioninfo; { TRemoteUpdateCheck } @@ -103,7 +88,7 @@ begin inherited Create; FShowErrors := True; - FParams.Result := wucUnknown; + FRemoteResult := wucUnknown; FForce := AForce; FCheckOnly := ACheckOnly; @@ -117,7 +102,7 @@ begin 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 @@ begin 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 @@ begin 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 @@ begin 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 @@ begin 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 @@ begin 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 007/124] 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 0c102fdca7..32fd7b2cf6 100644 --- a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -30,7 +30,6 @@ uses 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 e83b0022d281300f0ab77a5b82a4d02ab3cfba38 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:15:42 +1000 Subject: [PATCH 008/124] feat(windows): WIP buc sm doesn't build just backup background update state machine doesn't even build --- .../windows/delphi/general/RegistryKeys.pas | 5 + .../desktop/kmshell/main/BackgroundUpdate.pas | 1244 +++++++++++++++++ .../main/Keyman.System.DownloadUpdate.pas | 166 +++ .../main/Keyman.System.RemoteUpdateCheck.pas | 151 +- 4 files changed, 1458 insertions(+), 108 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/BackgroundUpdate.pas create mode 100644 windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 62599851cd..7404a2af4f 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -162,6 +162,7 @@ const SRegKey_KeymanDesktop_CU = SRegKey_KeymanDesktopRoot_CU; SRegKey_KeymanDesktop_LM = SRegKey_KeymanDesktopRoot_LM; + { Other Keyman Settings } SRegValue_DeadkeyConversionMode = 'deadkey conversion mode'; // CU // I4552 @@ -177,6 +178,9 @@ const SRegValue_AvailableLanguages = 'available languages'; //CU SRegValue_CurrentLanguage = 'current language'; //CU + SRegValue_Install_Update = 'install update'; + SRegValue_Update_State = 'update state'; + { Privacy } SRegValue_AutomaticallyReportErrors = 'automatically report errors'; // CU, SRegKey_IDEOptions and SRegKey_KeymanEngine_CU @@ -297,6 +301,7 @@ const SRegKey_KeymanDeveloperRoot_LM = SRegKey_KeymanRoot_LM + '\Keyman Developer'; // LM CU SRegKey_KeymanDeveloper_LM = SRegKey_KeymanDeveloperRoot_LM; // LM CU + SRegKey_IDE_CU = SRegKey_KeymanDeveloper_CU + '\IDE'; // CU SRegKey_IDEDock_CU = SRegKey_IDE_CU + '\Dock'; // CU SRegKey_IDEFiles_CU = SRegKey_IDE_CU + '\Files'; // CU diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas new file mode 100644 index 0000000000..a8d4e72cab --- /dev/null +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -0,0 +1,1244 @@ +(* + Name: BackgroundUpdate + Copyright: Copyright (C) SIL International. + Documentation: + Description: + Create Date: 2 Nov 2023 + + Modified Date: 2 Nov 2023 + Authors: rcruickshank + Related Files: + Dependencies: + + Bugs: + Todo: + Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md + History: +*) +unit BackgroundUpdate; + +interface + +uses + System.Classes, + System.SysUtils, + System.UITypes, + System.IOUtils, + System.Types, + Vcl.Forms, + TypInfo, + KeymanPaths, + utilkmshell, + + httpuploader, + Keyman.System.UpdateCheckResponse, + UfrmDownloadProgress; + +type + EBackgroundUpdate = class(Exception); + + TBackgroundUpdateResult = (oucUnknown, oucShutDown, oucSuccess, oucNoUpdates, oucUpdatesAvailable, oucFailure, oucOffline); + + TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, usInstalling, usRetry, usWaitingPostInstall); + + { Keyboard Package Params } + TBackgroundUpdateParamsPackage = record + ID: string; + NewID: string; + Description: string; + OldVersion, NewVersion: string; + DownloadURL: string; + SavePath: string; + FileName: string; + DownloadSize: Integer; + Install: Boolean; + end; + { Main Keyman Program } + TBackgroundUpdateParamsKeyman = record + OldVersion, NewVersion: string; + DownloadURL: string; + SavePath: string; + FileName: string; + DownloadSize: Integer; + Install: Boolean; + end; + + TBackgroundUpdateParams = record + Keyman: TBackgroundUpdateParamsKeyman; + Packages: array of TBackgroundUpdateParamsPackage; + Result: TBackgroundUpdateResult; + end; + + TBackgroundUpdateDownloadParams = record + Owner: TfrmDownloadProgress; + TotalSize: Integer; + TotalDownloads: Integer; + StartPosition: Integer; + end; + + // Forward declaration + TBackgroundUpdate = class; + { State Classes Update } + + TStateClass = class of TState; + + TState = class abstract + private + bucStateContext: TBackgroundUpdate; + procedure ChangeState(newState: TStateClass); + + public + constructor Create(Context: TBackgroundUpdate); + procedure Enter; virtual; abstract; + procedure Exit; virtual; abstract; + procedure HandleCheck; virtual; abstract; + procedure HandleDownload; virtual; abstract; + function HandleKmShell : Integer; virtual; abstract; + procedure HandleInstall; virtual; abstract; + procedure HandleMSIInstallComplete; virtual; abstract; + procedure HandleAbort; virtual; abstract; + + // For convenience + function StateName: string; virtual; abstract; + + end; + + // Derived classes for each state + IdleState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + UpdateAvailableState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + DownloadingState = class(TState) + private + { These function could become members of the state objects + or at the very least controlled by the state objects } + + { TODO: make a comment clear when we are in a elevate process} + + { + Performs updates download in the background, without displaying a GUI + progress bar. This function is similar to DownloadUpdates, but it runs in + the background. + + @returns True if all updates were successfully downloaded, False if any + download failed. + } + + function DownloadUpdatesBackground: Boolean; + { + Performs updates download in the background, without displaying a GUI + progress bar. This procedure is similar to DownloadUpdates, but it runs in + the background. + + @params SavePath The path where the downloaded files will be saved. + Result A Boolean value indicating the overall result of the + download process. + } + procedure DoDownloadUpdatesBackground(SavePath: string; var Result: Boolean); + { + Performs an online update check, including package retrieval and version + query. + + This function checks if a week has passed since the last update check. It + utilizes the kmcom API to retrieve the current packages. The function then + performs an HTTP request to query the remote versions of these packages. + The resulting information is stored in the FParams variable. Additionally, + the function handles the main Keyman install package. + + @returns A TBackgroundUpdateResult indicating the result of the update + check. + } + // This is just for testing only. + procedure DoDownloadUpdatesBackgroundTest(SavePath: string; var Result: Boolean); + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + WaitingRestartState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + InstallingState = class(TState) + private + procedure DoInstallKeyman; overload; + function DoInstallKeyman(SavePath: string) : Boolean; overload; + { + Installs the Keyman file using either msiexec.exe or the setup launched in + a separate shell. + + @params Package The package to be installed. + + @returns True if the installation is successful, False otherwise. + } + function DoInstallPackage(Package: TBackgroundUpdateParamsPackage): Boolean; + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + RetryState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + WaitingPostInstallState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + function StateName: string; override; + end; + + { This class also controls the state flow see } + TBackgroundUpdate = class + private + FOwner: TCustomForm; + FSilent: Boolean; + FForce: Boolean; + FAuto: Boolean; + FParams: TBackgroundUpdateParams; + + FErrorMessage: string; + + DownloadTempPath: string; + + FShowErrors: Boolean; + + + + FDownload: TBackgroundUpdateDownloadParams; + + CurrentState: TState; + // State object for performance (could lazy create?) + FIdle: IdleState; + FUpdateAvailable: UpdateAvailableState; + FDownloading: DownloadingState; + FWaitingRestart: WaitingRestartState; + FInstalling: InstallingState; + FRetry: RetryState; + FWaitingPostInstall: WaitingPostInstallState; + function GetState: TStateClass; + procedure SetState(const Value: TStateClass); + function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; + + procedure ShutDown; + + { + SavePackageUpgradesToDownloadTempPath saves any new package IDs to a + single file in the download tempPath. This procedure saves the IDs of any + new packages to a file named "upgrade_packages.inf" in the download + tempPath. + } + procedure SavePackageUpgradesToDownloadTempPath; + function IsKeymanRunning: Boolean; + function checkUpdateSchedule : Boolean; + + function SetRegistryState (Update : TUpdateState): Boolean; + + protected + property State: TStateClass read GetState write SetState; + + public + constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean); + destructor Destroy; override; + + procedure HandleCheck; + function HandleKmShell : Integer; + procedure HandleDownload; + procedure HandleInstall; + procedure HandleMSIInstallComplete; + procedure HandleAbort; + function CurrentStateName: string; + + property ShowErrors: Boolean read FShowErrors write FShowErrors; + function CheckRegistryState : TUpdateState; + + end; + + IOnlineUpdateSharedData = interface + ['{7442A323-C1E3-404B-BEEA-5B24A52BBB0E}'] + function Params: TBackgroundUpdateParams; + end; + + TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) + private + FParams: TBackgroundUpdateParams; + public + constructor Create(AParams: TBackgroundUpdateParams); + function Params: TBackgroundUpdateParams; + end; + +implementation + +uses + Winapi.Shlobj, + System.WideStrUtils, + Vcl.Dialogs, + Winapi.ShellApi, + Winapi.Windows, + Winapi.WinINet, + + GlobalProxySettings, + KLog, + keymanapi_TLB, + KeymanVersion, + kmint, + ErrorControlledRegistry, + RegistryKeys, + Upload_Settings, + utildir, + utilexecute, + OnlineUpdateCheckMessages, // todo create own messages + UfrmOnlineUpdateIcon, + UfrmOnlineUpdateNewVersion, + utilsystem, + utiluac, + versioninfo; + +const + SPackageUpgradeFilename = 'upgrade_packages.inf'; + kmShellContinue = 0; + kmShellExit = 1; + +{ TBackgroundUpdate } + +constructor TBackgroundUpdate.Create(AOwner: TCustomForm; AForce, ASilent: Boolean); +var TSerailsedState : TUpdateState; +begin + inherited Create; + + FOwner := AOwner; + + FShowErrors := True; + FParams.Result := oucUnknown; + + FSilent := ASilent; + FForce := AForce; + FAuto := True; // Default to automatically check, download, and install + FIdle := IdleState.Create(Self); + FUpdateAvailable := UpdateAvailableState.Create(Self); + FDownloading := DownloadingState.Create(Self); + FWaitingRestart := WaitingRestartState.Create(Self); + FInstalling := InstallingState.Create(Self); + FRetry := RetryState.Create(Self); + FWaitingPostInstall := WaitingPostInstallState.Create(Self); + // Check the Registry setting. + state := ConvertEnumState(CheckRegistryState); + KL.Log('TBackgroundUpdate.Create'); +end; + +destructor TBackgroundUpdate.Destroy; +begin + if (FErrorMessage <> '') and not FSilent and FShowErrors then + ShowMessage(FErrorMessage); + + if FParams.Result = oucShutDown then + ShutDown; + + FIdle.Free; + FUpdateAvailable.Free; + FDownloading.Free; + FWaitingRestart.Free; + FInstalling.Free; + FRetry.Free; + FWaitingPostInstall.Free; + + KL.Log('TBackgroundUpdate.Destroy: FErrorMessage = '+FErrorMessage); + KL.Log('TBackgroundUpdate.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + + inherited Destroy; +end; + + +procedure TBackgroundUpdate.SavePackageUpgradesToDownloadTempPath; +var + i: Integer; +begin + with TStringList.Create do + try + for i := 0 to High(FParams.Packages) do + if FParams.Packages[i].NewID <> '' then + Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); + if Count > 0 then + SaveToFile(DownloadTempPath + SPackageUpgradeFileName); + finally + Free; + end; +end; + +procedure TBackgroundUpdate.ShutDown; +begin + if Assigned(Application) then + Application.Terminate; +end; + + +{ TOnlineUpdateSharedData } + +constructor TOnlineUpdateSharedData.Create(AParams: TBackgroundUpdateParams); +begin + inherited Create; + FParams := AParams; +end; + +function TOnlineUpdateSharedData.Params: TBackgroundUpdateParams; +begin + Result := FParams; +end; + + +function TBackgroundUpdate.SetRegistryState(Update : TUpdateState): Boolean; +var + UpdateStr : string; +begin + + Result := False; + with TRegistryErrorControlled.Create do + try + RootKey := HKEY_LOCAL_MACHINE; + KL.Log('SetRegistryState State Entry'); + if OpenKey(SRegKey_KeymanEngine_LM, True) then + begin + UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); + WriteString(SRegValue_Update_State, UpdateStr); + KL.Log('SetRegistryState State is:[' + UpdateStr + ']'); + end; + Result := True; + finally + Free; + end; + +end; + + +function TBackgroundUpdate.CheckRegistryState : TUpdateState; // I2329 +var + UpdateState : TUpdateState; + +begin + // We will use a registry flag to maintain the state of the background update + + UpdateState := usIdle; // do we need a unknown state ? + // check the registry value + with TRegistryErrorControlled.Create do // I2890 + try + RootKey := HKEY_LOCAL_MACHINE; + if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Update_State) then + begin + UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), ReadString(SRegValue_Update_State))); + KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); + end + else + begin + UpdateState := usIdle; // do we need a unknown state ? + KL.Log('CheckRegistryState State reg value not found default:[' + ReadString(SRegValue_Update_State) + ']'); + end + finally + Free; + end; + Result := UpdateState; +end; + + + +function TBackgroundUpdate.IsKeymanRunning: Boolean; // I2329 +begin + try + Result := kmcom.Control.IsKeymanRunning; + except + on E:Exception do + begin + KL.Log(E.Message); + Exit(False); + end; + end; +end; + +function TBackgroundUpdate.CheckUpdateSchedule: Boolean; +begin + try + Result := False; + with TRegistryErrorControlled.Create do + try + if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + begin + if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then + begin + Result := False; + Exit; + end; + if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then + begin + Result := False; + Exit; + end; + // Else Time to check for updates + Result := True; + end; + finally + Free; + end; + except + { we will not run the check if an error occurs reading the settings } + on E:Exception do + begin + Result := False; + FErrorMessage := E.Message; + Exit; + end; + end; +end; + +function TBackgroundUpdate.GetState: TStateClass; +begin + Result := TStateClass(CurrentState.ClassType); +end; + +procedure TBackgroundUpdate.SetState(const Value: TStateClass); +begin + if Assigned(CurrentState) then + begin + CurrentState.Exit; + end; + + if Value = IdleState then + begin + CurrentState := FIdle; + end + else if Value = UpdateAvailableState then + begin + CurrentState := FUpdateAvailable; + end + else if Value = DownloadingState then + begin + CurrentState := FDownloading; + end + else if Value = WaitingRestartState then + begin + CurrentState := FWaitingRestart; + end + else if Value = InstallingState then + begin + CurrentState := FInstalling; + end + else if Value = RetryState then + begin + CurrentState := FRetry; + end + else if Value = WaitingPostInstallState then + begin + CurrentState := FWaitingPostInstall; + end; + + if Assigned(CurrentState) then + begin + CurrentState.Enter; + end + else + begin + // TODO: Unable to set state for Value [] + end; + +end; + +function TBackgroundUpdate.ConvertEnumState(const TEnumState: TUpdateState) : TStateClass; +begin + case TEnumState of + usIdle: Result := IdleState; + usUpdateAvailable: Result := UpdateAvailableState; + usDownloading: Result := DownloadingState; + usWaitingRestart: Result := WaitingRestartState; + usInstalling: Result := InstallingState; + usRetry: Result := RetryState; + usWaitingPostInstall: Result := WaitingPostInstallState; + else + // Log error unknown state setting to idle + Result := IdleState; + end; +end; + +procedure TBackgroundUpdate.HandleCheck; +begin + CurrentState.HandleCheck; +end; + +function TBackgroundUpdate.HandleKmShell; +begin + Result := CurrentState.HandleKmShell; +end; + +procedure TBackgroundUpdate.HandleDownload; +begin + CurrentState.HandleDownload; +end; + +procedure TBackgroundUpdate.HandleInstall; +begin + CurrentState.HandleInstall; +end; + +procedure TBackgroundUpdate.HandleMSIInstallComplete; +begin + CurrentState.HandleMSIInstallComplete; +end; + +procedure TBackgroundUpdate.HandleAbort; +begin + CurrentState.HandleAbort; +end; + +function TBackgroundUpdate.CurrentStateName: string; +begin + // Implement your logic here + Result := CurrentState.StateName; +end; + + + +{ State Class Memebers } +constructor TState.Create(Context: TBackgroundUpdate); +begin + bucStateContext := Context; +end; + +procedure TState.ChangeState(NewState: TStateClass); +begin + bucStateContext.State := NewState; +end; + + +{ IdleState } + +procedure IdleState.Enter; +begin + // Enter UpdateAvailableState + // register name + bucStateContext.SetRegistryState(usIdle); +end; + +procedure IdleState.Exit; +begin + // Exit UpdateAvailableState +end; + +procedure IdleState.HandleCheck; +begin + { TODO: Verify that it has been at least 7 days since last update check - + only if FSilent = TRUE } + + { Make a HTTP request out and see if updates are available for now do + this all in the Idle HandleCheck message. But could be broken into an + seperate state of WaitngCheck RESP } + { if Response not OK stay in the idle state and return } + + { Response OK and Update is available } + ChangeState(UpdateAvailableState); + +end; + +procedure IdleState.HandleDownload; +begin + // Implement your logic here +end; + +function IdleState.HandleKmShell; +begin + // Implement your logic here + Result := kmShellContinue; +end; + +procedure IdleState.HandleInstall; +begin + // Implement your logic here +end; + +procedure IdleState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure IdleState.HandleAbort; +begin + // Implement your logic here +end; + +function IdleState.StateName; +begin + // Implement your logic here + Result := 'IdleState'; +end; + +{ UpdateAvailableState } + +procedure UpdateAvailableState.Enter; +begin + // Enter UpdateAvailableState + bucStateContext.SetRegistryState(usUpdateAvailable); + if bucStateContext.FAuto then + begin + bucStateContext.CurrentState.HandleDownload ; + end; +end; + +procedure UpdateAvailableState.Exit; +begin + // Exit UpdateAvailableState +end; + +procedure UpdateAvailableState.HandleCheck; +begin + // Implement your logic here +end; + +procedure UpdateAvailableState.HandleDownload; +begin + ChangeState(DownloadingState); +end; + +function UpdateAvailableState.HandleKmShell; +begin + if bucStateContext.FAuto then + begin + bucStateContext.CurrentState.HandleDownload ; + end; + Result := kmShellContinue; +end; + +procedure UpdateAvailableState.HandleInstall; +begin + // Implement your logic here +end; + +procedure UpdateAvailableState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure UpdateAvailableState.HandleAbort; +begin + // Implement your logic here +end; + +function UpdateAvailableState.StateName; +begin + // Implement your logic here + Result := 'UpdateAvailableState'; +end; + +{ DownloadingState } + +procedure DownloadingState.Enter; +var DownloadResult : Boolean; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usDownloading); + DownloadResult := DownloadUpdatesBackground; + if DownloadResult then + begin + if bucStateContext.IsKeymanRunning then + ChangeState(WaitingRestartState) + else + ChangeState(InstallingState); + end + else + begin + ChangeState(RetryState); + end +end; + +procedure DownloadingState.Exit; +begin + // Exit DownloadingState +end; + +procedure DownloadingState.HandleCheck; +begin + // For now just pretend updated found +end; + +procedure DownloadingState.HandleDownload; +var DownloadResult : Boolean; +begin + // We are already downloading do nothing +end; + +function DownloadingState.HandleKmShell; +var DownloadResult : Boolean; +begin + DownloadResult := DownloadUpdatesBackground; + // TODO check if keyman is running then send to Waiting Restart + if DownloadResult then + begin + ChangeState(InstallingState); + end + else + begin + ChangeState(RetryState); + end; + Result := kmShellContinue; +end; + +procedure DownloadingState.HandleInstall; +begin + // Implement your logic here + ChangeState(InstallingState); +end; + +procedure DownloadingState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure DownloadingState.HandleAbort; +begin + // Implement your logic here +end; + +function DownloadingState.StateName; +begin + // Implement your logic here + Result := 'DownloadingState'; +end; + +procedure DownloadingState.DoDownloadUpdatesBackground(SavePath: string; var Result: Boolean); +begin +end; + +// Test installing only +procedure DownloadingState.DoDownloadUpdatesBackgroundTest(SavePath: string; var Result: Boolean); +var + i, downloadCount: Integer; + UpdateDir : string; + +begin + try + Result := False; + + UpdateDir := 'C:\Projects\rcswag\testCache'; + KL.Log('DoDownloadUpdatesBackgroundTest SavePath:'+ SavePath); + // Check if the update source directory exists + if DirectoryExists(UpdateDir) then + begin + // Create the update cached directory if it doesn't exist + if not DirectoryExists(SavePath) then + ForceDirectories(SavePath); + + // Copy all files from the updatedir to savepath + TDirectory.Copy(UpdateDir, SavePath); + Result:= True; + KL.Log('All files copied successfully.'); + end + else + KL.Log('Source directory does not exist.'); + except + on E: Exception do + KL.Log('Error: ' + E.Message); + end; + +end; + + +function DownloadingState.DownloadUpdatesBackground: Boolean; +var + i: Integer; + DownloadBackGroundSavePath : String; + DownloadResult : Boolean; +begin + //DownloadTempPath := IncludeTrailingPathDelimiter(CreateTempPath); + DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); + //DownloadBackGroundSavePath := DownloadBackGroundSavePath + 'RCTest.txt'; + { For now lets download all the updates. We need to take these from the user via check box form } + + if bucStateContext.FParams.Keyman.DownloadURL <> '' then + bucStateContext.FParams.Keyman.Install := True; + + for i := 0 to High(bucStateContext.FParams.Packages) do + bucStateContext.FParams.Packages[i].Install := True; + + // Download files + // DoDownloadUpdatesBackground(DownloadBackGroundSavePath, DownloadResult); + // For development by passing the DoDownloadUpdatesBackground and just add + // the cached file on my local disk as a simulation + DoDownloadUpdatesBackgroundTest(DownloadBackGroundSavePath, DownloadResult); + + KL.Log('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + //ShowMessage('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; +end; + +{ WaitingRestartState } + +procedure WaitingRestartState.Enter; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usWaitingRestart); +end; + +procedure WaitingRestartState.Exit; +begin + // Exit DownloadingState +end; + +procedure WaitingRestartState.HandleCheck; +begin + // Implement your logic here +end; + +procedure WaitingRestartState.HandleDownload; +begin + // Implement your logic here +end; + +function WaitingRestartState.HandleKmShell; +begin + // Check downloaded cache if available then + // change state intalling + // else then change to idle and handle checkupdates state + // if bucStateContext.IsKeymanRunning then + // stay as waiting + // log this is unexpected + //ChangeState(WaitingRestartState) + // else + // + + ChangeState(UpdateAvailableState); + Result := kmShellExit; +end; + +procedure WaitingRestartState.HandleInstall; +begin + // Implement your logic here +end; + +procedure WaitingRestartState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure WaitingRestartState.HandleAbort; +begin + // Implement your logic here +end; + +function WaitingRestartState.StateName; +begin + // Implement your logic here + Result := 'WaitingRestartState'; +end; + +{ InstallingState } + +function InstallingState.DoInstallPackage(Package: TBackgroundUpdateParamsPackage): Boolean; +var + FPackage: IKeymanPackageFile2; +begin + Result := True; + + FPackage := kmcom.Packages.GetPackageFromFile(Package.SavePath) as IKeymanPackageFile2; + FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact + FPackage := nil; + + kmcom.Refresh; + kmcom.Apply; + System.SysUtils.DeleteFile(Package.SavePath); +end; + +procedure InstallingState.DoInstallKeyman; +var + s: string; + FResult: Boolean; +begin + s := LowerCase(ExtractFileExt(bucStateContext.FParams.Keyman.SavePath)); + if s = '.msi' then + FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+bucStateContext.FParams.Keyman.SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 + else if s = '.exe' then + FResult := TUtilExecute.Shell(0, bucStateContext.FParams.Keyman.SavePath, '', '-au') // I3349 + else + Exit; + if not FResult then + ShowMessage(SysErrorMessage(GetLastError)); +end; + +function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; +var + s: string; + FResult: Boolean; +begin + s := LowerCase(ExtractFileExt(SavePath)); + if s = '.msi' then + FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 + else if s = '.exe' then + FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 + else + Exit; + //Exit(False); + + if not FResult then + begin + KL.Log('TBackgroundUpdate.InstallingState.DoInstall: Result = '+IntToStr(Ord(FResult))); + // Log messageShowMessage(SysErrorMessage(GetLastError)); + end; + + Result := FResult; +end; + +procedure InstallingState.Enter; +var + SavePath: String; + fileExt : String; + fileName: String; + fileNames: TStringDynArray; +begin + bucStateContext.SetRegistryState(usInstalling); + // Needs to be desing discusion about the correct location for the cache + //SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); + // For testing + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + + GetFileNamesInDirectory(SavePath, fileNames); + // for now we only want the exe although excute install can + // handle msi + for fileName in fileNames do + begin + fileExt := LowerCase(ExtractFileExt(fileName)); + if fileExt = '.exe' then + break; + end; + // ExecuteInstall(SavePath + ExtractFileName(fileName)); + // TODO DoInstallPackages ( this may need to be as state in the enum seperate + // to installing the main keyman executable. + if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then + begin + KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall OK'); + end + else + begin + // TODO: clean failed download + // TODO: Do we do a retry on install? probably not + // install error log the error. + KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall fail'); + ChangeState(IdleState); + end +end; + +procedure InstallingState.Exit; +begin + // Exit DownloadingState +end; + +procedure InstallingState.HandleCheck; +begin + // Implement your logic here +end; + +procedure InstallingState.HandleDownload; +begin + // Implement your logic here +end; + +function InstallingState.HandleKmShell; +begin + // Result = exit straight away as we are installing (MSI installer) + Result := kmShellExit; +end; + +procedure InstallingState.HandleInstall; +begin + // Implement your logic here +end; + +procedure InstallingState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure InstallingState.HandleAbort; +begin + ChangeState(IdleState); +end; + +function InstallingState.StateName; +begin + // Implement your logic here + Result := 'InstallingState'; +end; + +{ RetryState } + +procedure RetryState.Enter; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usRetry); +end; + +procedure RetryState.Exit; +begin + // Exit DownloadingState +end; + +procedure RetryState.HandleCheck; +begin + // Implement your logic here +end; + +procedure RetryState.HandleDownload; +begin + // Implement your logic here +end; + +function RetryState.HandleKmShell; +begin + // TODO Implement retry + Result := kmShellContinue +end; + +procedure RetryState.HandleInstall; +begin + // Implement your logic here +end; + +procedure RetryState.HandleMSIInstallComplete; +begin + // Implement your logic here +end; + +procedure RetryState.HandleAbort; +begin + // Implement your logic here +end; + +function RetryState.StateName; +begin + // Implement your logic here + Result := 'RetryState'; +end; + +{ WaitingPostInstallState } + +procedure WaitingPostInstallState.Enter; +begin + // Enter downloading state + bucStateContext.SetRegistryState(usWaitingPostInstall); +end; + +procedure WaitingPostInstallState.Exit; +begin + // Exit downloading state +end; + +procedure WaitingPostInstallState.HandleCheck; +begin + // Handle Check +end; + +procedure WaitingPostInstallState.HandleDownload; +begin + // Handle Download +end; + +function WaitingPostInstallState.HandleKmShell; +begin + // TODO maybe have a counter if we get called in this state + // to many time we need + HandleMSIInstallComplete; + Result := kmShellContinue; +end; + +procedure WaitingPostInstallState.HandleInstall; +begin + // Handle Install +end; + +procedure WaitingPostInstallState.HandleMSIInstallComplete; +var SavePath: string; + FileName: String; + FileNames: TStringDynArray; +begin + KL.Log('WaitingPostInstallState.HandleMSIInstallComplete'); + // TODO Remove cached files. Do any loging updating of files etc and then set back to idle + SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); + /// For testing using local user area cache + SavePath := 'C:\Projects\rcswag\testCache'; + KL.Log('WaitingPostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); + + GetFileNamesInDirectory(SavePath, FileNames); + for FileName in FileNames do + begin + System.SysUtils.DeleteFile(FileName); + end; + ChangeState(IdleState); +end; + +procedure WaitingPostInstallState.HandleAbort; +begin + // Handle Abort +end; + +function WaitingPostInstallState.StateName; +begin + // Implement your logic here + Result := 'WaitingPostInstallState'; +end; + + + +end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas new file mode 100644 index 0000000000..ccdcffa2bd --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -0,0 +1,166 @@ +(* + 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 Keyman.System.DownloadUpdate; + +interface +uses + System.Classes, + System.SysUtils, + KeymanPaths, + httpuploader, + Keyman.System.UpdateCheckResponse, + OnlineUpdateCheck; + +const + CheckPeriod: Integer = 7; // Days between checking for updates + +type + TRemoteUpdateCheckDownloadParams = record + TotalSize: Integer; + TotalDownloads: Integer; + StartPosition: Integer; + end; + + TDownloadUpdate = class + private + + FShowErrors: Boolean; + FDownload: TDownloadUpdateDownloadParams; + FCheckOnly: Boolean; + + function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; + procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); + + public + + constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); + destructor Destroy; override; + property ShowErrors: Boolean read FShowErrors write FShowErrors; + end; + +implementation + +procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); +var + i, downloadCount: Integer; + + function DownloadFile(const url, savepath: string): Boolean; + begin + 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; + + // 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; + + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); + + // 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; + + // 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 + 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 TDownloadUpdate.DownloadUpdates(Params: TUpdateCheckResponse): Boolean; +var + DownloadBackGroundSavePath : String; + DownloadResult : Boolean; +begin + DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + + DoDownloadUpdates(DownloadBackGroundSavePath, Params, DownloadResult); + KL.Log('TDownloadUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; + +end; + + +end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index fc40f584cd..fb2dcc3ddf 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -18,7 +18,6 @@ unit Keyman.System.RemoteUpdateCheck; // I3306 interface - uses System.Classes, System.SysUtils, @@ -27,6 +26,9 @@ uses Keyman.System.UpdateCheckResponse, OnlineUpdateCheck; +const + CheckPeriod: Integer = 7; // Days between checking for updates + type ERemoteUpdateCheck = class(Exception); @@ -61,6 +63,7 @@ type end; procedure LogMessage(LogMessage: string); +function CheckForUpdates: Boolean; implementation @@ -122,111 +125,6 @@ begin end; -procedure TRemoteUpdateCheck.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); -var - i, downloadCount: Integer; - - function DownloadFile(const url, savepath: string): Boolean; - begin - 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; - - // 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; - - // Add the Keyman installer - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.InstallSize); - - // 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; - - // 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 - 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(Params: TUpdateCheckResponse): Boolean; -var - DownloadBackGroundSavePath : String; - DownloadResult : Boolean; -begin - DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - - DoDownloadUpdates(DownloadBackGroundSavePath, Params, DownloadResult); - KL.Log('TRemoteUpdateCheck.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - Result := DownloadResult; - -end; function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; var @@ -246,7 +144,7 @@ begin Exit; end; - { Verify that it has been at least 7 days since last update check } + { Verify that it has been at least CheckPeriod days since last update check } try with TRegistryErrorControlled.Create do // I2890 try @@ -257,7 +155,7 @@ begin Result := wucNoUpdates; Exit; end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) and not FForce then + if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < CheckPeriod) and not FForce then begin Result := wucNoUpdates; // TODO: This exit is just to remove the time check for testing. @@ -383,4 +281,41 @@ end; KL.Log(LogMessage); end; +function CheckForUpdates: Boolean; +begin +{ Verify that it has been at least CheckPeriod 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) then + begin + Result := False; + Exit; + end; + if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + begin + Result := True; + end + else + begin + Result := False; + end; + Exit; + end; + finally + Free; + end; + except + { we will not run the check if an error occurs reading the settings } + on E:Exception do + begin + Result := False; + LogMessage(E.Message); + Exit; + end; + end; +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 009/124] 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 fc40f584cd..418f720674 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 @@ var 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 @@ begin // 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 @@ begin 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 @@ begin 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 010/124] feat(windows): Remove the with pattern --- .../main/Keyman.System.RemoteUpdateCheck.pas | 82 ++++++++++--------- windows/src/desktop/kmshell/main/initprog.pas | 7 +- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 418f720674..077e4b42de 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 @@ end; 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; + 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; - Request.SetURL(url); - Upload; - if Response.StatusCode = 200 then + 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 @@ var Result := False; Exit; finally - Free; + http.Free; end; end; @@ -235,6 +237,8 @@ var ucr: TUpdateCheckResponse; pkg: IKeymanPackage; downloadResult: boolean; + registry: TRegistryErrorControlled; + http: THttpUploader; begin {FProxyHost := ''; FProxyPort := 0;} @@ -248,16 +252,16 @@ begin { 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 @@ begin 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 @@ begin 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 @@ begin // 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 @@ begin 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 @@ begin 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 5affe65caf..4076ab2101 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -384,6 +384,7 @@ var kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 + RemoteUpdateCheck: TRemoteUpdateCheck; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -432,15 +433,15 @@ begin 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 011/124] 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 077e4b42de..d9fb1ab6c2 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 @@ begin // 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 @@ begin 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 @@ begin 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 012/124] feat(windows): move exception for each file download --- .../main/Keyman.System.RemoteUpdateCheck.pas | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index d9fb1ab6c2..0eebd52ff9 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 @@ var 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 := 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.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); - - // 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; - - // 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; - 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; + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; end; + + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); + + // 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; + + // 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; From 947b8299cec68201645975aabbc8743dd0752c00 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:36:42 +1000 Subject: [PATCH 013/124] feat(windows): WIP --- windows/src/desktop/kmshell/kmshell.dpr | 4 +++- windows/src/desktop/kmshell/kmshell.dproj | 14 ++++++++------ .../kmshell/main/Keyman.System.DownloadUpdate.pas | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 71e434af91..c3a2312f6b 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -179,7 +179,9 @@ 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', - Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas'; + Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', + BackgroundUpdate in 'main\BackgroundUpdate.pas', + Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 16f49e757d..d09d24cdc1 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -356,6 +356,8 @@ + + Cfg_2 @@ -417,12 +419,6 @@ False - - - kmshell.exe - true - - .\ @@ -435,6 +431,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index ccdcffa2bd..f68e422337 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -31,7 +31,7 @@ const CheckPeriod: Integer = 7; // Days between checking for updates type - TRemoteUpdateCheckDownloadParams = record + TDownloadUpdateParams = record TotalSize: Integer; TotalDownloads: Integer; StartPosition: Integer; @@ -41,7 +41,7 @@ type private FShowErrors: Boolean; - FDownload: TDownloadUpdateDownloadParams; + FDownload: TDownloadUpdateParams; FCheckOnly: Boolean; function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; From bca532972a8772987b6d62663ea9f05cd5bbe397 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:18:56 +1000 Subject: [PATCH 014/124] feat(windows): WIP merge sm --- .../desktop/kmshell/main/BackgroundUpdate.pas | 3 +- .../main/Keyman.System.DownloadUpdate.pas | 169 ++++++++++-------- .../main/Keyman.System.RemoteUpdateCheck.pas | 2 +- 3 files changed, 99 insertions(+), 75 deletions(-) diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index a8d4e72cab..cd545045c4 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -352,7 +352,8 @@ uses UfrmOnlineUpdateNewVersion, utilsystem, utiluac, - versioninfo; + versioninfo, + Keyman.System.DownloadUpdate; const SPackageUpgradeFilename = 'upgrade_packages.inf'; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index f68e422337..b5f570579b 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -56,97 +56,119 @@ type implementation + +uses + GlobalProxySettings, + KLog, + keymanapi_TLB, + KeymanVersion, + Keyman.System.UpdateCheckStorage, + kmint, + ErrorControlledRegistry, + RegistryKeys, + Upload_Settings, + OnlineUpdateCheckMessages; + + // temp wrapper for converting showmessage to logs don't know where + // if nt using klog + procedure LogMessage(LogMessage: string); + begin + KL.Log(LogMessage); + end; + procedure TDownloadUpdate.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 try - Proxy.Server := GetProxySettings.Server; - Proxy.Port := GetProxySettings.Port; - Proxy.Username := GetProxySettings.Username; - Proxy.Password := GetProxySettings.Password; - Request.Agent := API_UserAgent; + 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; - Request.SetURL(url); - Upload; - if Response.StatusCode = 200 then + 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 - 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 + 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 - 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); - - // 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; - - // 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 - 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; + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; end; + + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); + + // 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; + + // 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 TDownloadUpdate.DownloadUpdates(Params: TUpdateCheckResponse): Boolean; @@ -154,10 +176,11 @@ var DownloadBackGroundSavePath : String; DownloadResult : Boolean; begin + // DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); DoDownloadUpdates(DownloadBackGroundSavePath, Params, DownloadResult); - KL.Log('TDownloadUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + KL.Log('TRemoteUpdateCheck.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); Result := DownloadResult; end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index b6680ca40e..63510b946e 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -253,7 +253,7 @@ begin Result := wucNoUpdates; Exit; end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < CheckPeriod) and not FForce then + if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) < CheckPeriod) and not FForce then begin Result := wucNoUpdates; // TODO: #10210 This exit is just to remove the time check for testing. From 6cb83989d567ff68ffa3b022dbeac1e3960d0167 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 20 Dec 2023 08:14:33 +1000 Subject: [PATCH 015/124] feat(windows): WIP added keyman has run atom --- .../general/Keyman.System.ExecuteHistory.pas | 68 ++++++ windows/src/desktop/kmshell/kmshell.dpr | 3 +- windows/src/desktop/kmshell/kmshell.dproj | 13 +- .../desktop/kmshell/main/BackgroundUpdate.pas | 160 ++++--------- .../main/Keyman.System.DownloadUpdate.pas | 57 ++++- .../main/Keyman.System.RemoteUpdateCheck.pas | 217 +++--------------- windows/src/desktop/kmshell/main/initprog.pas | 2 +- .../src/desktop/kmshell/util/utilkmshell.pas | 16 +- windows/src/engine/keyman/keyman.dpr | 5 +- windows/src/engine/keyman/keyman.dproj | 13 +- windows/src/engine/keyman/main.pas | 6 +- 11 files changed, 231 insertions(+), 329 deletions(-) create mode 100644 common/windows/delphi/general/Keyman.System.ExecuteHistory.pas diff --git a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas new file mode 100644 index 0000000000..558defbdec --- /dev/null +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -0,0 +1,68 @@ +unit Keyman.System.ExecuteHistory; + +interface + +const + AtomName = 'KeymanSessionFlag'; + +function RecordKeymanStarted : Boolean; +function HasKeymanRun : Boolean; + +implementation +uses + System.SysUtils,KLog, + Winapi.Windows; + +function RecordKeymanStarted : Boolean; +var + atom: WORD; +begin + Result := False; + try + atom := GlobalFindAtom(AtomName); + if atom = 0 then + begin + if GetLastError <> ERROR_SUCCESS then + RaiseLastOSError; + //writeln('The Sample Keyman Session Flag has not been set, so the process have never been started in this session.'); + atom := GlobalAddAtom(AtomName); + Result := True; + if atom = 0 then + RaiseLastOSError; + end; + //else + //writeln('The process has been started previously because the Sample Keyman Session Flag has been set.'); + + //writeln; + //writeln('* The Sample Keyman Session Flag atom is: '+IntToStr(atom)); + //writeln; + except + on E: Exception do + KL.Log(E.ClassName + ': ' + E.Message); + end; +end; + +function HasKeymanRun : Boolean; +var + atom: WORD; +begin + Result := False; + try + atom := GlobalFindAtom(AtomName); + if atom <> 0 then + begin + if GetLastError <> ERROR_SUCCESS then + RaiseLastOSError; + + Result := True; + end + else + Result := False; + except + on E: Exception do + KL.log(E.ClassName + ': ' + E.Message); + end; + +end; + +end. diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index c3a2312f6b..5eb0ee9013 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -181,7 +181,8 @@ uses Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', BackgroundUpdate in 'main\BackgroundUpdate.pas', - Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas'; + Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', + Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index d09d24cdc1..159a3fe88d 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -358,6 +358,7 @@ + Cfg_2 @@ -419,12 +420,6 @@ False - - - .\ - true - - kmshell.rsm @@ -437,6 +432,12 @@ true + + + .\ + true + + 1 diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index cd545045c4..4809d9587c 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -32,6 +32,7 @@ uses httpuploader, Keyman.System.UpdateCheckResponse, + Keyman.System.ExecuteHistory, UfrmDownloadProgress; type @@ -132,47 +133,8 @@ type DownloadingState = class(TState) private - { These function could become members of the state objects - or at the very least controlled by the state objects } - - { TODO: make a comment clear when we are in a elevate process} - - { - Performs updates download in the background, without displaying a GUI - progress bar. This function is similar to DownloadUpdates, but it runs in - the background. - - @returns True if all updates were successfully downloaded, False if any - download failed. - } function DownloadUpdatesBackground: Boolean; - { - Performs updates download in the background, without displaying a GUI - progress bar. This procedure is similar to DownloadUpdates, but it runs in - the background. - - @params SavePath The path where the downloaded files will be saved. - Result A Boolean value indicating the overall result of the - download process. - } - procedure DoDownloadUpdatesBackground(SavePath: string; var Result: Boolean); - { - Performs an online update check, including package retrieval and version - query. - - This function checks if a week has passed since the last update check. It - utilizes the kmcom API to retrieve the current packages. The function then - performs an HTTP request to query the remote versions of these packages. - The resulting information is stored in the FParams variable. Additionally, - the function handles the main Keyman install package. - - @returns A TBackgroundUpdateResult indicating the result of the update - check. - } - // This is just for testing only. - procedure DoDownloadUpdatesBackgroundTest(SavePath: string; var Result: Boolean); - public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; @@ -289,7 +251,7 @@ type tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - function IsKeymanRunning: Boolean; + //function IsKeymanRunning: Boolean; function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; @@ -353,6 +315,7 @@ uses utilsystem, utiluac, versioninfo, + Keyman.System.RemoteUpdateCheck, Keyman.System.DownloadUpdate; const @@ -501,18 +464,18 @@ end; -function TBackgroundUpdate.IsKeymanRunning: Boolean; // I2329 -begin - try - Result := kmcom.Control.IsKeymanRunning; - except - on E:Exception do - begin - KL.Log(E.Message); - Exit(False); - end; - end; -end; +//function TBackgroundUpdate.IsKeymanRunning: Boolean; // I2329 +//begin +// try +// Result := kmcom.Control.IsKeymanRunning; +// except +// on E:Exception do +// begin +// KL.Log(E.Message); +// Exit(False); +// end; +// end; +//end; function TBackgroundUpdate.CheckUpdateSchedule: Boolean; begin @@ -682,18 +645,28 @@ begin end; procedure IdleState.HandleCheck; +var + CheckForUpdates: TRemoteUpdateCheck; + Result : TRemoteUpdateCheckResult; begin - { TODO: Verify that it has been at least 7 days since last update check - - only if FSilent = TRUE } { Make a HTTP request out and see if updates are available for now do this all in the Idle HandleCheck message. But could be broken into an seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } + CheckForUpdates := TRemoteUpdateCheck.Create(False); + try + Result:= CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; { Response OK and Update is available } - ChangeState(UpdateAvailableState); - + if Result = wucSuccess then + begin + ChangeState(UpdateAvailableState); + end; + // else staty in idle state end; procedure IdleState.HandleDownload; @@ -795,7 +768,7 @@ begin DownloadResult := DownloadUpdatesBackground; if DownloadResult then begin - if bucStateContext.IsKeymanRunning then + if HasKeymanRun then ChangeState(WaitingRestartState) else ChangeState(InstallingState); @@ -860,70 +833,31 @@ begin Result := 'DownloadingState'; end; -procedure DownloadingState.DoDownloadUpdatesBackground(SavePath: string; var Result: Boolean); -begin -end; - -// Test installing only -procedure DownloadingState.DoDownloadUpdatesBackgroundTest(SavePath: string; var Result: Boolean); -var - i, downloadCount: Integer; - UpdateDir : string; - -begin - try - Result := False; - - UpdateDir := 'C:\Projects\rcswag\testCache'; - KL.Log('DoDownloadUpdatesBackgroundTest SavePath:'+ SavePath); - // Check if the update source directory exists - if DirectoryExists(UpdateDir) then - begin - // Create the update cached directory if it doesn't exist - if not DirectoryExists(SavePath) then - ForceDirectories(SavePath); - - // Copy all files from the updatedir to savepath - TDirectory.Copy(UpdateDir, SavePath); - Result:= True; - KL.Log('All files copied successfully.'); - end - else - KL.Log('Source directory does not exist.'); - except - on E: Exception do - KL.Log('Error: ' + E.Message); - end; - -end; - function DownloadingState.DownloadUpdatesBackground: Boolean; var i: Integer; DownloadBackGroundSavePath : String; DownloadResult : Boolean; + DownloadUpdate: TDownloadUpdate; begin - //DownloadTempPath := IncludeTrailingPathDelimiter(CreateTempPath); - DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); - //DownloadBackGroundSavePath := DownloadBackGroundSavePath + 'RCTest.txt'; - { For now lets download all the updates. We need to take these from the user via check box form } + DownloadUpdate := TDownloadUpdate.Create; + try + DownloadResult := DownloadUpdate.DownloadUpdates; + KL.Log('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; +// TODO: workout when we need to refresh kmcom keyboards - if bucStateContext.FParams.Keyman.DownloadURL <> '' then - bucStateContext.FParams.Keyman.Install := True; +// if Result in [ wucSuccess] then +// begin +// kmcom.Keyboards.Refresh; +// kmcom.Keyboards.Apply; +// kmcom.Packages.Refresh; +// end; - for i := 0 to High(bucStateContext.FParams.Packages) do - bucStateContext.FParams.Packages[i].Install := True; - - // Download files - // DoDownloadUpdatesBackground(DownloadBackGroundSavePath, DownloadResult); - // For development by passing the DoDownloadUpdatesBackground and just add - // the cached file on my local disk as a simulation - DoDownloadUpdatesBackgroundTest(DownloadBackGroundSavePath, DownloadResult); - - KL.Log('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - //ShowMessage('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - Result := DownloadResult; + finally + DownloadUpdate.Free; + end; end; { WaitingRestartState } @@ -1216,7 +1150,7 @@ var SavePath: string; begin KL.Log('WaitingPostInstallState.HandleMSIInstallComplete'); // TODO Remove cached files. Do any loging updating of files etc and then set back to idle - SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); + //SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); /// For testing using local user area cache SavePath := 'C:\Projects\rcswag\testCache'; KL.Log('WaitingPostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index b5f570579b..d216f2f463 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -39,19 +39,34 @@ type TDownloadUpdate = class private - FShowErrors: Boolean; FDownload: TDownloadUpdateParams; - FCheckOnly: Boolean; - - function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; + FErrorMessage: string; + { + Performs updates download in the background, without displaying a GUI + progress bar. + @params SavePath The path where the downloaded files will be saved. + Result A Boolean value indicating the overall result of the + download process. + } procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); public - constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); + constructor Create; destructor Destroy; override; + { + Performs updates download in the background, without displaying a GUI + progress bar. This function is similar to DownloadUpdates, but it runs in + the background. + + @returns True if all updates were successfully downloaded, False if any + download failed. + } + + function DownloadUpdates : Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; + end; implementation @@ -76,6 +91,23 @@ uses KL.Log(LogMessage); end; +constructor TDownloadUpdate.Create; +begin + inherited Create; + + FShowErrors := True; + KL.Log('TDownloadUpdate.Create'); +end; + +destructor TDownloadUpdate.Destroy; +begin + if (FErrorMessage <> '') and FShowErrors then + LogMessage(FErrorMessage); + + KL.Log('TDownloadUpdate.Destroy: FErrorMessage = '+FErrorMessage); + inherited Destroy; +end; + procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); var i, downloadCount: Integer; @@ -171,19 +203,22 @@ begin Result := True; end; -function TDownloadUpdate.DownloadUpdates(Params: TUpdateCheckResponse): Boolean; +function TDownloadUpdate.DownloadUpdates: Boolean; var DownloadBackGroundSavePath : String; DownloadResult : Boolean; + ucr: TUpdateCheckResponse; begin // DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - - DoDownloadUpdates(DownloadBackGroundSavePath, Params, DownloadResult); - KL.Log('TRemoteUpdateCheck.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - Result := DownloadResult; + if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then + begin + DoDownloadUpdates(DownloadBackGroundSavePath, ucr, DownloadResult); + KL.Log('DownloadUpdates.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; + end; + Result := False; end; - end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 63510b946e..b1738bad93 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -33,26 +33,32 @@ type private FForce: Boolean; FRemoteResult: TRemoteUpdateCheckResult; - FErrorMessage: string; - FShowErrors: Boolean; - FDownload: TRemoteUpdateCheckDownloadParams; - FCheckOnly: Boolean; + { + Performs an online update check, including package retrieval and version + query. - function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; - procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); + This function checks if a week has passed since the last update check. It + utilizes the kmcom API to retrieve the current packages. The function then + performs an HTTP request to query the remote versions of these packages. + The resulting information is stored in the FParams variable. Additionally, + the function handles the main Keyman install package. + + @returns A TBackgroundUpdateResult indicating the result of the update + check. + } function DoRun: TRemoteUpdateCheckResult; public - constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); + constructor Create(AForce : Boolean); destructor Destroy; override; function Run: TRemoteUpdateCheckResult; property ShowErrors: Boolean read FShowErrors write FShowErrors; end; procedure LogMessage(LogMessage: string); -function CheckForUpdates: Boolean; +function ConfigCheckContinue: Boolean; implementation @@ -75,7 +81,7 @@ uses { TRemoteUpdateCheck } -constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); +constructor TRemoteUpdateCheck.Create(AForce: Boolean); begin inherited Create; @@ -83,7 +89,6 @@ begin FRemoteResult := wucUnknown; FForce := AForce; - FCheckOnly := ACheckOnly; KL.Log('TRemoteUpdateCheck.Create'); end; @@ -102,135 +107,18 @@ end; function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; begin Result := DoRun; - - if Result in [ wucSuccess] then - begin - kmcom.Keyboards.Refresh; - kmcom.Keyboards.Apply; - kmcom.Packages.Refresh; - end; - FRemoteResult := Result; end; - -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 - try - 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 - 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; - -begin - Result := False; - - 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; - - // Add the Keyman installer - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.InstallSize); - - // 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; - - // 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; -var - DownloadBackGroundSavePath : String; - DownloadResult : Boolean; -begin - DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - - DoDownloadUpdates(DownloadBackGroundSavePath, Params, 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; registry: TRegistryErrorControlled; http: THttpUploader; + proceed : boolean; begin {FProxyHost := ''; FProxyPort := 0;} @@ -242,44 +130,13 @@ begin Exit; end; - { Verify that it has been at least CheckPeriod days since last update check } - try - registry := TRegistryErrorControlled.Create; // I2890 - try - if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then - begin - if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) and not FForce then - begin - Result := wucNoUpdates; - Exit; - end; - if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) < CheckPeriod) and not FForce then - begin - Result := wucNoUpdates; - // TODO: #10210 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 - registry.Free; - end; - except - { we will not run the check if an error occurs reading the settings } - on E:Exception do + proceed := ConfigCheckContinue; + if not proceed and not FForce then begin - Result := wucFailure; - FErrorMessage := E.Message; + Result := wucNoUpdates; Exit; end; - end; - Result := wucNoUpdates; try http := THTTPUploader.Create(nil); @@ -316,25 +173,8 @@ begin begin if ucr.Parse(http.Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin - //ResponseToParams(ucr); - - if FCheckOnly then - begin - TUpdateCheckStorage.SaveUpdateCacheData(ucr); - Result := FRemoteResult; - end - // TODO: ##10210 - // Integerate into state machine. in the download state - // the process can call LoadUpdateCacheData if needed to get the - // response result. - else if (Length(ucr.Packages) > 0) or (ucr.InstallURL <> '') then - begin - downloadResult := DownloadUpdates(ucr); - if DownloadResult then - begin - Result := wucSuccess; - end; - end; + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := wucSuccess; end else begin @@ -378,20 +218,23 @@ end; KL.Log(LogMessage); end; -function CheckForUpdates: Boolean; +function ConfigCheckContinue: Boolean; +var + registry: TRegistryErrorControlled; begin { Verify that it has been at least CheckPeriod days since last update check } + Result := False; 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) then + if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then begin Result := False; Exit; end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then begin Result := True; end @@ -402,7 +245,7 @@ begin Exit; end; finally - Free; + registry.Free; end; except { we will not run the check if an error occurs reading the settings } diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 4076ab2101..7a2e05200d 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -433,7 +433,7 @@ begin end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - RemoteUpdateCheck := TRemoteUpdateCheck.Create(False, False); + RemoteUpdateCheck := TRemoteUpdateCheck.Create(False); try if (FMode = fmBackgroundUpdateCheck) then begin diff --git a/windows/src/desktop/kmshell/util/utilkmshell.pas b/windows/src/desktop/kmshell/util/utilkmshell.pas index 7485753283..76eb4929dd 100644 --- a/windows/src/desktop/kmshell/util/utilkmshell.pas +++ b/windows/src/desktop/kmshell/util/utilkmshell.pas @@ -33,7 +33,7 @@ unit utilkmshell; // I3306 // I4181 interface uses - System.UITypes, + System.UITypes, System.IOUtils, System.Types, Dialogs, Windows, ComObj, shlobj, controls, sysutils, classes; const @@ -96,6 +96,7 @@ procedure SplitString(const instr: string; var outstr1, outstr2: string; const s function ValidDirectory(const dir: string): string; function GetLongFile(APath:String):String; +procedure GetFileNamesInDirectory(const directoryPath: string; var fileNames: TStringDynArray); function TSFInstalled: Boolean; @@ -540,6 +541,19 @@ begin until Length(APath)=0; end; {Peter Haas} +procedure GetFileNamesInDirectory(const directoryPath: string; var fileNames: TStringDynArray); + +begin + // Check if the directory exists + if TDirectory.Exists(directoryPath) then + begin + // Retrieve file names within the directory + fileNames := TDirectory.GetFiles(directoryPath); + end + else + KL.Log('Directory does not exist.'); +end; + { TString } constructor TString.Create(const AString: string); diff --git a/windows/src/engine/keyman/keyman.dpr b/windows/src/engine/keyman/keyman.dpr index 9f8a3fba8e..d995ca24c9 100644 --- a/windows/src/engine/keyman/keyman.dpr +++ b/windows/src/engine/keyman/keyman.dpr @@ -32,7 +32,7 @@ uses UfrmOSKPlugInBase in 'viskbd\UfrmOSKPlugInBase.pas' {frmOSKPlugInBase}, UfrmOSKCharacterMap in 'viskbd\UfrmOSKCharacterMap.pas' {frmOSKCharacterMap}, UfrmOSKEntryHelper in 'viskbd\UfrmOSKEntryHelper.pas' {frmOSKEntryHelper}, - TTInfo in '..\..\..\..\common\windows\delphi\general\TTInfo.pas', + ttinfo in '..\..\..\..\common\windows\delphi\general\ttinfo.pas', UnicodeData in '..\..\..\..\common\windows\delphi\charmap\UnicodeData.pas', CharacterMapSettings in '..\..\..\..\common\windows\delphi\charmap\CharacterMapSettings.pas', CharacterRanges in '..\..\..\..\common\windows\delphi\charmap\CharacterRanges.pas', @@ -112,7 +112,8 @@ uses Sentry.Client.Vcl in '..\..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.Vcl.pas', sentry in '..\..\..\..\common\windows\delphi\ext\sentry\sentry.pas', Keyman.System.KeymanSentryClient in '..\..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas', - Keyman.System.LocaleStrings in '..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas'; + Keyman.System.LocaleStrings in '..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas', + Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas'; {$R ICONS.RES} {$R VERSION.RES} diff --git a/windows/src/engine/keyman/keyman.dproj b/windows/src/engine/keyman/keyman.dproj index 44959854d5..71cdb756d3 100644 --- a/windows/src/engine/keyman/keyman.dproj +++ b/windows/src/engine/keyman/keyman.dproj @@ -141,7 +141,7 @@
    frmOSKEntryHelper
    - + @@ -238,6 +238,7 @@ + Cfg_2 @@ -299,21 +300,21 @@ False - + - keyman.exe + .\ true - + keyman.rsm true - + - .\ + keyman.exe true diff --git a/windows/src/engine/keyman/main.pas b/windows/src/engine/keyman/main.pas index 2c019150d7..223c138558 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -45,7 +45,8 @@ uses KeymanVersion, RegistryKeys, UfrmKeyman7Main, - UserMessages; + UserMessages, + Keyman.System.ExecuteHistory; function ValidateParameters(var FCommand: Integer): Boolean; forward; function PassParametersToRunningInstance(FCommand: Integer): Boolean; forward; @@ -79,6 +80,9 @@ begin if not ValidateParameters(FCommand) then Exit; + // TODO set atom application running + RecordKeymanStarted; + hProgramMutex := CreateMutex(nil, False, 'KeymanEXE70'); if hProgramMutex = 0 then begin From dd5767a372a52102b198dfe755b3c0b8f378ecdf Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 22 Dec 2023 10:11:32 +1000 Subject: [PATCH 016/124] feat(windows): integrate sm states and handlekmshell --- .../general/Keyman.System.ExecuteHistory.pas | 5 + .../desktop/kmshell/main/BackgroundUpdate.pas | 102 +++++++++++------- .../main/Keyman.System.DownloadUpdate.pas | 59 +++++++++- windows/src/desktop/kmshell/main/initprog.pas | 14 ++- ...eyman.System.Install.EnginePostInstall.pas | 36 +++++++ windows/src/engine/keyman/main.pas | 4 +- 6 files changed, 177 insertions(+), 43 deletions(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas index 558defbdec..97e9b2bfb1 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -18,14 +18,18 @@ var atom: WORD; begin Result := False; + KL.Log('RecordKeymanStarted: Enter'); try atom := GlobalFindAtom(AtomName); + KL.Log('Keyman Session Flag atom is: '+IntToStr(atom)); if atom = 0 then begin + KL.Log('RecordKeymanStarted: if atom = 0'); if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; //writeln('The Sample Keyman Session Flag has not been set, so the process have never been started in this session.'); atom := GlobalAddAtom(AtomName); + KL.Log('RecordKeymanStarted: True'); Result := True; if atom = 0 then RaiseLastOSError; @@ -54,6 +58,7 @@ begin if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; + KL.Log('HasKeymanRun: Keyman Has Run'); Result := True; end else diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index 4809d9587c..fe2eb22a6c 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -213,8 +213,6 @@ type { This class also controls the state flow see } TBackgroundUpdate = class private - FOwner: TCustomForm; - FSilent: Boolean; FForce: Boolean; FAuto: Boolean; FParams: TBackgroundUpdateParams; @@ -240,6 +238,7 @@ type FWaitingPostInstall: WaitingPostInstallState; function GetState: TStateClass; procedure SetState(const Value: TStateClass); + procedure SetStateOnly(const Value: TStateClass); function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; procedure ShutDown; @@ -260,7 +259,7 @@ type property State: TStateClass read GetState write SetState; public - constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean); + constructor Create(AForce: Boolean); destructor Destroy; override; procedure HandleCheck; @@ -325,17 +324,15 @@ const { TBackgroundUpdate } -constructor TBackgroundUpdate.Create(AOwner: TCustomForm; AForce, ASilent: Boolean); +constructor TBackgroundUpdate.Create(AForce : Boolean); var TSerailsedState : TUpdateState; begin inherited Create; - FOwner := AOwner; FShowErrors := True; FParams.Result := oucUnknown; - FSilent := ASilent; FForce := AForce; FAuto := True; // Default to automatically check, download, and install FIdle := IdleState.Create(Self); @@ -346,14 +343,14 @@ begin FRetry := RetryState.Create(Self); FWaitingPostInstall := WaitingPostInstallState.Create(Self); // Check the Registry setting. - state := ConvertEnumState(CheckRegistryState); + SetStateOnly(ConvertEnumState(CheckRegistryState)); KL.Log('TBackgroundUpdate.Create'); end; destructor TBackgroundUpdate.Destroy; begin - if (FErrorMessage <> '') and not FSilent and FShowErrors then - ShowMessage(FErrorMessage); + if (FErrorMessage <> '') and FShowErrors then + KL.Log(FErrorMessage); if FParams.Result = oucShutDown then ShutDown; @@ -524,6 +521,21 @@ begin CurrentState.Exit; end; + SetStateOnly(Value); + + if Assigned(CurrentState) then + begin + CurrentState.Enter; + end + else + begin + // TODO: Unable to set state for Value [] + end; + +end; + +procedure TBackgroundUpdate.SetStateOnly(const Value: TStateClass); +begin if Value = IdleState then begin CurrentState := FIdle; @@ -552,16 +564,6 @@ begin begin CurrentState := FWaitingPostInstall; end; - - if Assigned(CurrentState) then - begin - CurrentState.Enter; - end - else - begin - // TODO: Unable to set state for Value [] - end; - end; function TBackgroundUpdate.ConvertEnumState(const TEnumState: TUpdateState) : TStateClass; @@ -654,7 +656,9 @@ begin this all in the Idle HandleCheck message. But could be broken into an seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - CheckForUpdates := TRemoteUpdateCheck.Create(False); + //CheckForUpdates := TRemoteUpdateCheck.Create(False); + // should be false but forcing check for testing + CheckForUpdates := TRemoteUpdateCheck.Create(True); try Result:= CheckForUpdates.Run; finally @@ -884,19 +888,40 @@ begin end; function WaitingRestartState.HandleKmShell; +var + SavedPath : String; + Filenames : TStringDynArray; begin - // Check downloaded cache if available then - // change state intalling - // else then change to idle and handle checkupdates state - // if bucStateContext.IsKeymanRunning then - // stay as waiting - // log this is unexpected - //ChangeState(WaitingRestartState) - // else - // - - ChangeState(UpdateAvailableState); - Result := kmShellExit; + KL.Log('WaitingRestartState.HandleKmShell Enter'); + // Still can't go if keyman has run + if HasKeymanRun then + begin + KL.Log('WaitingRestartState.HandleKmShell Keyman Has Run'); + Result := kmShellExit; + // Exit; // Exit is not wokring for some reason. + // this else is only here because the exit is not working. + end + else + begin + // Check downloaded cache if available then + SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + GetFileNamesInDirectory(SavedPath, FileNames); + if Length(FileNames) = 0 then + begin + KL.Log('WaitingRestartState.HandleKmShell No Files in Download Cache'); + // Return to Idle state and check for Updates state + ChangeState(IdleState); + bucStateContext.CurrentState.HandleCheck; + Result := kmShellExit; + // Exit; // again exit was not working + end + else + begin + KL.Log('WaitingRestartState.HandleKmShell is good to install'); + ChangeState(InstallingState); + Result := kmShellExit; + end; + end; end; procedure WaitingRestartState.HandleInstall; @@ -962,10 +987,12 @@ begin if s = '.msi' then FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 else if s = '.exe' then + begin + KL.Log('TBackgroundUpdate.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 + end else - Exit; - //Exit(False); + FResult := False; if not FResult then begin @@ -1033,7 +1060,9 @@ end; function InstallingState.HandleKmShell; begin // Result = exit straight away as we are installing (MSI installer) - Result := kmShellExit; + // need to just do a no-op keyman will it maybe using kmshell to install + // packages. + Result := kmShellContinue; end; procedure InstallingState.HandleInstall; @@ -1152,7 +1181,8 @@ begin // TODO Remove cached files. Do any loging updating of files etc and then set back to idle //SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); /// For testing using local user area cache - SavePath := 'C:\Projects\rcswag\testCache'; + //SavePath := 'C:\Projects\rcswag\testCache'; + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); KL.Log('WaitingPostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); GetFileNamesInDirectory(SavePath, FileNames); diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index d216f2f463..bee733e4b7 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -65,6 +65,7 @@ type } function DownloadUpdates : Boolean; + function CheckAllFilesDownloaded : Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; end; @@ -82,7 +83,10 @@ uses ErrorControlledRegistry, RegistryKeys, Upload_Settings, - OnlineUpdateCheckMessages; + OnlineUpdateCheckMessages, + utilkmshell, + System.Types, + System.StrUtils; // temp wrapper for converting showmessage to logs don't know where // if nt using klog @@ -216,8 +220,59 @@ begin DoDownloadUpdates(DownloadBackGroundSavePath, ucr, DownloadResult); KL.Log('DownloadUpdates.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); Result := DownloadResult; + end + else + Result := False; +end; + +function TDownloadUpdate.CheckAllFilesDownloaded: Boolean; +var + i : Integer; + SavedPath : String; + DownloadResult : Boolean; + Params: TUpdateCheckResponse; + VerifyDownloads : TDownloadUpdateParams; + FileNames : TStringDynArray; + +begin + // DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); + SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + GetFileNamesInDirectory(SavedPath, FileNames); + if Length(FileNames) = 0 then + begin + Result := False; + Exit; + end; + + if TUpdateCheckStorage.LoadUpdateCacheData(Params) then + begin + for i := 0 to High(Params.Packages) do + begin + Inc(VerifyDownloads.TotalDownloads); + Inc(VerifyDownloads.TotalSize, Params.Packages[i].DownloadSize); + if Not MatchStr(Params.Packages[i].FileName, FileNames) then + begin + Result := False; + Exit; + end; + Params.Packages[i].SavePath := SavedPath + Params.Packages[i].FileName; + end; + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); + // Check if the Keyman installer downloaded + if Not MatchStr(Params.FileName, FileNames) then + begin + Result := False; + Exit; + end; + // TODO verify filesizes match so we know we don't have partical downloades. + Result := True; + end + else + begin + Result := False; end; - Result := False; end; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 7a2e05200d..94e47a3268 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -143,6 +143,7 @@ uses UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, + BackgroundUpdate, KeyboardTIPCheck, @@ -384,7 +385,7 @@ var kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 - RemoteUpdateCheck: TRemoteUpdateCheck; + BUpdateSM : TBackgroundUpdate; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -433,15 +434,20 @@ begin end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - RemoteUpdateCheck := TRemoteUpdateCheck.Create(False); + BUpdateSM := TBackgroundUpdate.Create(False); try if (FMode = fmBackgroundUpdateCheck) then begin - RemoteUpdateCheck.Run; + BUpdateSM.HandleCheck; Exit; end + else + begin + if BUpdateSM.HandleKmShell = 1 then + Exit; + end; finally - RemoteUpdateCheck.Free; + BUpdateSM.Free; end; diff --git a/windows/src/engine/inst/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/inst/insthelper/Keyman.System.Install.EnginePostInstall.pas index bdf602ae05..5e61ff1f4d 100644 --- a/windows/src/engine/inst/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/inst/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -25,6 +25,40 @@ begin Result := code; end; + +function UpdateState: Boolean; +var + UpdateStr : UnicodeString; + UpdatePBytes : PByte; + hk: Winapi.Windows.HKEY; + updateData: Cardinal; +begin + + Result := False; + UpdateStr := 'usWaitingPostInstall'; + //KL.Log('SetBackgroundState State Entry'); + if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_LM), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then + begin + try + if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PWideChar(UpdateStr), Length(UpdateStr) * SizeOf(Char)) = ERROR_SUCCESS then + begin + Result := True; + end + else + begin + // error log + end; + finally + RegCloseKey(hk); + end; + end + else + begin + // couldn't open registry key + end; +end; + + { Add permission for ALL APPLICATION PACKAGES to read %ProgramData%\Keyman folder } @@ -61,6 +95,8 @@ begin end; Result := ERROR_SUCCESS; + // TODO better error checking on the registry key update + UpdateState; finally if not CloseHandle(hFile) then diff --git a/windows/src/engine/keyman/main.pas b/windows/src/engine/keyman/main.pas index 223c138558..9f1dbc9b3f 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -46,6 +46,7 @@ uses RegistryKeys, UfrmKeyman7Main, UserMessages, + Klog, Keyman.System.ExecuteHistory; function ValidateParameters(var FCommand: Integer): Boolean; forward; @@ -77,10 +78,11 @@ var hMutex: Cardinal; begin - + KL.Log('Keyman RunProgram'); if not ValidateParameters(FCommand) then Exit; // TODO set atom application running + KL.Log('Calling RecordKeymanStarted'); RecordKeymanStarted; hProgramMutex := CreateMutex(nil, False, 'KeymanEXE70'); From 486586dc8d5339d25c120317344c1dd07211a2b1 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 22 Dec 2023 17:27:40 +1000 Subject: [PATCH 017/124] feat(windows): apply now install for state machine The event handling needs to be added for all the states --- windows/src/desktop/kmshell/main/UfrmMain.pas | 28 +++++++++++++++---- windows/src/desktop/kmshell/main/initprog.pas | 7 +++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index a779fdcdd2..5df7c2aa6a 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -149,6 +149,7 @@ type procedure DoApply; procedure DoRefresh; procedure Update_CheckNow; + procedure Update_ApplyNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -185,6 +186,7 @@ uses LanguagesXMLRenderer, MessageIdentifierConsts, MessageIdentifiers, + Keyman.System.RemoteUpdateCheck, OnlineUpdateCheck, OptionsXMLRenderer, Keyman.Configuration.System.UmodWebHttpServer, @@ -209,7 +211,8 @@ uses utilkmshell, utilhttp, utiluac, - utilxml; + utilxml, + KeymanPaths; type PHKL = ^HKL; @@ -349,6 +352,7 @@ begin else if command = 'support_proxyconfig' then Support_ProxyConfig else if command = 'update_checknow' then Update_CheckNow + else if command = 'update_applynow' then Update_ApplyNow else if command = 'contact_support' then Support_ContactSupport(params) // I4390 @@ -793,7 +797,7 @@ begin Free; end; end; - +// TODO: #10210 Remove Update procedure TfrmMain.Support_UpdateCheck; begin with TOnlineUpdateCheck.Create(Self, True, False) do @@ -821,16 +825,30 @@ begin end; procedure TfrmMain.Update_CheckNow; +var UpdateCheck : TRemoteUpdateCheck; begin - with TOnlineUpdateCheck.Create(Self, True, True, True) do + UpdateCheck := TRemoteUpdateCheck.Create(True); try - Run; + UpdateCheck.Run; finally - Free; + UpdateCheck.Free; end; DoRefresh; end; +procedure TfrmMain.Update_ApplyNow; +var + ShellPath, s: WideString; + FResult: Boolean; +begin + ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); + FResult := TUtilExecute.Shell(0, ShellPath, '', '-an'); + if not FResult then + KL.Log('TrmfMain: Executing Update_ApplyNow Failed'); +end; + + + procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 94e47a3268..d4d43874cc 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -83,6 +83,7 @@ type fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, fmBackgroundUpdateCheck, + fmApplyInstallNow, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 @@ -253,6 +254,7 @@ begin else if s = '-t' then FMode := fmTextEditor else if s = '-ouc' then FMode := fmOnlineUpdateCheck else if s = '-buc' then FMode := fmBackgroundUpdateCheck + else if s = '-an' then FMode := fmApplyInstallNow else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169 else if s = '-nowelcome' then FNoWelcome := True else if s = '-kw' then FMode := fmKeyboardWelcome // I2569 @@ -441,6 +443,11 @@ begin BUpdateSM.HandleCheck; Exit; end + else if (FMode = fmApplyInstallNow) then + begin + BUpdateSM.HandleInstall; + Exit; + end else begin if BUpdateSM.HandleKmShell = 1 then From 2eab485c76def8894c9d82c56ee5996a07d94271 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:23:54 +1000 Subject: [PATCH 018/124] feat(windows): Add a installNow event to SM Added an event for install now when the user wants to manual start the install process straight away. --- .../windows/delphi/general/RegistryKeys.pas | 1 + .../desktop/kmshell/main/BackgroundUpdate.pas | 116 +++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 7404a2af4f..6de8909423 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -180,6 +180,7 @@ const SRegValue_Install_Update = 'install update'; SRegValue_Update_State = 'update state'; + SRegValue_Install_Mode = 'install mode'; { Privacy } diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index fe2eb22a6c..a816987822 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -98,6 +98,7 @@ type procedure HandleInstall; virtual; abstract; procedure HandleMSIInstallComplete; virtual; abstract; procedure HandleAbort; virtual; abstract; + procedure HandleInstallNow; virtual; abstract; // For convenience function StateName: string; virtual; abstract; @@ -111,10 +112,11 @@ type procedure Exit; override; procedure HandleCheck; override; procedure HandleDownload; override; - function HandleKmShell : Integer; override; + function HandleKmShell : Integer; override; procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -124,10 +126,11 @@ type procedure Exit; override; procedure HandleCheck; override; procedure HandleDownload; override; - function HandleKmShell : Integer; override; + function HandleKmShell : Integer; override; procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -143,6 +146,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -156,6 +160,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -181,6 +186,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -194,6 +200,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -207,6 +214,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -254,6 +262,7 @@ type function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; + function SetRegistryInstallMode (InstallMode : Boolean): Boolean; protected property State: TStateClass read GetState write SetState; @@ -268,10 +277,12 @@ type procedure HandleInstall; procedure HandleMSIInstallComplete; procedure HandleAbort; + procedure HandleInstallNow; function CurrentStateName: string; property ShowErrors: Boolean read FShowErrors write FShowErrors; function CheckRegistryState : TUpdateState; + function CheckRegistryInstallMode : Boolean; end; @@ -459,6 +470,57 @@ begin Result := UpdateState; end; +function TBackgroundUpdate.SetRegistryInstallMode (InstallMode : Boolean): Boolean; +var + InstallModeStr : string; +begin + + Result := False; + with TRegistryErrorControlled.Create do + try + RootKey := HKEY_LOCAL_MACHINE; + KL.Log('SetRegistryState State Entry'); + if OpenKey(SRegKey_KeymanEngine_LM, True) then + begin + InstallModeStr := BoolToStr(InstallMode, True); + WriteString(SRegValue_Install_Mode, InstallModeStr); + KL.Log('SetRegistryInstallMode is:[' + InstallModeStr + ']'); + end; + Result := True; + finally + Free; + end; + +end; + +function TBackgroundUpdate.CheckRegistryInstallMode : Boolean; +var + InstallMode : Boolean; + +begin + // We will use a registry flag to maintain the install mode background/foreground + + InstallMode := False; + // check the registry value + with TRegistryErrorControlled.Create do // I2890 + try + RootKey := HKEY_LOCAL_MACHINE; + if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Install_Mode) then + begin + InstallMode := StrToBool(ReadString(SRegValue_Install_Mode)); + KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); + end + else + begin + InstallMode := False; // default to background + KL.Log('CheckRegistryInstallMode reg value not found default:[ False ]'); + end + finally + Free; + end; + Result := InstallMode; +end; + //function TBackgroundUpdate.IsKeymanRunning: Boolean; // I2329 @@ -612,6 +674,11 @@ begin CurrentState.HandleAbort; end; +procedure TBackgroundUpdate.HandleInstallNow; +begin + CurrentState.HandleInstallNow; +end; + function TBackgroundUpdate.CurrentStateName: string; begin // Implement your logic here @@ -699,6 +766,12 @@ begin // Implement your logic here end; +procedure IdleState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + bucStateContext.CurrentState.HandleCheck; +end; + function IdleState.StateName; begin // Implement your logic here @@ -713,7 +786,7 @@ begin bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAuto then begin - bucStateContext.CurrentState.HandleDownload ; + bucStateContext.CurrentState.HandleDownload; end; end; @@ -756,6 +829,12 @@ begin // Implement your logic here end; +procedure UpdateAvailableState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + ChangeState(DownloadingState); +end; + function UpdateAvailableState.StateName; begin // Implement your logic here @@ -831,6 +910,12 @@ begin // Implement your logic here end; +procedure DownloadingState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // Continue downloading +end; + function DownloadingState.StateName; begin // Implement your logic here @@ -939,6 +1024,14 @@ begin // Implement your logic here end; +procedure WaitingRestartState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // Notify User to install + ChangeState(InstallingState); + +end; + function WaitingRestartState.StateName; begin // Implement your logic here @@ -1080,6 +1173,11 @@ begin ChangeState(IdleState); end; +procedure InstallingState.HandleInstallNow; +begin + // Do Nothing. Need the UI to let user know installation in progress OR +end; + function InstallingState.StateName; begin // Implement your logic here @@ -1130,6 +1228,13 @@ begin // Implement your logic here end; +procedure RetryState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // TODO: #10038 handle retry counts + ChangeState(InstallingState); +end; + function RetryState.StateName; begin // Implement your logic here @@ -1198,6 +1303,11 @@ begin // Handle Abort end; +procedure WaitingPostInstallState.HandleInstallNow; +begin + // Do nothing as files will be cleaned via HandleKmShell +end; + function WaitingPostInstallState.StateName; begin // Implement your logic here From 3dc3a250eca20009d550146430f607c5ec9844d2 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:42:46 +1000 Subject: [PATCH 019/124] feat(windows): remove stub comments --- .../desktop/kmshell/main/BackgroundUpdate.pas | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index fe2eb22a6c..881ce6a7eb 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -614,7 +614,6 @@ end; function TBackgroundUpdate.CurrentStateName: string; begin - // Implement your logic here Result := CurrentState.StateName; end; @@ -675,33 +674,33 @@ end; procedure IdleState.HandleDownload; begin - // Implement your logic here + end; function IdleState.HandleKmShell; begin - // Implement your logic here + Result := kmShellContinue; end; procedure IdleState.HandleInstall; begin - // Implement your logic here + end; procedure IdleState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure IdleState.HandleAbort; begin - // Implement your logic here + end; function IdleState.StateName; begin - // Implement your logic here + Result := 'IdleState'; end; @@ -724,7 +723,7 @@ end; procedure UpdateAvailableState.HandleCheck; begin - // Implement your logic here + end; procedure UpdateAvailableState.HandleDownload; @@ -743,22 +742,22 @@ end; procedure UpdateAvailableState.HandleInstall; begin - // Implement your logic here + end; procedure UpdateAvailableState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure UpdateAvailableState.HandleAbort; begin - // Implement your logic here + end; function UpdateAvailableState.StateName; begin - // Implement your logic here + Result := 'UpdateAvailableState'; end; @@ -817,23 +816,20 @@ end; procedure DownloadingState.HandleInstall; begin - // Implement your logic here ChangeState(InstallingState); end; procedure DownloadingState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure DownloadingState.HandleAbort; begin - // Implement your logic here end; function DownloadingState.StateName; begin - // Implement your logic here Result := 'DownloadingState'; end; @@ -879,12 +875,12 @@ end; procedure WaitingRestartState.HandleCheck; begin - // Implement your logic here + end; procedure WaitingRestartState.HandleDownload; begin - // Implement your logic here + end; function WaitingRestartState.HandleKmShell; @@ -926,22 +922,22 @@ end; procedure WaitingRestartState.HandleInstall; begin - // Implement your logic here + end; procedure WaitingRestartState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure WaitingRestartState.HandleAbort; begin - // Implement your logic here + end; function WaitingRestartState.StateName; begin - // Implement your logic here + Result := 'WaitingRestartState'; end; @@ -1049,12 +1045,12 @@ end; procedure InstallingState.HandleCheck; begin - // Implement your logic here + end; procedure InstallingState.HandleDownload; begin - // Implement your logic here + end; function InstallingState.HandleKmShell; @@ -1067,12 +1063,12 @@ end; procedure InstallingState.HandleInstall; begin - // Implement your logic here + end; procedure InstallingState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure InstallingState.HandleAbort; @@ -1082,7 +1078,7 @@ end; function InstallingState.StateName; begin - // Implement your logic here + Result := 'InstallingState'; end; @@ -1101,12 +1097,12 @@ end; procedure RetryState.HandleCheck; begin - // Implement your logic here + end; procedure RetryState.HandleDownload; begin - // Implement your logic here + end; function RetryState.HandleKmShell; @@ -1117,22 +1113,22 @@ end; procedure RetryState.HandleInstall; begin - // Implement your logic here + end; procedure RetryState.HandleMSIInstallComplete; begin - // Implement your logic here + end; procedure RetryState.HandleAbort; begin - // Implement your logic here + end; function RetryState.StateName; begin - // Implement your logic here + Result := 'RetryState'; end; @@ -1200,7 +1196,7 @@ end; function WaitingPostInstallState.StateName; begin - // Implement your logic here + Result := 'WaitingPostInstallState'; end; From f7744f9a491101f3ec18a0bed17f73ac0abcb066 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:41:56 +1000 Subject: [PATCH 020/124] feat(windows): mark outstanding issues with feature --- .../general/Keyman.System.ExecuteHistory.pas | 10 --- .../desktop/kmshell/main/BackgroundUpdate.pas | 89 ++++++++----------- .../main/Keyman.System.DownloadUpdate.pas | 9 +- 3 files changed, 42 insertions(+), 66 deletions(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas index 97e9b2bfb1..2ed824ec0c 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -18,28 +18,18 @@ var atom: WORD; begin Result := False; - KL.Log('RecordKeymanStarted: Enter'); try atom := GlobalFindAtom(AtomName); - KL.Log('Keyman Session Flag atom is: '+IntToStr(atom)); if atom = 0 then begin - KL.Log('RecordKeymanStarted: if atom = 0'); if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; - //writeln('The Sample Keyman Session Flag has not been set, so the process have never been started in this session.'); atom := GlobalAddAtom(AtomName); KL.Log('RecordKeymanStarted: True'); Result := True; if atom = 0 then RaiseLastOSError; end; - //else - //writeln('The process has been started previously because the Sample Keyman Session Flag has been set.'); - - //writeln; - //writeln('* The Sample Keyman Session Flag atom is: '+IntToStr(atom)); - //writeln; except on E: Exception do KL.Log(E.ClassName + ': ' + E.Message); diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index 881ce6a7eb..2e96b6528d 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -94,7 +94,7 @@ type procedure Exit; virtual; abstract; procedure HandleCheck; virtual; abstract; procedure HandleDownload; virtual; abstract; - function HandleKmShell : Integer; virtual; abstract; + function HandleKmShell : Integer; virtual; abstract; procedure HandleInstall; virtual; abstract; procedure HandleMSIInstallComplete; virtual; abstract; procedure HandleAbort; virtual; abstract; @@ -111,11 +111,11 @@ type procedure Exit; override; procedure HandleCheck; override; procedure HandleDownload; override; - function HandleKmShell : Integer; override; + function HandleKmShell : Integer; override; procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; UpdateAvailableState = class(TState) @@ -124,11 +124,11 @@ type procedure Exit; override; procedure HandleCheck; override; procedure HandleDownload; override; - function HandleKmShell : Integer; override; + function HandleKmShell : Integer; override; procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; DownloadingState = class(TState) @@ -143,7 +143,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; WaitingRestartState = class(TState) @@ -156,7 +156,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; InstallingState = class(TState) @@ -181,7 +181,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; RetryState = class(TState) @@ -194,7 +194,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; WaitingPostInstallState = class(TState) @@ -207,7 +207,7 @@ type procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; - function StateName: string; override; + function StateName: string; override; end; { This class also controls the state flow see } @@ -250,7 +250,6 @@ type tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - //function IsKeymanRunning: Boolean; function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; @@ -460,20 +459,6 @@ begin end; - -//function TBackgroundUpdate.IsKeymanRunning: Boolean; // I2329 -//begin -// try -// Result := kmcom.Control.IsKeymanRunning; -// except -// on E:Exception do -// begin -// KL.Log(E.Message); -// Exit(False); -// end; -// end; -//end; - function TBackgroundUpdate.CheckUpdateSchedule: Boolean; begin try @@ -529,7 +514,7 @@ begin end else begin - // TODO: Unable to set state for Value [] + // TODO: #10210 Error log for Unable to set state for Value end; end; @@ -577,7 +562,7 @@ begin usRetry: Result := RetryState; usWaitingPostInstall: Result := WaitingPostInstallState; else - // Log error unknown state setting to idle + // TODO: #10210 Log error unknown state setting to idle Result := IdleState; end; end; @@ -636,13 +621,12 @@ end; procedure IdleState.Enter; begin // Enter UpdateAvailableState - // register name bucStateContext.SetRegistryState(usIdle); end; procedure IdleState.Exit; begin - // Exit UpdateAvailableState + end; procedure IdleState.HandleCheck; @@ -655,9 +639,11 @@ begin this all in the Idle HandleCheck message. But could be broken into an seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - //CheckForUpdates := TRemoteUpdateCheck.Create(False); + + // should be false but forcing check for testing - CheckForUpdates := TRemoteUpdateCheck.Create(True); + //CheckForUpdates := TRemoteUpdateCheck.Create(True); + CheckForUpdates := TRemoteUpdateCheck.Create(False); try Result:= CheckForUpdates.Run; finally @@ -789,7 +775,7 @@ end; procedure DownloadingState.HandleCheck; begin - // For now just pretend updated found + end; procedure DownloadingState.HandleDownload; @@ -805,13 +791,23 @@ begin // TODO check if keyman is running then send to Waiting Restart if DownloadResult then begin - ChangeState(InstallingState); + if HasKeymanRun then + begin + ChangeState(WaitingRestartState); + Result := kmShellContinue; + end + else + begin + ChangeState(InstallingState); + Result := kmShellExit; + end; end else begin ChangeState(RetryState); + Result := kmShellContinue; end; - Result := kmShellContinue; + end; procedure DownloadingState.HandleInstall; @@ -846,7 +842,7 @@ begin DownloadResult := DownloadUpdate.DownloadUpdates; KL.Log('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); Result := DownloadResult; -// TODO: workout when we need to refresh kmcom keyboards +// #TODO: #10210 workout when we need to refresh kmcom keyboards // if Result in [ wucSuccess] then // begin @@ -963,6 +959,7 @@ var s: string; FResult: Boolean; begin + FResult := False; s := LowerCase(ExtractFileExt(bucStateContext.FParams.Keyman.SavePath)); if s = '.msi' then FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+bucStateContext.FParams.Keyman.SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 @@ -1007,9 +1004,6 @@ var fileNames: TStringDynArray; begin bucStateContext.SetRegistryState(usInstalling); - // Needs to be desing discusion about the correct location for the cache - //SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); - // For testing SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); GetFileNamesInDirectory(SavePath, fileNames); @@ -1021,18 +1015,15 @@ begin if fileExt = '.exe' then break; end; - // ExecuteInstall(SavePath + ExtractFileName(fileName)); - // TODO DoInstallPackages ( this may need to be as state in the enum seperate - // to installing the main keyman executable. + if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then begin KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall OK'); end else begin - // TODO: clean failed download - // TODO: Do we do a retry on install? probably not - // install error log the error. + // TODO: #10210 clean failed download + // TODO: #10210 Do we do a retry on install? probably not KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall fail'); ChangeState(IdleState); end @@ -1107,7 +1098,7 @@ end; function RetryState.HandleKmShell; begin - // TODO Implement retry + // #TODO: #10210 Implement retry Result := kmShellContinue end; @@ -1157,8 +1148,8 @@ end; function WaitingPostInstallState.HandleKmShell; begin - // TODO maybe have a counter if we get called in this state - // to many time we need + // TODO: #10210 have a counter if we get called in this state + // too many time abort. HandleMSIInstallComplete; Result := kmShellContinue; end; @@ -1174,10 +1165,6 @@ var SavePath: string; FileNames: TStringDynArray; begin KL.Log('WaitingPostInstallState.HandleMSIInstallComplete'); - // TODO Remove cached files. Do any loging updating of files etc and then set back to idle - //SavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); - /// For testing using local user area cache - //SavePath := 'C:\Projects\rcswag\testCache'; SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); KL.Log('WaitingPostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index bee733e4b7..f095341c82 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -89,7 +89,7 @@ uses System.StrUtils; // temp wrapper for converting showmessage to logs don't know where - // if nt using klog + // if not using klog procedure LogMessage(LogMessage: string); begin KL.Log(LogMessage); @@ -194,7 +194,8 @@ begin // 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 + // TODO: #10210 convert to error log. + LogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); end else begin @@ -213,7 +214,6 @@ var DownloadResult : Boolean; ucr: TUpdateCheckResponse; begin - // DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then begin @@ -235,7 +235,6 @@ var FileNames : TStringDynArray; begin - // DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(GetFolderPath(CSIDL_COMMON_APPDATA) + SFolder_CachedUpdateFiles); SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); GetFileNamesInDirectory(SavedPath, FileNames); if Length(FileNames) = 0 then @@ -266,7 +265,7 @@ begin Result := False; Exit; end; - // TODO verify filesizes match so we know we don't have partical downloades. + // TODO verify filesizes match so we know we don't have partial downloades. Result := True; end else From e29e212eafbed4c61bc59a9ca3e4aafe2bd3318f Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:58:45 +1000 Subject: [PATCH 021/124] feat(windows): WIP - leave commit so work not lost --- .../desktop/kmshell/main/UfrmStartInstall.dfm | 16 ++++++++ .../desktop/kmshell/main/UfrmStartInstall.pas | 38 +++++++++++++++++++ windows/src/desktop/kmshell/xml/strings.xml | 10 +++++ 3 files changed, 64 insertions(+) create mode 100644 windows/src/desktop/kmshell/main/UfrmStartInstall.dfm create mode 100644 windows/src/desktop/kmshell/main/UfrmStartInstall.pas diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm new file mode 100644 index 0000000000..4414a07c10 --- /dev/null +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm @@ -0,0 +1,16 @@ +object frmStartInstall: TfrmStartInstall + Left = 0 + Top = 0 + Caption = 'frmStartInstall' + ClientHeight = 299 + ClientWidth = 635 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + PixelsPerInch = 96 + TextHeight = 13 +end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas new file mode 100644 index 0000000000..dce20b6cce --- /dev/null +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -0,0 +1,38 @@ +unit UfrmStartInstall; + +interface + +uses + + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, UserMessages, StdCtrls, ExtCtrls, UfrmKeymanBase; + +type + TfrmStartInstall = class(TfrmKeymanBase) + LabelMessage: TLabel; + InstallButton: TButton; + CancelButton: TButton; + procedure InstallButtonClick(Sender: TObject); + procedure CancelButtonClick(Sender: TObject); + private + public + end; + +var + frmStartInstall: TfrmStartInstall; + +implementation + +{$R *.dfm} + +procedure TfrmStartInstall.InstallButtonClick(Sender: TObject); +begin + ModalResult := mrOk; +end; + +procedure TfrmStartInstall.CancelButtonClick(Sender: TObject); +begin + ModalResult := mrCancel; +end; + +end. diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 2fa2307a33..c4cd8d6f22 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -788,6 +788,16 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to + + + + Apply update now + + + + + Check for new updates + From db18917a631d8a4bbfce6f2f08e0a9397b27fdff Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:00:10 +1000 Subject: [PATCH 022/124] feat(windows): project file listing updated --- windows/src/desktop/kmshell/kmshell.dpr | 5 ++++- windows/src/desktop/kmshell/kmshell.dproj | 16 ++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 5eb0ee9013..eb4cbaa4c2 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -182,7 +182,8 @@ uses Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', BackgroundUpdate in 'main\BackgroundUpdate.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', - Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas'; + Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas', + UfrmStartInstall in 'main\UfrmStartInstall.pas' {Form1}; {$R VERSION.RES} {$R manifest.res} @@ -204,6 +205,8 @@ begin Application.Initialize; Application.Title := 'Keyman Configuration'; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); + Application.CreateForm(TForm1, Form1); + Application.CreateForm(TForm1, Form1); try Run; finally diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 159a3fe88d..64a6b83be1 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -359,6 +359,10 @@ + +
    Form1
    + dfm +
    Cfg_2 @@ -420,12 +424,6 @@ False - - - kmshell.rsm - true - - kmshell.exe @@ -438,6 +436,12 @@ true + + + kmshell.rsm + true + + 1 From 14310a118626fcfa70a02ba1449a7144c37febeb Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:39:45 +1000 Subject: [PATCH 023/124] feat(windows): rename files to new delphi pattern --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 14 +++++++------- .../kmshell/main/BackgroundUpdateStateDiagram.md | 11 +++++++++++ ...ckgroundUpdate.pas => Keyman.System.Update.pas} | 2 +- windows/src/desktop/kmshell/main/initprog.pas | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md rename windows/src/desktop/kmshell/main/{BackgroundUpdate.pas => Keyman.System.Update.pas} (99%) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 5d19e25509..1f1f618ec6 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -181,7 +181,7 @@ uses UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', - BackgroundUpdate in 'main\BackgroundUpdate.pas', + Keyman.System.Update in 'main\Keyman.System.Update.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas', UfrmStartInstall in 'main\UfrmStartInstall.pas' {Form1}; diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index d1e73fb3bd..83a0827efb 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,7 +357,7 @@ - + @@ -425,12 +425,6 @@ False - - - kmshell.exe - true - - kmshell.exe @@ -443,6 +437,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md new file mode 100644 index 0000000000..59c735eeb2 --- /dev/null +++ b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md @@ -0,0 +1,11 @@ +``` mermaid +stateDiagram + [*] --> Idle + Idle --> UpdateAvailable + UpdateAvailable --> Downloading + Downloading --> Installing + Downloading --> WaitingRestart + WaitingRestart --> Installing + Installing --> WaitingPostInstall + WaitingPostInstall --> Idle +``` diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.Update.pas similarity index 99% rename from windows/src/desktop/kmshell/main/BackgroundUpdate.pas rename to windows/src/desktop/kmshell/main/Keyman.System.Update.pas index eafa8194ff..fb87834783 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.Update.pas @@ -15,7 +15,7 @@ Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md History: *) -unit BackgroundUpdate; +unit Keyman.System.Update; interface diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index d4d43874cc..763cf11f99 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -144,7 +144,7 @@ uses UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, - BackgroundUpdate, + Keyman.System.Update, KeyboardTIPCheck, From 9c7e2c879a35af3e5abe0a7dc751a3490b8ef291 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 May 2024 11:56:34 +0200 Subject: [PATCH 024/124] change(common): preliminary update of min node version This updates the minimum required node version to Node 20. The latest version of Node 18 still contains the npm bug (https://github.com/npm/cli/issues/7072) whereas Node 20 got updated to a npm version that contains a fix. Node 20 is known to work with our current code, so this change updates to that as an intermediate step before we investigate if we can update to Node 22 as discussed for Keyman 18. --- resources/build/minimum-versions.inc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index 17757d5584..d7aff76b94 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -13,7 +13,7 @@ KEYMAN_MIN_TARGET_VERSION_UBUNTU=20.04 # Ubuntu 20.04 Focal KEYMAN_MIN_TARGET_VERSION_CHROME=95.0 # Final version that runs on Android 5.0 # Dependency versions -KEYMAN_MIN_VERSION_NODE_MAJOR=18 +KEYMAN_MIN_VERSION_NODE_MAJOR=20 # Latest Node 20 doesn't have the buggy npm (npm#7072) KEYMAN_MIN_VERSION_NPM=10.5.1 # 10.5.0 has bug, discussed in #10350 KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.44 # Warning: 3.1.45 is bad (#9529); newer versions work KEYMAN_MAX_VERSION_EMSCRIPTEN=3.1.58 # See #9529 From 2dffec366ce9c4fa24c094b05e0000f9099000bc Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 6 May 2024 15:07:37 +0200 Subject: [PATCH 025/124] docs(common): improve formatting of `builder.md` --- resources/build/builder.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resources/build/builder.md b/resources/build/builder.md index 3855288392..0d0c385c50 100644 --- a/resources/build/builder.md +++ b/resources/build/builder.md @@ -86,13 +86,12 @@ This somewhat unwieldy incantation handles all our build environments. The intent is to get a good solid consistent path for the script so that we can safely include the build script, no matter what `pwd` is when the script is run. - The only modification permissible in this block is the `` text which will be a series of `../` paths taking us to the repository root from the location of the script itself. It is essential to make the include relative to the repo root, even for scripts -under the resources/ folder. Doing this gives us significant performance +under the `resources/` folder. Doing this gives us significant performance benefits. Inclusion of other scripts should be kept outside this standard build script @@ -1030,4 +1029,4 @@ Note: it is recommended that you use `$(builder_term text)` instead of [`builder_echo`]: #builderecho-function [`builder_die`]: #builderdie-function [`builder_echo_debug`]: #builderechodebug-function -[`builder_is_debug_build`]: #builderisdebugbuild-function \ No newline at end of file +[`builder_is_debug_build`]: #builderisdebugbuild-function From d554415b1c7ddf7704bad9d6b95063014c06a124 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 6 May 2024 15:08:30 +0200 Subject: [PATCH 026/124] change(common): add readme and `build.sh` --- docs/build/linux-ubuntu.md | 90 ++++-------------- linux/Dockerfile | 90 ------------------ resources/docker-images/README.md | 148 ++++++++++++++++++++++++++++++ resources/docker-images/build.sh | 109 ++++++++++++++++++++++ 4 files changed, 273 insertions(+), 164 deletions(-) delete mode 100644 linux/Dockerfile create mode 100644 resources/docker-images/README.md create mode 100755 resources/docker-images/build.sh diff --git a/docs/build/linux-ubuntu.md b/docs/build/linux-ubuntu.md index 00aaeef0b3..5879e43337 100644 --- a/docs/build/linux-ubuntu.md +++ b/docs/build/linux-ubuntu.md @@ -205,83 +205,25 @@ Android projects. `JAVA_HOME_11` is mostly used by CI. ## Docker Builder The Docker builder allows you to perform a build from anywhere Docker is supported. - -To build the docker image: - -```shell -cd linux -docker pull ubuntu:latest # (to make sure you have an up-to-date image) -docker build . -t keymanapp/keyman-linux-builder:latest -``` - -Once the image is built, it may be used to build parts of Keyman. - -**Note** that it's not yet possible to run tests in the Docker container. - -- core - - ```shell - # build 'Keyman Core' in docker - # keep linux build artifacts separate - mkdir -p $(git rev-parse --show-toplevel)/core/build/linux - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - -v $(git rev-parse --show-toplevel)/core/build/linux:/home/build/build/core/build \ - keymanapp/keyman-linux-builder:latest \ - core/build.sh --debug - ``` - -- linux - - ```shell - # build 'Keyman for Linux' installation in docker - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - --entrypoint /bin/bash keymanapp/keyman-linux-builder:latest \ - -c 'DESTDIR=/home/build /usr/bin/bashwrapper linux/build.sh --debug build install' - ``` - -- Keyman Web - - ```shell - # build 'Keyman Web' in docker - docker run --privileged -it --rm \ - -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-linux-builder:latest \ - web/build.sh --debug - ``` - -- Keyman for Android - - ```shell - # build 'Keyman for Android' in docker - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-linux-builder:latest \ - android/build.sh --debug - ``` - -### Customizing the builder - -You can use Docker [build args](https://docs.docker.com/build/guide/build-args/) to customize the image build. As an example, the following will build an image explicitly with Ubuntu 23.04 and Node.js 20. Check the [Dockerfile](../../linux/Dockerfile) for `ARG` entries. - -```shell -cd linux -docker pull ubuntu:23.04 # (to make sure you have an up-to-date image) -docker build . -t keymanapp/keyman-linux-builder:u23.04-node20 --build-arg OS_VERSION=23.04 --build-arg NODE_MAJOR=20 -```` +See [this README.md](../../resources/docker-images/README.md) for details. ### Using the builder with VSCode [Dev Containers](https://code.visualstudio.com/docs/devcontainers/tutorial) -1. Save the following as `.devcontainer/devcontainer.json`, updating the `image` to match the Docker image built above. +1. Save the following as `.devcontainer/devcontainer.json`, updating the `image` + to match the Docker image built above. -```json -// file: .devcontainer/devcontainer.json -{ - "name": "Keyman Ubuntu 23.04", - "image": "keymanapp/keyman-linux-builder:u23.04-node18" -} -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu -``` + ```json + // file: .devcontainer/devcontainer.json + { + "name": "Keyman Ubuntu 23.04", + "image": "keymanapp/keyman-linux-builder:u23.04-node18" + } + // For format details, see https://aka.ms/devcontainer.json. For config options, + // see the README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu + ``` -2. in VSCode, use the "Dev Containers: Open Folder In Container…" option and choose the Keyman directory. +2. in VSCode, use the "Dev Containers: Open Folder In Container…" option and + choose the Keyman directory. -3. You will be given a window which is running VSCode inside this builder image, regardless of your host OS. +3. You will be given a window which is running VSCode inside this builder + image, regardless of your host OS. diff --git a/linux/Dockerfile b/linux/Dockerfile deleted file mode 100644 index ea07ec699a..0000000000 --- a/linux/Dockerfile +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2022-2023 SIL International. All rights reserved. -# -# builder image for a linux build -# see ../docs/build/linux-ubuntu.md - -ARG OS_VERSION=latest -ARG OS_PLATFORM=amd64 - -FROM --platform=${OS_PLATFORM} ubuntu:${OS_VERSION} -LABEL org.opencontainers.image.authors="SIL International." -LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" -LABEL org.opencontainers.image.title="Keyman Linux Build Image" - -# We will switch to a build user after some installation -USER root -ENV HOME /home/build -RUN useradd -c "Build user" --home-dir $HOME --create-home --shell /usr/bin/bashwrapper build -VOLUME /home/build/build -WORKDIR /home/build/build -ENV DEBIAN_FRONTEND noninteractive -ENV DEBIAN_PRIORITY critical -ENV DEBCONF_NOWARNINGS yes - -# Update to the latest -RUN apt-get -q -y update && \ - apt-get -q -y install devscripts equivs meson python3 python3-setuptools software-properties-common curl && \ - add-apt-repository ppa:keymanapp/keyman && \ - add-apt-repository ppa:keymanapp/keyman-alpha -RUN apt-get -q -y update && \ - apt-get -q -y upgrade - -# Install dependencies -ADD debian/control /tmp/control -# Answer 'yes' to install questions -RUN (yes | mk-build-deps --install /tmp/control) || true && \ - rm /tmp/control - -# Install Node -ARG NODE_MAJOR=18 -RUN apt-get install -q -y ca-certificates curl gnupg && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"${NODE_MAJOR}".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y - -ARG EMSCRIPTEN_VERSION=3.1.44 -# Install emscripten -RUN cd /usr/share && \ - git clone https://github.com/emscripten-core/emsdk.git && \ - cd emsdk && \ - ./emsdk install ${EMSCRIPTEN_VERSION} && \ - ./emsdk activate ${EMSCRIPTEN_VERSION} && \ - echo "#!/bin/bash" > /usr/bin/bashwrapper && \ - echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper - -# Keyman Web -RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ - apt-get -q -y install ./google-chrome-stable_current_amd64.deb && \ - rm google-chrome-stable_current_amd64.deb && \ - echo "export CHROME_BIN=/opt/google/chrome/chrome" >> /usr/bin/bashwrapper - -# Keyman for Android -RUN apt-get -q -y install gradle maven pandoc sdkmanager jq && \ - sdkmanager platform-tools && \ - yes | sdkmanager --licenses && \ - chown -R build:build /opt/android-sdk/ && \ - echo "export ANDROID_HOME=/opt/android-sdk" >> /usr/bin/bashwrapper && \ - echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64" >> /usr/bin/bashwrapper - -# Finish bashwrapper script and adjust permissions -RUN echo "\${@:-bash}" >> /usr/bin/bashwrapper && \ - chmod +x /usr/bin/bashwrapper && \ - chown -R build:build $HOME - -# now, switch to build user -USER build - -# Pre-install gradle. This will put files in ~/.gradle which will speed up builds. -RUN mkdir -p $HOME/tmp/gradle/wrapper && \ - # KMEA uses gradle-7.5.1-bin - curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \ - curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.properties && \ - curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradlew && \ - chmod +x $HOME/tmp/gradlew && \ - $HOME/tmp/gradlew --quiet && \ - # Some projects use gradle-7.5.1-all, so we pre-install that as well - curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.jar && \ - curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties && \ - curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradlew && \ - chmod +x $HOME/tmp/gradlew && \ - $HOME/tmp/gradlew --quiet && \ - rm -rf $HOME/tmp - -ENTRYPOINT [ "/usr/bin/bashwrapper" ] diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md new file mode 100644 index 0000000000..5a241af91f --- /dev/null +++ b/resources/docker-images/README.md @@ -0,0 +1,148 @@ +# Container + +Docker containers that can be used to build Keyman on the respective +platforms. They contain everything that a CI build agent needs to +build for the platform. + +## Prerequisites + +You'll need Docker Buildx installed to successfully be able to build the +container images. This is easiest achieved by installing the [official +Docker version](https://docs.docker.com/engine/install/ubuntu/). + +Currently it is not possible to use Podman instead of Docker due to a number +of bugs and incompatibilities in the Podman implementation. + +## Building the images + +To build the docker images: + +```shell +resources/docker-images/build.sh +``` + +By default this will create 64-bit images for building +Keyman for Android, Keyman for Linux and Keyman for Web. These images +are based on the Ubuntu 24.04 with Node 20 and Emscripten +3.1.44 (for the exact versions, see [`minimum-versions.inc.sh`](../build/minimum-versions.inc.sh)) +and are named e.g. `keyman-core-ci:default`. + +The versions can be changed, e.g. + +```shell +resources/docker-images/build.sh --ubuntu-version jammy --node 20 +``` + +This will create an image named e.g. `keyman-core-ci:jammy-node20`. + +Once the image is built, it may be used to build parts of Keyman. + +## Building locally + +It is possible to build locally with these images: + +- Keyman Core + + ```shell + # build 'Keyman Core' in docker + # keep build artifacts separate + mkdir -p $(git rev-parse --show-toplevel)/core/build/docker-core + docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + -v $(git rev-parse --show-toplevel)/core/build/docker-core:/home/build/build/core/build \ + keymanapp/keyman-core-ci:default \ + core/build.sh --debug build + ``` + + Note: Since the generated binaries are platform dependent we put them in a container + specific directory. + +- Keyman for Linux + + ```shell + # build 'Keyman for Linux' installation in docker + # keep build artifacts separate + mkdir -p $(git rev-parse --show-toplevel)/linux/build/docker-linux + docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + -v $(git rev-parse --show-toplevel)/linux/build/docker-linux:/home/build/build/linux/build \ + -e DESTDIR=/tmp \ + keymanapp/keyman-linux-ci:default \ + linux/build.sh --debug build install + ``` + + Note: Since the generated binaries are platform dependent we put them in a container + specific directory. + +- Keyman Web + + ```shell + # build 'Keyman Web' in docker + docker run --privileged -it --rm \ + -v $(git rev-parse --show-toplevel):/home/build/build \ + keymanapp/keyman-web-ci:default \ + web/build.sh --debug + ``` + +- Keyman for Android + + ```shell + # build 'Keyman for Android' in docker + docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + keymanapp/keyman-android-ci:default \ + android/build.sh --debug + ``` + +## Running tests locally + +- Keyman Core + + ```shell + # build 'Keyman Core' in docker + # keep build artifacts separate + mkdir -p $(git rev-parse --show-toplevel)/core/build/docker-core + docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + -v $(git rev-parse --show-toplevel)/core/build/docker-core:/home/build/build/core/build \ + keymanapp/keyman-core-ci:default \ + core/build.sh --debug test + ``` + + Note: Since the generated binaries are platform dependent we put them in a container + specific directory. + +- Keyman for Linux + + ```shell + # build 'Keyman for Linux' installation in docker + # keep build artifacts separate + mkdir -p $(git rev-parse --show-toplevel)/linux/build/docker-linux + docker run --privileged -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + -v $(git rev-parse --show-toplevel)/linux/build/docker-linux:/home/build/build/linux/build \ + -e DESTDIR=/tmp \ + keymanapp/keyman-linux-ci:default \ + linux/build.sh --debug test + ``` + + Note: this requires the `--privileged` parameter in order for all tests to pass! + + Note: Since the generated binaries are platform dependent we put them in a container + specific directory. + +- Keyman Web + + ```shell + # build 'Keyman Web' in docker + docker run --privileged -it --rm \ + -v $(git rev-parse --show-toplevel):/home/build/build \ + keymanapp/keyman-web-ci:default \ + web/build.sh --debug test + ``` + + Note: this requires the `--privileged` parameter in order for all tests to pass! + +- Keyman for Android + + ```shell + # build 'Keyman for Android' in docker + docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ + keymanapp/keyman-android-ci:default \ + android/build.sh --debug test + ``` diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh new file mode 100755 index 0000000000..294d5f463a --- /dev/null +++ b/resources/docker-images/build.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +################################ Main script ################################ + +. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh" + +builder_describe \ + "Build docker images" \ + ":android" \ + ":base" \ + ":core" \ + ":linux" \ + ":web" \ + "--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ + "--node=NODE_MAJOR Node version (default: ${KEYMAN_MIN_VERSION_NODE_MAJOR})" \ + "--emscripten=EMSCRIPTEN_VERSION Emscripten version (default: ${KEYMAN_MIN_VERSION_EMSCRIPTEN})" \ + "--no-cache Force rebuild of docker images" \ + "build" + +builder_parse "$@" + +_add_build_args() { + local var=$1 + local default_var=$2 + local name=$3 + local value + + if [[ -n "${!var:-}" ]]; then + value="${!var}" + else + value="${!default_var:-}" + fi + + build_args+=(--build-arg="${var}=${value}") + + if [[ -n "${build_version:-}" ]]; then + build_version="${build_version}-${name:-}${value}" + else + build_version="${name}${value}" + fi +} + +_convert_parameters_to_build_args() { + build_args=() + build_version= + + _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" + _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java + _add_build_args NODE_MAJOR KEYMAN_MIN_VERSION_NODE_MAJOR node + _add_build_args EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emscr + + if [[ -n "${BASE_VERSION:-}" ]]; then + build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}") + fi +} + +_is_default_values() { + [[ -z "${UBUNTU_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]] && \ + [[ -z "${NODE_MAJOR:-}" ]] && [[ -z "${EMSCRIPTEN_VERSION:-}" ]] +} + +build_action() { + local platform=$1 + + builder_echo debug "Building image for ${platform}" + + _convert_parameters_to_build_args + + if [[ "${platform}" == "base" ]]; then + docker pull --platform "amd64" "ubuntu:${UBUNTU_VERSION:-${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER}}" + ### TMP code + elif [[ "${platform}" == "core" ]]; then + cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}" + ### TMP END + elif [[ "${platform}" == "linux" ]]; then + cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}" + fi + + if builder_has_option --no-cache; then + OPTION_NO_CACHE="--no-cache" + fi + + cd "${platform}" || true + # shellcheck disable=SC2248 + docker build ${OPTION_NO_CACHE:-} -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . + # If the user didn't specify particular versions we will additionaly create an image + # with the tag 'default'. + if _is_default_values; then + builder_echo debug "Setting default tag for ${platform}" + docker build . -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" + fi + cd - || true + builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" +} + +if builder_has_action build; then + build_action base + BASE_VERSION="${build_version}" + builder_run_action build:android build_action android + builder_run_action build:core build_action core + builder_run_action build:linux build_action linux + builder_run_action build:web build_action web +fi From 5c337bec8e2427bedbf16d5b822c9d18f9402b54 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 3 May 2024 17:47:48 +0200 Subject: [PATCH 027/124] feat(common): add base CI image --- resources/docker-images/base/Dockerfile | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 resources/docker-images/base/Dockerfile diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile new file mode 100644 index 0000000000..116d4b8084 --- /dev/null +++ b/resources/docker-images/base/Dockerfile @@ -0,0 +1,30 @@ +# Copyright (c) 2024 SIL International. All rights reserved. + +ARG UBUNTU_VERSION=latest +FROM --platform=amd64 ubuntu:${UBUNTU_VERSION} + +LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" +LABEL org.opencontainers.image.title="Keyman Build Base Image" + +# We will switch to a build user after some installation +USER root +ENV HOME=/home/build +RUN grep ubuntu /etc/passwd && userdel ubuntu || true && \ + rm -rf /home/ubuntu && \ + useradd -c "Build user" --uid 1000 --home-dir $HOME --create-home --shell /usr/bin/bashwrapper build + +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBIAN_PRIORITY=critical +ENV DEBCONF_NOWARNINGS=yes + +# Update to the latest +RUN apt-get -q -y update && \ + apt-get -q -y install ca-certificates curl gnupg meson software-properties-common sudo && \ + add-apt-repository ppa:keymanapp/keyman && \ + add-apt-repository ppa:keymanapp/keyman-alpha +RUN apt-get -q -y update && \ + apt-get -q -y upgrade + +# Allow build user to use `sudo` +RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers From 72bc6c7b8bb9ce0acd55a8303d0cc4449068ecf6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 25 Apr 2024 18:04:46 +0200 Subject: [PATCH 028/124] feat(linux): add CI image for building Keyman for Linux This change allows to build a docker image that can build Keyman for Linux with test coverage reports, and installs the necessary dependencies for running integration tests. --- resources/docker-images/linux/.gitignore | 1 + resources/docker-images/linux/Dockerfile | 56 ++++++++++++++++++++++ resources/docker-images/linux/run-tests.sh | 19 ++++++++ 3 files changed, 76 insertions(+) create mode 100644 resources/docker-images/linux/.gitignore create mode 100644 resources/docker-images/linux/Dockerfile create mode 100755 resources/docker-images/linux/run-tests.sh diff --git a/resources/docker-images/linux/.gitignore b/resources/docker-images/linux/.gitignore new file mode 100644 index 0000000000..4db28ac495 --- /dev/null +++ b/resources/docker-images/linux/.gitignore @@ -0,0 +1 @@ +control diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile new file mode 100644 index 0000000000..6766aa0859 --- /dev/null +++ b/resources/docker-images/linux/Dockerfile @@ -0,0 +1,56 @@ +# Copyright (c) 2024 SIL International. All rights reserved. + +ARG BASE_VERSION +FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" +LABEL org.opencontainers.image.title="Keyman Linux Build Image" + +# Install dependencies +ADD control /tmp/control +# Answer 'yes' to install questions +RUN apt-get install -qy python3 python3-setuptools python3-coverage \ + devscripts equivs libdatetime-perl lcov gcovr xvfb \ + xserver-xephyr metacity mutter dbus-x11 weston xwayland && \ + (yes | mk-build-deps --install /tmp/control) || true && \ + rm /tmp/control + +# Install lcov for code coverage +# Update to the latest and install packages needed for Linux coverage reporting +# and integration tests. We need at least version 2.0 of lcov. However, +# version 2.0-4 from Noble doesn't work either on Jammy. So we use +# version 2.0-1 from Mantic. +RUN LCOV_VERSION=$(dpkg -s lcov | grep Version | cut -d' ' -f2) && \ + if dpkg --compare-versions "${LCOV_VERSION}" lt 2.0; then \ + curl -sS -o /tmp/lcov.deb --location http://mirrors.kernel.org/ubuntu/pool/universe/l/lcov/lcov_2.0-1_all.deb && \ + apt-get -qy install /tmp/lcov.deb && \ + rm /tmp/lcov.deb ; \ + fi + +RUN mkdir -p /var/run/1000 && \ + chown build:build /var/run/1000 && \ + echo "#!/bin/bash" > /usr/bin/bashwrapper && \ + echo "export XDG_RUNTIME_DIR=/var/run/1000" >> /usr/bin/bashwrapper + +COPY run-tests.sh /usr/bin/run-tests.sh + +# Finish bashwrapper script and adjust permissions +RUN <> /usr/bin/bashwrapper + +if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then + /usr/bin/run-tests.sh "\${@:-bash}" +else + "\${@:-bash}" +fi +EOF + +RUN chmod +x /usr/bin/bashwrapper && \ + chown -R build:build $HOME + +# now, switch to build user +USER build + +VOLUME /home/build/build +WORKDIR /home/build/build + +ENTRYPOINT [ "/usr/bin/bashwrapper" ] diff --git a/resources/docker-images/linux/run-tests.sh b/resources/docker-images/linux/run-tests.sh new file mode 100755 index 0000000000..962418467e --- /dev/null +++ b/resources/docker-images/linux/run-tests.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -e + +# Start system dbus +sudo dbus-daemon --system --fork + +# Start session dbus +# shellcheck disable=SC2046 # SC2046: quote this to prevent word-splitting +export $(dbus-launch) + +# Start Wayland +weston --no-config --socket=wayland-0 --backend=headless & +export WAYLAND_DISPLAY=wayland-0 + +# Start X11 (on Wayland) +Xwayland & +export DISPLAY=:0 + +"${@:-bash}" From 2767765c5a92cfb0d95b51fee3e2bee50a48312e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 25 Apr 2024 19:11:37 +0200 Subject: [PATCH 029/124] feat(web): add CI image for building Keyman Web --- resources/docker-images/web/Dockerfile | 71 ++++++++++++++++++++++++ resources/docker-images/web/run-tests.sh | 12 ++++ 2 files changed, 83 insertions(+) create mode 100644 resources/docker-images/web/Dockerfile create mode 100755 resources/docker-images/web/run-tests.sh diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile new file mode 100644 index 0000000000..83aa5432d9 --- /dev/null +++ b/resources/docker-images/web/Dockerfile @@ -0,0 +1,71 @@ +# Copyright (c) 2024 SIL International. All rights reserved. + +ARG BASE_VERSION +FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} + +LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" +LABEL org.opencontainers.image.title="Keyman for Web Build Image" + +USER root +RUN apt-get install -qy git jq xvfb xserver-xephyr metacity + +COPY run-tests.sh /usr/bin/run-tests.sh + +# Install node +ARG NODE_MAJOR +RUN set -eu; \ + NODE_VERSION=$(curl -sSL https://unofficial-builds.nodejs.org/download/release/ | cut -d'>' -f2 | cut -d'/' -f1 | grep v${NODE_MAJOR} | sort -V | tail -1) && \ + echo "Installing node version ${NODE_VERSION}" && \ + curl -fsSLO --compressed "https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" && \ + tar -xJf "node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" -C /usr/local --strip-components=1 --no-same-owner && \ + ln -s /usr/local/bin/node /usr/local/bin/nodejs + +# Install emscripten +ARG EMSCRIPTEN_VERSION +RUN echo "Installing emscripten version ${EMSCRIPTEN_VERSION}" && \ + cd /usr/share && \ + git clone https://github.com/emscripten-core/emsdk.git && \ + cd emsdk && \ + ./emsdk install ${EMSCRIPTEN_VERSION} && \ + ./emsdk activate ${EMSCRIPTEN_VERSION} && \ + echo "#!/bin/bash" > /usr/bin/bashwrapper && \ + echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper + +# Keyman Web +RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ + apt-get -qy install ./google-chrome-stable_current_amd64.deb && \ + rm google-chrome-stable_current_amd64.deb && \ + echo "export CHROME_BIN=/opt/google/chrome/chrome" >> /usr/bin/bashwrapper + +RUN < /etc/apt/preferences.d/mozilla +Package: * +Pin: origin packages.mozilla.org +Pin-Priority: 1000 +EOF +RUN curl https://packages.mozilla.org/apt/repo-signing-key.gpg > /etc/apt/keyrings/packages.mozilla.org.asc && \ + echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" >> /etc/apt/sources.list.d/mozilla.list && \ + apt-get update && \ + apt-get -qy install firefox && \ + echo "export FIREFOX_BIN=/usr/bin/firefox" >> /usr/bin/bashwrapper + +# Finish bashwrapper script and adjust permissions +RUN <> /usr/bin/bashwrapper + +if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then + /usr/bin/run-tests.sh "\${@:-bash}" +else + "\${@:-bash}" +fi +EOF + +RUN chmod +x /usr/bin/bashwrapper && \ + chown -R build:build $HOME + +# now, switch to build user +USER build + +VOLUME /home/build/build +WORKDIR /home/build/build + +ENTRYPOINT [ "/usr/bin/bashwrapper" ] diff --git a/resources/docker-images/web/run-tests.sh b/resources/docker-images/web/run-tests.sh new file mode 100755 index 0000000000..1a3100159f --- /dev/null +++ b/resources/docker-images/web/run-tests.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e +echo "Starting Xvfb..." +Xvfb -screen 0 1024x768x24 :33 &> /dev/null & +sleep 1 +echo "Starting Xephyr..." +DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null & +sleep 1 +echo "Starting metacity" +metacity --display=:32 &> /dev/null & +export DISPLAY=:32 +"${@:-bash}" From 23d8a014fd0a03fedd89d59fd2b10cdd6a086123 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 3 May 2024 10:30:38 +0200 Subject: [PATCH 030/124] feat(android): add CI image for building Keyman for Android --- resources/docker-images/android/Dockerfile | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 resources/docker-images/android/Dockerfile diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile new file mode 100644 index 0000000000..5cd8fb1e63 --- /dev/null +++ b/resources/docker-images/android/Dockerfile @@ -0,0 +1,68 @@ +# Copyright (c) 2024 SIL International. All rights reserved. + +ARG BASE_VERSION=latest +FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" +LABEL org.opencontainers.image.title="Keyman Android Build Image" + +# Keyman for Android +SHELL ["/bin/bash", "-c"] +# Starting with Ubuntu 24.04 sdkmanager is no longer available, instead +# a version dependent package allows to install the cmdline tools +ARG JAVA_VERSION=11 +RUN </dev/null) + echo "OS_VER=${OS_VER}" + if (( ${OS_VER%%.*} > 22 )); then + PKG_SDKMANAGER=google-android-cmdline-tools-13.0-installer + DIR_SDK=/usr/lib/android-sdk + else + PKG_SDKMANAGER=sdkmanager + DIR_SDK=/opt/android-sdk + fi + apt-get -q -y install gradle maven pandoc $PKG_SDKMANAGER jq openjdk-${JAVA_VERSION}-jdk + sdkmanager platform-tools + yes | sdkmanager --licenses + chown -R build:build $DIR_SDK + echo "#!/bin/bash" > /usr/bin/bashwrapper + echo "export ANDROID_HOME=$DIR_SDK" >> /usr/bin/bashwrapper + echo "export JAVA_HOME_${JAVA_VERSION}=/usr/lib/jvm/java-${JAVA_VERSION}-openjdk-amd64" >> /usr/bin/bashwrapper +EOF + +# Finish bashwrapper script and adjust permissions +RUN <> /usr/bin/bashwrapper + +if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then + /usr/bin/run-tests.sh "\${@:-bash}" +else + "\${@:-bash}" +fi +EOF + +RUN chmod +x /usr/bin/bashwrapper && \ + chown -R build:build $HOME + +# now, switch to build user +USER build + +VOLUME /home/build/build +WORKDIR /home/build/build + +# Pre-install gradle. This will put files in ~/.gradle which will speed up builds. +RUN mkdir -p $HOME/tmp/gradle/wrapper && \ + # KMEA uses gradle-7.5.1-bin + curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \ + curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.properties && \ + curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradlew && \ + chmod +x $HOME/tmp/gradlew && \ + $HOME/tmp/gradlew --quiet && \ + # Some projects use gradle-7.5.1-all, so we pre-install that as well + curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.jar && \ + curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties && \ + curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradlew && \ + chmod +x $HOME/tmp/gradlew && \ + $HOME/tmp/gradlew --quiet && \ + rm -rf $HOME/tmp + +ENTRYPOINT [ "/usr/bin/bashwrapper" ] From 8a4cdcedf411a85c3c1fffa58265ce70dfa268c3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 6 May 2024 18:22:08 +0200 Subject: [PATCH 031/124] feat(core): add CI image for building Keyman Core --- resources/docker-images/core/.gitignore | 1 + resources/docker-images/core/Dockerfile | 64 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 resources/docker-images/core/.gitignore create mode 100644 resources/docker-images/core/Dockerfile diff --git a/resources/docker-images/core/.gitignore b/resources/docker-images/core/.gitignore new file mode 100644 index 0000000000..4db28ac495 --- /dev/null +++ b/resources/docker-images/core/.gitignore @@ -0,0 +1 @@ +control diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile new file mode 100644 index 0000000000..9b7073b7a2 --- /dev/null +++ b/resources/docker-images/core/Dockerfile @@ -0,0 +1,64 @@ +# Copyright (c) 2024 SIL International. All rights reserved. + +ARG BASE_VERSION +FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} + +LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" +LABEL org.opencontainers.image.title="Keyman Core Build Image" + +USER root +RUN apt-get install -qy git jq llvm meson pkgconf \ + xvfb xserver-xephyr metacity + +#### TMP until we properly figure out the dependencies needed for Core +#### We should not need to install /tmp/control +# Install dependencies +ADD control /tmp/control +RUN apt-get install -qy python3 python3-setuptools python3-coverage \ + devscripts equivs libdatetime-perl meson pkgconf lcov gcovr xvfb \ + xserver-xephyr metacity mutter dbus-x11 weston xwayland && \ + (yes | mk-build-deps --install /tmp/control) || true && \ + rm /tmp/control +#### TMP END + +# Install node +ARG NODE_MAJOR +RUN set -eu; \ + NODE_VERSION=$(curl -sSL https://unofficial-builds.nodejs.org/download/release/ | cut -d'>' -f2 | cut -d'/' -f1 | grep v${NODE_MAJOR} | sort -V | tail -1) && \ + echo "Installing node version ${NODE_VERSION}" && \ + curl -fsSLO --compressed "https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" && \ + tar -xJf "node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" -C /usr/local --strip-components=1 --no-same-owner && \ + ln -s /usr/local/bin/node /usr/local/bin/nodejs + +# Install emscripten +ARG EMSCRIPTEN_VERSION +RUN echo "Installing emscripten version ${EMSCRIPTEN_VERSION}" && \ + cd /usr/share && \ + git clone https://github.com/emscripten-core/emsdk.git && \ + cd emsdk && \ + ./emsdk install ${EMSCRIPTEN_VERSION} && \ + ./emsdk activate ${EMSCRIPTEN_VERSION} && \ + echo "#!/bin/bash" > /usr/bin/bashwrapper && \ + echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper + +# Finish bashwrapper script and adjust permissions +RUN <> /usr/bin/bashwrapper + +if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then + /usr/bin/run-tests.sh "\${@:-bash}" +else + "\${@:-bash}" +fi +EOF + +RUN chmod +x /usr/bin/bashwrapper && \ + chown -R build:build $HOME + +# now, switch to build user +USER build + +VOLUME /home/build/build +WORKDIR /home/build/build + +ENTRYPOINT [ "/usr/bin/bashwrapper" ] From a9fd916c8ac6d345ccce9468a64140b734aada56 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:58:22 +1000 Subject: [PATCH 032/124] feat(windows): small refactor of sm wip --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 14 +- .../main/Keyman.System.UpdateStateMachine.pas | 1302 +++++++++++++++++ windows/src/desktop/kmshell/main/initprog.pas | 6 +- 4 files changed, 1313 insertions(+), 11 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 1f1f618ec6..805a79aa58 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -181,7 +181,7 @@ uses UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', - Keyman.System.Update in 'main\Keyman.System.Update.pas', + Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas', UfrmStartInstall in 'main\UfrmStartInstall.pas' {Form1}; diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 83a0827efb..c999a12d42 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,7 +357,7 @@ - + @@ -425,12 +425,6 @@ False - - - kmshell.exe - true - - kmshell.rsm @@ -443,6 +437,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas new file mode 100644 index 0000000000..9ae3df3195 --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -0,0 +1,1302 @@ +(* + Name: UpdateStateMachine + Copyright: Copyright (C) SIL International. + Documentation: + Description: + Create Date: 2 Nov 2023 + + Modified Date: 2 Nov 2023 + Authors: rcruickshank + Related Files: + Dependencies: + + Bugs: + Todo: + Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md + History: +*) +unit Keyman.System.UpdateStateMachine; + +interface + +uses + System.Classes, + System.SysUtils, + System.UITypes, + System.IOUtils, + System.Types, + Vcl.Forms, + TypInfo, + KeymanPaths, + utilkmshell, + + httpuploader, + Keyman.System.UpdateCheckResponse, + Keyman.System.ExecuteHistory, + UfrmDownloadProgress; + +type + EUpdateStateMachine = class(Exception); + + TUpdateStateMachineResult = (oucUnknown, oucShutDown, oucSuccess, oucNoUpdates, oucUpdatesAvailable, oucFailure, oucOffline); + + TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, usInstalling, usRetry, usPostInstall); + + { Keyboard Package Params } + TUpdateStateMachineParamsPackage = record + ID: string; + NewID: string; + Description: string; + OldVersion, NewVersion: string; + DownloadURL: string; + SavePath: string; + FileName: string; + DownloadSize: Integer; + Install: Boolean; + end; + { Main Keyman Program } + TUpdateStateMachineParamsKeyman = record + OldVersion, NewVersion: string; + DownloadURL: string; + SavePath: string; + FileName: string; + DownloadSize: Integer; + Install: Boolean; + end; + + TUpdateStateMachineParams = record + Keyman: TUpdateStateMachineParamsKeyman; + Packages: array of TUpdateStateMachineParamsPackage; + Result: TUpdateStateMachineResult; + end; + + TUpdateStateMachineDownloadParams = record + Owner: TfrmDownloadProgress; + TotalSize: Integer; + TotalDownloads: Integer; + StartPosition: Integer; + end; + + // Forward declaration + TUpdateStateMachine = class; + { State Classes Update } + + TStateClass = class of TState; + + TState = class abstract + private + bucStateContext: TUpdateStateMachine; + procedure ChangeState(newState: TStateClass); + + public + constructor Create(Context: TUpdateStateMachine); + procedure Enter; virtual; abstract; + procedure Exit; virtual; abstract; + procedure HandleCheck; virtual; abstract; + procedure HandleDownload; virtual; abstract; + function HandleKmShell : Integer; virtual; abstract; + procedure HandleInstall; virtual; abstract; + procedure HandleMSIInstallComplete; virtual; abstract; + procedure HandleAbort; virtual; abstract; + procedure HandleInstallNow; virtual; abstract; + + // For convenience + function StateName: string; virtual; abstract; + + end; + + // Derived classes for each state + IdleState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + UpdateAvailableState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + DownloadingState = class(TState) + private + + function DownloadUpdatesBackground: Boolean; + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + WaitingRestartState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + InstallingState = class(TState) + private + procedure DoInstallKeyman; overload; + function DoInstallKeyman(SavePath: string) : Boolean; overload; + { + Installs the Keyman file using either msiexec.exe or the setup launched in + a separate shell. + + @params Package The package to be installed. + + @returns True if the installation is successful, False otherwise. + } + function DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + RetryState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + PostInstallState = class(TState) + public + procedure Enter; override; + procedure Exit; override; + procedure HandleCheck; override; + procedure HandleDownload; override; + function HandleKmShell : Integer; override; + procedure HandleInstall; override; + procedure HandleMSIInstallComplete; override; + procedure HandleAbort; override; + procedure HandleInstallNow; override; + function StateName: string; override; + end; + + { This class also controls the state flow see } + TUpdateStateMachine = class + private + FForce: Boolean; + FAuto: Boolean; + FParams: TUpdateStateMachineParams; + + FErrorMessage: string; + + DownloadTempPath: string; + + FShowErrors: Boolean; + + + + FDownload: TUpdateStateMachineDownloadParams; + + CurrentState: TState; + // State object for performance (could lazy create?) + FIdle: IdleState; + FUpdateAvailable: UpdateAvailableState; + FDownloading: DownloadingState; + FWaitingRestart: WaitingRestartState; + FInstalling: InstallingState; + FRetry: RetryState; + FPostInstall: PostInstallState; + function GetState: TStateClass; + procedure SetState(const Value: TStateClass); + procedure SetStateOnly(const Value: TStateClass); + function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; + + procedure ShutDown; + + { + SavePackageUpgradesToDownloadTempPath saves any new package IDs to a + single file in the download tempPath. This procedure saves the IDs of any + new packages to a file named "upgrade_packages.inf" in the download + tempPath. + } + procedure SavePackageUpgradesToDownloadTempPath; + function checkUpdateSchedule : Boolean; + + function SetRegistryState (Update : TUpdateState): Boolean; + function SetRegistryInstallMode (InstallMode : Boolean): Boolean; + + protected + property State: TStateClass read GetState write SetState; + + public + constructor Create(AForce: Boolean); + destructor Destroy; override; + + procedure HandleCheck; + function HandleKmShell : Integer; + procedure HandleDownload; + procedure HandleInstall; + procedure HandleMSIInstallComplete; + procedure HandleAbort; + procedure HandleInstallNow; + function CurrentStateName: string; + + property ShowErrors: Boolean read FShowErrors write FShowErrors; + function CheckRegistryState : TUpdateState; + function CheckRegistryInstallMode : Boolean; + + end; + + IOnlineUpdateSharedData = interface + ['{7442A323-C1E3-404B-BEEA-5B24A52BBB0E}'] + function Params: TUpdateStateMachineParams; + end; + + TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) + private + FParams: TUpdateStateMachineParams; + public + constructor Create(AParams: TUpdateStateMachineParams); + function Params: TUpdateStateMachineParams; + end; + +implementation + +uses + Winapi.Shlobj, + System.WideStrUtils, + Vcl.Dialogs, + Winapi.ShellApi, + Winapi.Windows, + Winapi.WinINet, + + GlobalProxySettings, + KLog, + keymanapi_TLB, + KeymanVersion, + kmint, + ErrorControlledRegistry, + RegistryKeys, + Upload_Settings, + utildir, + utilexecute, + OnlineUpdateCheckMessages, // todo create own messages + UfrmOnlineUpdateIcon, + UfrmOnlineUpdateNewVersion, + utilsystem, + utiluac, + versioninfo, + Keyman.System.RemoteUpdateCheck, + Keyman.System.DownloadUpdate; + +const + SPackageUpgradeFilename = 'upgrade_packages.inf'; + kmShellContinue = 0; + kmShellExit = 1; + +{ TUpdateStateMachine } + +constructor TUpdateStateMachine.Create(AForce : Boolean); +var TSerailsedState : TUpdateState; +begin + inherited Create; + + + FShowErrors := True; + FParams.Result := oucUnknown; + + FForce := AForce; + FAuto := True; // Default to automatically check, download, and install + FIdle := IdleState.Create(Self); + FUpdateAvailable := UpdateAvailableState.Create(Self); + FDownloading := DownloadingState.Create(Self); + FWaitingRestart := WaitingRestartState.Create(Self); + FInstalling := InstallingState.Create(Self); + FRetry := RetryState.Create(Self); + FPostInstall := PostInstallState.Create(Self); + // Check the Registry setting. + SetStateOnly(ConvertEnumState(CheckRegistryState)); + KL.Log('TUpdateStateMachine.Create'); +end; + +destructor TUpdateStateMachine.Destroy; +begin + if (FErrorMessage <> '') and FShowErrors then + KL.Log(FErrorMessage); + + if FParams.Result = oucShutDown then + ShutDown; + + FIdle.Free; + FUpdateAvailable.Free; + FDownloading.Free; + FWaitingRestart.Free; + FInstalling.Free; + FRetry.Free; + FPostInstall.Free; + + KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); + KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + + inherited Destroy; +end; + + +procedure TUpdateStateMachine.SavePackageUpgradesToDownloadTempPath; +var + i: Integer; +begin + with TStringList.Create do + try + for i := 0 to High(FParams.Packages) do + if FParams.Packages[i].NewID <> '' then + Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); + if Count > 0 then + SaveToFile(DownloadTempPath + SPackageUpgradeFileName); + finally + Free; + end; +end; + +procedure TUpdateStateMachine.ShutDown; +begin + if Assigned(Application) then + Application.Terminate; +end; + + +{ TOnlineUpdateSharedData } + +constructor TOnlineUpdateSharedData.Create(AParams: TUpdateStateMachineParams); +begin + inherited Create; + FParams := AParams; +end; + +function TOnlineUpdateSharedData.Params: TUpdateStateMachineParams; +begin + Result := FParams; +end; + + +function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; +var + UpdateStr : string; +begin + + Result := False; + with TRegistryErrorControlled.Create do + try + RootKey := HKEY_LOCAL_MACHINE; + KL.Log('SetRegistryState State Entry'); + if OpenKey(SRegKey_KeymanEngine_LM, True) then + begin + UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); + WriteString(SRegValue_Update_State, UpdateStr); + KL.Log('SetRegistryState State is:[' + UpdateStr + ']'); + end; + Result := True; + finally + Free; + end; + +end; + + +function TUpdateStateMachine.CheckRegistryState : TUpdateState; // I2329 +var + UpdateState : TUpdateState; + +begin + // We will use a registry flag to maintain the state of the background update + + UpdateState := usIdle; // do we need a unknown state ? + // check the registry value + with TRegistryErrorControlled.Create do // I2890 + try + RootKey := HKEY_LOCAL_MACHINE; + if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Update_State) then + begin + UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), ReadString(SRegValue_Update_State))); + KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); + end + else + begin + UpdateState := usIdle; // do we need a unknown state ? + KL.Log('CheckRegistryState State reg value not found default:[' + ReadString(SRegValue_Update_State) + ']'); + end + finally + Free; + end; + Result := UpdateState; +end; + +function TUpdateStateMachine.SetRegistryInstallMode (InstallMode : Boolean): Boolean; +var + InstallModeStr : string; +begin + + Result := False; + with TRegistryErrorControlled.Create do + try + RootKey := HKEY_LOCAL_MACHINE; + KL.Log('SetRegistryState State Entry'); + if OpenKey(SRegKey_KeymanEngine_LM, True) then + begin + InstallModeStr := BoolToStr(InstallMode, True); + WriteString(SRegValue_Install_Mode, InstallModeStr); + KL.Log('SetRegistryInstallMode is:[' + InstallModeStr + ']'); + end; + Result := True; + finally + Free; + end; + +end; + +function TUpdateStateMachine.CheckRegistryInstallMode : Boolean; +var + InstallMode : Boolean; + +begin + // We will use a registry flag to maintain the install mode background/foreground + + InstallMode := False; + // check the registry value + with TRegistryErrorControlled.Create do // I2890 + try + RootKey := HKEY_LOCAL_MACHINE; + if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Install_Mode) then + begin + InstallMode := StrToBool(ReadString(SRegValue_Install_Mode)); + KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); + end + else + begin + InstallMode := False; // default to background + KL.Log('CheckRegistryInstallMode reg value not found default:[ False ]'); + end + finally + Free; + end; + Result := InstallMode; +end; + + +function TUpdateStateMachine.CheckUpdateSchedule: Boolean; +begin + try + Result := False; + with TRegistryErrorControlled.Create do + try + if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + begin + if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then + begin + Result := False; + Exit; + end; + if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then + begin + Result := False; + Exit; + end; + // Else Time to check for updates + Result := True; + end; + finally + Free; + end; + except + { we will not run the check if an error occurs reading the settings } + on E:Exception do + begin + Result := False; + FErrorMessage := E.Message; + Exit; + end; + end; +end; + +function TUpdateStateMachine.GetState: TStateClass; +begin + Result := TStateClass(CurrentState.ClassType); +end; + +procedure TUpdateStateMachine.SetState(const Value: TStateClass); +begin + if Assigned(CurrentState) then + begin + CurrentState.Exit; + end; + + SetStateOnly(Value); + + if Assigned(CurrentState) then + begin + CurrentState.Enter; + end + else + begin + // TODO: #10210 Error log for Unable to set state for Value + end; + +end; + +procedure TUpdateStateMachine.SetStateOnly(const Value: TStateClass); +begin + if Value = IdleState then + begin + CurrentState := FIdle; + end + else if Value = UpdateAvailableState then + begin + CurrentState := FUpdateAvailable; + end + else if Value = DownloadingState then + begin + CurrentState := FDownloading; + end + else if Value = WaitingRestartState then + begin + CurrentState := FWaitingRestart; + end + else if Value = InstallingState then + begin + CurrentState := FInstalling; + end + else if Value = RetryState then + begin + CurrentState := FRetry; + end + else if Value = PostInstallState then + begin + CurrentState := FPostInstall; + end; +end; + +function TUpdateStateMachine.ConvertEnumState(const TEnumState: TUpdateState) : TStateClass; +begin + case TEnumState of + usIdle: Result := IdleState; + usUpdateAvailable: Result := UpdateAvailableState; + usDownloading: Result := DownloadingState; + usWaitingRestart: Result := WaitingRestartState; + usInstalling: Result := InstallingState; + usRetry: Result := RetryState; + usPostInstall: Result := PostInstallState; + else + // TODO: #10210 Log error unknown state setting to idle + Result := IdleState; + end; +end; + +procedure TUpdateStateMachine.HandleCheck; +begin + CurrentState.HandleCheck; +end; + +function TUpdateStateMachine.HandleKmShell; +begin + Result := CurrentState.HandleKmShell; +end; + +procedure TUpdateStateMachine.HandleDownload; +begin + CurrentState.HandleDownload; +end; + +procedure TUpdateStateMachine.HandleInstall; +begin + CurrentState.HandleInstall; +end; + +procedure TUpdateStateMachine.HandleMSIInstallComplete; +begin + CurrentState.HandleMSIInstallComplete; +end; + +procedure TUpdateStateMachine.HandleAbort; +begin + CurrentState.HandleAbort; +end; + +procedure TUpdateStateMachine.HandleInstallNow; +begin + CurrentState.HandleInstallNow; +end; + +function TUpdateStateMachine.CurrentStateName: string; +begin + Result := CurrentState.StateName; +end; + + + +{ State Class Memebers } +constructor TState.Create(Context: TUpdateStateMachine); +begin + bucStateContext := Context; +end; + +procedure TState.ChangeState(NewState: TStateClass); +begin + bucStateContext.State := NewState; +end; + + +{ IdleState } + +procedure IdleState.Enter; +begin + // Enter UpdateAvailableState + bucStateContext.SetRegistryState(usIdle); +end; + +procedure IdleState.Exit; +begin + +end; + +procedure IdleState.HandleCheck; +var + CheckForUpdates: TRemoteUpdateCheck; + Result : TRemoteUpdateCheckResult; +begin + + { Make a HTTP request out and see if updates are available for now do + this all in the Idle HandleCheck message. But could be broken into an + seperate state of WaitngCheck RESP } + { if Response not OK stay in the idle state and return } + + + // should be false but forcing check for testing + //CheckForUpdates := TRemoteUpdateCheck.Create(True); + CheckForUpdates := TRemoteUpdateCheck.Create(False); + try + Result:= CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; + + { Response OK and Update is available } + if Result = wucSuccess then + begin + ChangeState(UpdateAvailableState); + end; + // else staty in idle state +end; + +procedure IdleState.HandleDownload; +begin + +end; + +function IdleState.HandleKmShell; +begin + + Result := kmShellContinue; +end; + +procedure IdleState.HandleInstall; +begin + +end; + +procedure IdleState.HandleMSIInstallComplete; +begin + +end; + +procedure IdleState.HandleAbort; +begin + +end; + +procedure IdleState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + bucStateContext.CurrentState.HandleCheck; +end; + +function IdleState.StateName; +begin + + Result := 'IdleState'; +end; + +{ UpdateAvailableState } + +procedure UpdateAvailableState.Enter; +begin + // Enter UpdateAvailableState + bucStateContext.SetRegistryState(usUpdateAvailable); + if bucStateContext.FAuto then + begin + bucStateContext.CurrentState.HandleDownload; + end; +end; + +procedure UpdateAvailableState.Exit; +begin + // Exit UpdateAvailableState +end; + +procedure UpdateAvailableState.HandleCheck; +begin + +end; + +procedure UpdateAvailableState.HandleDownload; +begin + ChangeState(DownloadingState); +end; + +function UpdateAvailableState.HandleKmShell; +begin + if bucStateContext.FAuto then + begin + bucStateContext.CurrentState.HandleDownload ; + end; + Result := kmShellContinue; +end; + +procedure UpdateAvailableState.HandleInstall; +begin + +end; + +procedure UpdateAvailableState.HandleMSIInstallComplete; +begin + +end; + +procedure UpdateAvailableState.HandleAbort; +begin + +end; + +procedure UpdateAvailableState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + ChangeState(DownloadingState); +end; + +function UpdateAvailableState.StateName; +begin + + Result := 'UpdateAvailableState'; +end; + +{ DownloadingState } + +procedure DownloadingState.Enter; +var DownloadResult : Boolean; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usDownloading); + DownloadResult := DownloadUpdatesBackground; + if DownloadResult then + begin + if HasKeymanRun then + ChangeState(WaitingRestartState) + else + ChangeState(InstallingState); + end + else + begin + ChangeState(RetryState); + end +end; + +procedure DownloadingState.Exit; +begin + // Exit DownloadingState +end; + +procedure DownloadingState.HandleCheck; +begin + +end; + +procedure DownloadingState.HandleDownload; +var DownloadResult : Boolean; +begin + // We are already downloading do nothing +end; + +function DownloadingState.HandleKmShell; +var DownloadResult : Boolean; +begin + DownloadResult := DownloadUpdatesBackground; + // TODO check if keyman is running then send to Waiting Restart + if DownloadResult then + begin + if HasKeymanRun then + begin + ChangeState(WaitingRestartState); + Result := kmShellContinue; + end + else + begin + ChangeState(InstallingState); + Result := kmShellExit; + end; + end + else + begin + ChangeState(RetryState); + Result := kmShellContinue; + end; + +end; + +procedure DownloadingState.HandleInstall; +begin + ChangeState(InstallingState); +end; + +procedure DownloadingState.HandleMSIInstallComplete; +begin + +end; + +procedure DownloadingState.HandleAbort; +begin +end; + +procedure DownloadingState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // Continue downloading +end; + +function DownloadingState.StateName; +begin + Result := 'DownloadingState'; +end; + + +function DownloadingState.DownloadUpdatesBackground: Boolean; +var + i: Integer; + DownloadBackGroundSavePath : String; + DownloadResult : Boolean; + DownloadUpdate: TDownloadUpdate; +begin + DownloadUpdate := TDownloadUpdate.Create; + try + DownloadResult := DownloadUpdate.DownloadUpdates; + KL.Log('TUpdateStateMachine.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); + Result := DownloadResult; +// #TODO: #10210 workout when we need to refresh kmcom keyboards + +// if Result in [ wucSuccess] then +// begin +// kmcom.Keyboards.Refresh; +// kmcom.Keyboards.Apply; +// kmcom.Packages.Refresh; +// end; + + finally + DownloadUpdate.Free; + end; +end; + +{ WaitingRestartState } + +procedure WaitingRestartState.Enter; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usWaitingRestart); +end; + +procedure WaitingRestartState.Exit; +begin + // Exit DownloadingState +end; + +procedure WaitingRestartState.HandleCheck; +begin + +end; + +procedure WaitingRestartState.HandleDownload; +begin + +end; + +function WaitingRestartState.HandleKmShell; +var + SavedPath : String; + Filenames : TStringDynArray; +begin + KL.Log('WaitingRestartState.HandleKmShell Enter'); + // Still can't go if keyman has run + if HasKeymanRun then + begin + KL.Log('WaitingRestartState.HandleKmShell Keyman Has Run'); + Result := kmShellExit; + // Exit; // Exit is not wokring for some reason. + // this else is only here because the exit is not working. + end + else + begin + // Check downloaded cache if available then + SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + GetFileNamesInDirectory(SavedPath, FileNames); + if Length(FileNames) = 0 then + begin + KL.Log('WaitingRestartState.HandleKmShell No Files in Download Cache'); + // Return to Idle state and check for Updates state + ChangeState(IdleState); + bucStateContext.CurrentState.HandleCheck; + Result := kmShellExit; + // Exit; // again exit was not working + end + else + begin + KL.Log('WaitingRestartState.HandleKmShell is good to install'); + ChangeState(InstallingState); + Result := kmShellExit; + end; + end; +end; + +procedure WaitingRestartState.HandleInstall; +begin + +end; + +procedure WaitingRestartState.HandleMSIInstallComplete; +begin + +end; + +procedure WaitingRestartState.HandleAbort; +begin + +end; + +procedure WaitingRestartState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // Notify User to install + ChangeState(InstallingState); + +end; + +function WaitingRestartState.StateName; +begin + + Result := 'WaitingRestartState'; +end; + +{ InstallingState } + +function InstallingState.DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; +var + FPackage: IKeymanPackageFile2; +begin + Result := True; + + FPackage := kmcom.Packages.GetPackageFromFile(Package.SavePath) as IKeymanPackageFile2; + FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact + FPackage := nil; + + kmcom.Refresh; + kmcom.Apply; + System.SysUtils.DeleteFile(Package.SavePath); +end; + +procedure InstallingState.DoInstallKeyman; +var + s: string; + FResult: Boolean; +begin + FResult := False; + s := LowerCase(ExtractFileExt(bucStateContext.FParams.Keyman.SavePath)); + if s = '.msi' then + FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+bucStateContext.FParams.Keyman.SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 + else if s = '.exe' then + FResult := TUtilExecute.Shell(0, bucStateContext.FParams.Keyman.SavePath, '', '-au') // I3349 + else + Exit; + if not FResult then + ShowMessage(SysErrorMessage(GetLastError)); +end; + +function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; +var + s: string; + FResult: Boolean; +begin + s := LowerCase(ExtractFileExt(SavePath)); + if s = '.msi' then + FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 + else if s = '.exe' then + begin + KL.Log('TUpdateStateMachine.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); + FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 + end + else + FResult := False; + + if not FResult then + begin + KL.Log('TUpdateStateMachine.InstallingState.DoInstall: Result = '+IntToStr(Ord(FResult))); + // Log messageShowMessage(SysErrorMessage(GetLastError)); + end; + + Result := FResult; +end; + +procedure InstallingState.Enter; +var + SavePath: String; + fileExt : String; + fileName: String; + fileNames: TStringDynArray; +begin + bucStateContext.SetRegistryState(usInstalling); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + + GetFileNamesInDirectory(SavePath, fileNames); + // for now we only want the exe although excute install can + // handle msi + for fileName in fileNames do + begin + fileExt := LowerCase(ExtractFileExt(fileName)); + if fileExt = '.exe' then + break; + end; + + if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then + begin + KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall OK'); + end + else + begin + // TODO: #10210 clean failed download + // TODO: #10210 Do we do a retry on install? probably not + KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); + ChangeState(IdleState); + end +end; + +procedure InstallingState.Exit; +begin + // Exit DownloadingState +end; + +procedure InstallingState.HandleCheck; +begin + +end; + +procedure InstallingState.HandleDownload; +begin + +end; + +function InstallingState.HandleKmShell; +begin + // Result = exit straight away as we are installing (MSI installer) + // need to just do a no-op keyman will it maybe using kmshell to install + // packages. + Result := kmShellContinue; +end; + +procedure InstallingState.HandleInstall; +begin + +end; + +procedure InstallingState.HandleMSIInstallComplete; +begin + +end; + +procedure InstallingState.HandleAbort; +begin + ChangeState(IdleState); +end; + +procedure InstallingState.HandleInstallNow; +begin + // Do Nothing. Need the UI to let user know installation in progress OR +end; + +function InstallingState.StateName; +begin + + Result := 'InstallingState'; +end; + +{ RetryState } + +procedure RetryState.Enter; +begin + // Enter DownloadingState + bucStateContext.SetRegistryState(usRetry); +end; + +procedure RetryState.Exit; +begin + // Exit DownloadingState +end; + +procedure RetryState.HandleCheck; +begin + +end; + +procedure RetryState.HandleDownload; +begin + +end; + +function RetryState.HandleKmShell; +begin + // #TODO: #10210 Implement retry + Result := kmShellContinue +end; + +procedure RetryState.HandleInstall; +begin + +end; + +procedure RetryState.HandleMSIInstallComplete; +begin + +end; + +procedure RetryState.HandleAbort; +begin + +end; + +procedure RetryState.HandleInstallNow; +begin + bucStateContext.SetRegistryInstallMode(True); + // TODO: #10038 handle retry counts + ChangeState(InstallingState); +end; + +function RetryState.StateName; +begin + + Result := 'RetryState'; +end; + +{ PostInstallState } + +procedure PostInstallState.Enter; +begin + // Enter downloading state + bucStateContext.SetRegistryState(usPostInstall); +end; + +procedure PostInstallState.Exit; +begin + // Exit downloading state +end; + +procedure PostInstallState.HandleCheck; +begin + // Handle Check +end; + +procedure PostInstallState.HandleDownload; +begin + // Handle Download +end; + +function PostInstallState.HandleKmShell; +begin + // TODO: #10210 have a counter if we get called in this state + // too many time abort. + HandleMSIInstallComplete; + Result := kmShellContinue; +end; + +procedure PostInstallState.HandleInstall; +begin + // Handle Install +end; + +procedure PostInstallState.HandleMSIInstallComplete; +var SavePath: string; + FileName: String; + FileNames: TStringDynArray; +begin + KL.Log('PostInstallState.HandleMSIInstallComplete'); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + KL.Log('PostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); + + GetFileNamesInDirectory(SavePath, FileNames); + for FileName in FileNames do + begin + System.SysUtils.DeleteFile(FileName); + end; + ChangeState(IdleState); +end; + +procedure PostInstallState.HandleAbort; +begin + // Handle Abort +end; + +procedure PostInstallState.HandleInstallNow; +begin + // Do nothing as files will be cleaned via HandleKmShell +end; + +function PostInstallState.StateName; +begin + + Result := 'PostInstallState'; +end; + + + +end. diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 763cf11f99..284695bfd4 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -144,7 +144,7 @@ uses UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, - Keyman.System.Update, + Keyman.System.UpdateStateMachine, KeyboardTIPCheck, @@ -387,7 +387,7 @@ var kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 - BUpdateSM : TBackgroundUpdate; + BUpdateSM : TUpdateStateMachine; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -436,7 +436,7 @@ begin end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - BUpdateSM := TBackgroundUpdate.Create(False); + BUpdateSM := TUpdateStateMachine.Create(False); try if (FMode = fmBackgroundUpdateCheck) then begin From d6258e5135d76d8501ee20b389d30a2dd20884ed Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:52:35 +1000 Subject: [PATCH 033/124] feat(windows): add checkconfig for updated download --- .../main/Keyman.System.UpdateStateMachine.pas | 105 +++++++++++++----- ...eyman.System.Install.EnginePostInstall.pas | 2 +- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 9ae3df3195..6d6eb9b426 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -297,7 +297,8 @@ type constructor Create(AParams: TUpdateStateMachineParams); function Params: TUpdateStateMachineParams; end; - + // Private Utility functions + function ConfigCheckContinue: Boolean; implementation uses @@ -335,7 +336,7 @@ const { TUpdateStateMachine } constructor TUpdateStateMachine.Create(AForce : Boolean); -var TSerailsedState : TUpdateState; +// var TSerailsedState : TUpdateState; // TODO: Remove begin inherited Create; @@ -448,7 +449,6 @@ var begin // We will use a registry flag to maintain the state of the background update - UpdateState := usIdle; // do we need a unknown state ? // check the registry value with TRegistryErrorControlled.Create do // I2890 try @@ -702,15 +702,15 @@ var Result : TRemoteUpdateCheckResult; begin + { Make a HTTP request out and see if updates are available for now do this all in the Idle HandleCheck message. But could be broken into an seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - // should be false but forcing check for testing - //CheckForUpdates := TRemoteUpdateCheck.Create(True); - CheckForUpdates := TRemoteUpdateCheck.Create(False); + // If handle check event force check + CheckForUpdates := TRemoteUpdateCheck.Create(True); try Result:= CheckForUpdates.Run; finally @@ -731,8 +731,26 @@ begin end; function IdleState.HandleKmShell; +var + CheckForUpdates: TRemoteUpdateCheck; + UpdateCheckResult : TRemoteUpdateCheckResult; +const CheckPeriod: Integer = 7; // Days between checking for updates begin - + // Check if auto updates enable and if scheduled time has expired + if ConfigCheckContinue then + begin + CheckForUpdates := TRemoteUpdateCheck.Create(True); + try + UpdateCheckResult:= CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; + { Response OK and Update is available } + if UpdateCheckResult = wucSuccess then + begin + ChangeState(UpdateAvailableState); + end; + end; Result := kmShellContinue; end; @@ -755,6 +773,7 @@ procedure IdleState.HandleInstallNow; begin bucStateContext.SetRegistryInstallMode(True); bucStateContext.CurrentState.HandleCheck; + // TODO: How do we notify the command line no update available end; function IdleState.StateName; @@ -771,7 +790,7 @@ begin bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAuto then begin - bucStateContext.CurrentState.HandleDownload; + ChangeState(DownloadingState); end; end; @@ -787,14 +806,14 @@ end; procedure UpdateAvailableState.HandleDownload; begin - ChangeState(DownloadingState); + end; function UpdateAvailableState.HandleKmShell; begin if bucStateContext.FAuto then begin - bucStateContext.CurrentState.HandleDownload ; + ChangeState(DownloadingState);; end; Result := kmShellContinue; end; @@ -822,29 +841,22 @@ end; function UpdateAvailableState.StateName; begin - Result := 'UpdateAvailableState'; end; { DownloadingState } procedure DownloadingState.Enter; -var DownloadResult : Boolean; +var DownloadResult, FResult : Boolean; +RootPath: string; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); - DownloadResult := DownloadUpdatesBackground; - if DownloadResult then - begin - if HasKeymanRun then - ChangeState(WaitingRestartState) - else - ChangeState(InstallingState); - end - else - begin - ChangeState(RetryState); - end + // call seperate process + RootPath := ExtractFilePath(ParamStr(0)); + FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), ''); + if not FResult then + KL.Log('TrmfMain: Executing KMshell for download updated Failed'); end; procedure DownloadingState.Exit; @@ -917,7 +929,6 @@ end; function DownloadingState.DownloadUpdatesBackground: Boolean; var - i: Integer; DownloadBackGroundSavePath : String; DownloadResult : Boolean; DownloadUpdate: TDownloadUpdate; @@ -988,7 +999,7 @@ begin KL.Log('WaitingRestartState.HandleKmShell No Files in Download Cache'); // Return to Idle state and check for Updates state ChangeState(IdleState); - bucStateContext.CurrentState.HandleCheck; + bucStateContext.CurrentState.HandleCheck; // TODO no event here Result := kmShellExit; // Exit; // again exit was not working end @@ -1075,6 +1086,8 @@ begin else if s = '.exe' then begin KL.Log('TUpdateStateMachine.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); + // switch -au for auto update in silent mode. + // We will need to add the pop up that says install update now yes/no FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 end else @@ -1297,6 +1310,46 @@ begin Result := 'PostInstallState'; end; +// Private Functions: +function ConfigCheckContinue: Boolean; +var + registry: TRegistryErrorControlled; +begin +{ Verify that it has been at least CheckPeriod days since last update check } + Result := False; + try + registry := TRegistryErrorControlled.Create; // I2890 + try + if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + begin + if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then + begin + Result := False; + Exit; + end; + if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + begin + Result := True; + end + else + begin + Result := False; + end; + Exit; + end; + finally + registry.Free; + end; + except + { we will not run the check if an error occurs reading the settings } + on E:Exception do + begin + Result := False; + LogMessage(E.Message); + Exit; + end; + end; +end; end. diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 5e61ff1f4d..77fe071786 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -35,7 +35,7 @@ var begin Result := False; - UpdateStr := 'usWaitingPostInstall'; + UpdateStr := 'usPostInstall'; //KL.Log('SetBackgroundState State Entry'); if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_LM), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then begin From 9f72febff844ef9f4c3c9830c72b306365e35073 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:59:21 +1000 Subject: [PATCH 034/124] feat(windows): merge conflicts wip --- VERSION.md | 2 +- common/windows/delphi/general/klog.pas | 2 +- core/tests/unit/kmx/fixtures/meson.build | 4 ++-- core/tests/unit/meson.build | 6 +++--- windows/src/desktop/insthelp/insthelp.dpr | 7 ++++++- windows/src/desktop/insthelp/insthelp.dproj | 5 +++++ windows/src/desktop/kmshell/kmshell.dproj | 13 +++++++------ windows/src/desktop/kmshell/kmshell.res | Bin 7036 -> 7036 bytes .../kmshell/util/UfrmDownloadProgress.pas | 14 +++++++------- windows/src/desktop/kmshell/xml/strings.xml | 6 ++++++ 10 files changed, 38 insertions(+), 21 deletions(-) diff --git a/VERSION.md b/VERSION.md index 70957c1c68..a9a1646b48 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.73 \ No newline at end of file +18.0.70 diff --git a/common/windows/delphi/general/klog.pas b/common/windows/delphi/general/klog.pas index 2787f8b347..2756c63893 100644 --- a/common/windows/delphi/general/klog.pas +++ b/common/windows/delphi/general/klog.pas @@ -23,7 +23,7 @@ unit klog; // I3309 interface -{DEFINE KLOGGING} +{$DEFINE KLOGGING} {$IFDEF KLOGGING} uses diff --git a/core/tests/unit/kmx/fixtures/meson.build b/core/tests/unit/kmx/fixtures/meson.build index e40c30ad46..711bae6f80 100644 --- a/core/tests/unit/kmx/fixtures/meson.build +++ b/core/tests/unit/kmx/fixtures/meson.build @@ -2,5 +2,5 @@ if node.found() # Note: if node is not available, we cannot build the keyboards; build.sh # emits a warning that the 'ldml' keyboard tests will be skipped; that # includes these tests for now - subdir('binary') -endif \ No newline at end of file + #subdir('binary') +endif diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build index ee141cc12d..4b0bb489e5 100644 --- a/core/tests/unit/meson.build +++ b/core/tests/unit/meson.build @@ -10,6 +10,6 @@ hextobin_cmd = [node, hextobin_root] subdir('json') subdir('utftest') -subdir('kmnkbd') -subdir('kmx') -subdir('ldml') +#subdir('kmnkbd') +#subdir('kmx') +#subdir('ldml') diff --git a/windows/src/desktop/insthelp/insthelp.dpr b/windows/src/desktop/insthelp/insthelp.dpr index ab0e47ff7d..905e4ac8eb 100644 --- a/windows/src/desktop/insthelp/insthelp.dpr +++ b/windows/src/desktop/insthelp/insthelp.dpr @@ -8,7 +8,12 @@ uses KeymanVersion in '..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', Keyman.System.InstHelp.KeymanStartTaskUninstall in 'Keyman.System.InstHelp.KeymanStartTaskUninstall.pas', TaskScheduler_TLB in '..\..\global\delphi\winapi\TaskScheduler_TLB.pas', - UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas'; + ErrorControlledRegistry in '..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', + UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas', + DebugPaths in '..\..\..\..\common\windows\delphi\general\DebugPaths.pas', + VersionInfo in '..\..\..\..\common\windows\delphi\general\VersionInfo.pas', + Unicode in '..\..\..\..\common\windows\delphi\general\Unicode.pas', + KeymanPaths in '..\..\..\..\common\windows\delphi\general\KeymanPaths.pas'; {$R version.res} {-R manifest.res} diff --git a/windows/src/desktop/insthelp/insthelp.dproj b/windows/src/desktop/insthelp/insthelp.dproj index 45913bd470..cfb0ad37fc 100644 --- a/windows/src/desktop/insthelp/insthelp.dproj +++ b/windows/src/desktop/insthelp/insthelp.dproj @@ -98,6 +98,11 @@ + + + + + Cfg_2 Base diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index c999a12d42..79061ffe4a 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -425,12 +425,6 @@ False - - - kmshell.rsm - true - - kmshell.exe @@ -443,6 +437,12 @@ true + + + kmshell.exe + true + + 1 @@ -1238,6 +1238,7 @@ + False 12 diff --git a/windows/src/desktop/kmshell/kmshell.res b/windows/src/desktop/kmshell/kmshell.res index cfcbd309f137d6a5f5221aba48a1c759c0d56682..5154f0aa063b63d100be466835ba2a09bbe223bd 100644 GIT binary patch delta 15 Wcmexk_Qz~O3CqPx?n)agSfl|y;0D0} delta 15 Xcmexk_Qz~O3Cr{S4;406ut);{LY)U( diff --git a/windows/src/desktop/kmshell/util/UfrmDownloadProgress.pas b/windows/src/desktop/kmshell/util/UfrmDownloadProgress.pas index 2edcfbbcf7..9b5f779c8f 100644 --- a/windows/src/desktop/kmshell/util/UfrmDownloadProgress.pas +++ b/windows/src/desktop/kmshell/util/UfrmDownloadProgress.pas @@ -1,18 +1,18 @@ (* Name: UfrmDownloadProgress Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 4 Dec 2006 Modified Date: 18 May 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 04 Dec 2006 - mcdurdin - Initial version 05 Dec 2006 - mcdurdin - Localize caption 15 Jan 2007 - mcdurdin - Use font from locale.xml diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index b4e5e83350..487a818ddf 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -571,6 +571,12 @@ Diagnostics + + + + Check for Updates + + From b0cdcabaaa192a1acc8e439146b71c36354d0c48 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:29:16 +1000 Subject: [PATCH 035/124] feat(windows): correct error checking for has keyman run --- .../windows/delphi/general/Keyman.System.ExecuteHistory.pas | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas index 2ed824ec0c..e146f8fa69 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -22,7 +22,7 @@ begin atom := GlobalFindAtom(AtomName); if atom = 0 then begin - if GetLastError <> ERROR_SUCCESS then + if GetLastError <> ERROR_FILE_NOT_FOUND then RaiseLastOSError; atom := GlobalAddAtom(AtomName); KL.Log('RecordKeymanStarted: True'); @@ -52,7 +52,11 @@ begin Result := True; end else + begin + KL.Log('HasKeymanRun: Keyman Has Run'); Result := False; + end; + except on E: Exception do KL.log(E.ClassName + ': ' + E.Message); From c86ecfd6f3716079fb727f9ff09babbca00ab1da Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:51:58 +1000 Subject: [PATCH 036/124] feat(windows): use CU registry key for statemachine state Use Current User registry key for state machine state. Also remove events that are no longer used. Like start download. --- .../kmshell/main/Keyman.System.Update.pas | 1302 ----------------- .../main/Keyman.System.UpdateStateMachine.pas | 209 +-- ...eyman.System.Install.EnginePostInstall.pas | 2 +- 3 files changed, 63 insertions(+), 1450 deletions(-) delete mode 100644 windows/src/desktop/kmshell/main/Keyman.System.Update.pas diff --git a/windows/src/desktop/kmshell/main/Keyman.System.Update.pas b/windows/src/desktop/kmshell/main/Keyman.System.Update.pas deleted file mode 100644 index fb87834783..0000000000 --- a/windows/src/desktop/kmshell/main/Keyman.System.Update.pas +++ /dev/null @@ -1,1302 +0,0 @@ -(* - Name: BackgroundUpdate - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 2 Nov 2023 - - Modified Date: 2 Nov 2023 - Authors: rcruickshank - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md - History: -*) -unit Keyman.System.Update; - -interface - -uses - System.Classes, - System.SysUtils, - System.UITypes, - System.IOUtils, - System.Types, - Vcl.Forms, - TypInfo, - KeymanPaths, - utilkmshell, - - httpuploader, - Keyman.System.UpdateCheckResponse, - Keyman.System.ExecuteHistory, - UfrmDownloadProgress; - -type - EBackgroundUpdate = class(Exception); - - TBackgroundUpdateResult = (oucUnknown, oucShutDown, oucSuccess, oucNoUpdates, oucUpdatesAvailable, oucFailure, oucOffline); - - TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, usInstalling, usRetry, usWaitingPostInstall); - - { Keyboard Package Params } - TBackgroundUpdateParamsPackage = record - ID: string; - NewID: string; - Description: string; - OldVersion, NewVersion: string; - DownloadURL: string; - SavePath: string; - FileName: string; - DownloadSize: Integer; - Install: Boolean; - end; - { Main Keyman Program } - TBackgroundUpdateParamsKeyman = record - OldVersion, NewVersion: string; - DownloadURL: string; - SavePath: string; - FileName: string; - DownloadSize: Integer; - Install: Boolean; - end; - - TBackgroundUpdateParams = record - Keyman: TBackgroundUpdateParamsKeyman; - Packages: array of TBackgroundUpdateParamsPackage; - Result: TBackgroundUpdateResult; - end; - - TBackgroundUpdateDownloadParams = record - Owner: TfrmDownloadProgress; - TotalSize: Integer; - TotalDownloads: Integer; - StartPosition: Integer; - end; - - // Forward declaration - TBackgroundUpdate = class; - { State Classes Update } - - TStateClass = class of TState; - - TState = class abstract - private - bucStateContext: TBackgroundUpdate; - procedure ChangeState(newState: TStateClass); - - public - constructor Create(Context: TBackgroundUpdate); - procedure Enter; virtual; abstract; - procedure Exit; virtual; abstract; - procedure HandleCheck; virtual; abstract; - procedure HandleDownload; virtual; abstract; - function HandleKmShell : Integer; virtual; abstract; - procedure HandleInstall; virtual; abstract; - procedure HandleMSIInstallComplete; virtual; abstract; - procedure HandleAbort; virtual; abstract; - procedure HandleInstallNow; virtual; abstract; - - // For convenience - function StateName: string; virtual; abstract; - - end; - - // Derived classes for each state - IdleState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - UpdateAvailableState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - DownloadingState = class(TState) - private - - function DownloadUpdatesBackground: Boolean; - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - WaitingRestartState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - InstallingState = class(TState) - private - procedure DoInstallKeyman; overload; - function DoInstallKeyman(SavePath: string) : Boolean; overload; - { - Installs the Keyman file using either msiexec.exe or the setup launched in - a separate shell. - - @params Package The package to be installed. - - @returns True if the installation is successful, False otherwise. - } - function DoInstallPackage(Package: TBackgroundUpdateParamsPackage): Boolean; - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - RetryState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - WaitingPostInstallState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - procedure HandleDownload; override; - function HandleKmShell : Integer; override; - procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - function StateName: string; override; - end; - - { This class also controls the state flow see } - TBackgroundUpdate = class - private - FForce: Boolean; - FAuto: Boolean; - FParams: TBackgroundUpdateParams; - - FErrorMessage: string; - - DownloadTempPath: string; - - FShowErrors: Boolean; - - - - FDownload: TBackgroundUpdateDownloadParams; - - CurrentState: TState; - // State object for performance (could lazy create?) - FIdle: IdleState; - FUpdateAvailable: UpdateAvailableState; - FDownloading: DownloadingState; - FWaitingRestart: WaitingRestartState; - FInstalling: InstallingState; - FRetry: RetryState; - FWaitingPostInstall: WaitingPostInstallState; - function GetState: TStateClass; - procedure SetState(const Value: TStateClass); - procedure SetStateOnly(const Value: TStateClass); - function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; - - procedure ShutDown; - - { - SavePackageUpgradesToDownloadTempPath saves any new package IDs to a - single file in the download tempPath. This procedure saves the IDs of any - new packages to a file named "upgrade_packages.inf" in the download - tempPath. - } - procedure SavePackageUpgradesToDownloadTempPath; - function checkUpdateSchedule : Boolean; - - function SetRegistryState (Update : TUpdateState): Boolean; - function SetRegistryInstallMode (InstallMode : Boolean): Boolean; - - protected - property State: TStateClass read GetState write SetState; - - public - constructor Create(AForce: Boolean); - destructor Destroy; override; - - procedure HandleCheck; - function HandleKmShell : Integer; - procedure HandleDownload; - procedure HandleInstall; - procedure HandleMSIInstallComplete; - procedure HandleAbort; - procedure HandleInstallNow; - function CurrentStateName: string; - - property ShowErrors: Boolean read FShowErrors write FShowErrors; - function CheckRegistryState : TUpdateState; - function CheckRegistryInstallMode : Boolean; - - end; - - IOnlineUpdateSharedData = interface - ['{7442A323-C1E3-404B-BEEA-5B24A52BBB0E}'] - function Params: TBackgroundUpdateParams; - end; - - TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) - private - FParams: TBackgroundUpdateParams; - public - constructor Create(AParams: TBackgroundUpdateParams); - function Params: TBackgroundUpdateParams; - end; - -implementation - -uses - Winapi.Shlobj, - System.WideStrUtils, - Vcl.Dialogs, - Winapi.ShellApi, - Winapi.Windows, - Winapi.WinINet, - - GlobalProxySettings, - KLog, - keymanapi_TLB, - KeymanVersion, - kmint, - ErrorControlledRegistry, - RegistryKeys, - Upload_Settings, - utildir, - utilexecute, - OnlineUpdateCheckMessages, // todo create own messages - UfrmOnlineUpdateIcon, - UfrmOnlineUpdateNewVersion, - utilsystem, - utiluac, - versioninfo, - Keyman.System.RemoteUpdateCheck, - Keyman.System.DownloadUpdate; - -const - SPackageUpgradeFilename = 'upgrade_packages.inf'; - kmShellContinue = 0; - kmShellExit = 1; - -{ TBackgroundUpdate } - -constructor TBackgroundUpdate.Create(AForce : Boolean); -var TSerailsedState : TUpdateState; -begin - inherited Create; - - - FShowErrors := True; - FParams.Result := oucUnknown; - - FForce := AForce; - FAuto := True; // Default to automatically check, download, and install - FIdle := IdleState.Create(Self); - FUpdateAvailable := UpdateAvailableState.Create(Self); - FDownloading := DownloadingState.Create(Self); - FWaitingRestart := WaitingRestartState.Create(Self); - FInstalling := InstallingState.Create(Self); - FRetry := RetryState.Create(Self); - FWaitingPostInstall := WaitingPostInstallState.Create(Self); - // Check the Registry setting. - SetStateOnly(ConvertEnumState(CheckRegistryState)); - KL.Log('TBackgroundUpdate.Create'); -end; - -destructor TBackgroundUpdate.Destroy; -begin - if (FErrorMessage <> '') and FShowErrors then - KL.Log(FErrorMessage); - - if FParams.Result = oucShutDown then - ShutDown; - - FIdle.Free; - FUpdateAvailable.Free; - FDownloading.Free; - FWaitingRestart.Free; - FInstalling.Free; - FRetry.Free; - FWaitingPostInstall.Free; - - KL.Log('TBackgroundUpdate.Destroy: FErrorMessage = '+FErrorMessage); - KL.Log('TBackgroundUpdate.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); - - inherited Destroy; -end; - - -procedure TBackgroundUpdate.SavePackageUpgradesToDownloadTempPath; -var - i: Integer; -begin - with TStringList.Create do - try - for i := 0 to High(FParams.Packages) do - if FParams.Packages[i].NewID <> '' then - Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); - if Count > 0 then - SaveToFile(DownloadTempPath + SPackageUpgradeFileName); - finally - Free; - end; -end; - -procedure TBackgroundUpdate.ShutDown; -begin - if Assigned(Application) then - Application.Terminate; -end; - - -{ TOnlineUpdateSharedData } - -constructor TOnlineUpdateSharedData.Create(AParams: TBackgroundUpdateParams); -begin - inherited Create; - FParams := AParams; -end; - -function TOnlineUpdateSharedData.Params: TBackgroundUpdateParams; -begin - Result := FParams; -end; - - -function TBackgroundUpdate.SetRegistryState(Update : TUpdateState): Boolean; -var - UpdateStr : string; -begin - - Result := False; - with TRegistryErrorControlled.Create do - try - RootKey := HKEY_LOCAL_MACHINE; - KL.Log('SetRegistryState State Entry'); - if OpenKey(SRegKey_KeymanEngine_LM, True) then - begin - UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); - WriteString(SRegValue_Update_State, UpdateStr); - KL.Log('SetRegistryState State is:[' + UpdateStr + ']'); - end; - Result := True; - finally - Free; - end; - -end; - - -function TBackgroundUpdate.CheckRegistryState : TUpdateState; // I2329 -var - UpdateState : TUpdateState; - -begin - // We will use a registry flag to maintain the state of the background update - - UpdateState := usIdle; // do we need a unknown state ? - // check the registry value - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_LOCAL_MACHINE; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Update_State) then - begin - UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), ReadString(SRegValue_Update_State))); - KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); - end - else - begin - UpdateState := usIdle; // do we need a unknown state ? - KL.Log('CheckRegistryState State reg value not found default:[' + ReadString(SRegValue_Update_State) + ']'); - end - finally - Free; - end; - Result := UpdateState; -end; - -function TBackgroundUpdate.SetRegistryInstallMode (InstallMode : Boolean): Boolean; -var - InstallModeStr : string; -begin - - Result := False; - with TRegistryErrorControlled.Create do - try - RootKey := HKEY_LOCAL_MACHINE; - KL.Log('SetRegistryState State Entry'); - if OpenKey(SRegKey_KeymanEngine_LM, True) then - begin - InstallModeStr := BoolToStr(InstallMode, True); - WriteString(SRegValue_Install_Mode, InstallModeStr); - KL.Log('SetRegistryInstallMode is:[' + InstallModeStr + ']'); - end; - Result := True; - finally - Free; - end; - -end; - -function TBackgroundUpdate.CheckRegistryInstallMode : Boolean; -var - InstallMode : Boolean; - -begin - // We will use a registry flag to maintain the install mode background/foreground - - InstallMode := False; - // check the registry value - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_LOCAL_MACHINE; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Install_Mode) then - begin - InstallMode := StrToBool(ReadString(SRegValue_Install_Mode)); - KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); - end - else - begin - InstallMode := False; // default to background - KL.Log('CheckRegistryInstallMode reg value not found default:[ False ]'); - end - finally - Free; - end; - Result := InstallMode; -end; - - -function TBackgroundUpdate.CheckUpdateSchedule: Boolean; -begin - try - Result := False; - with TRegistryErrorControlled.Create do - try - if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then - begin - if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then - begin - Result := False; - Exit; - end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then - begin - Result := False; - Exit; - end; - // Else Time to check for updates - Result := True; - end; - finally - Free; - end; - except - { we will not run the check if an error occurs reading the settings } - on E:Exception do - begin - Result := False; - FErrorMessage := E.Message; - Exit; - end; - end; -end; - -function TBackgroundUpdate.GetState: TStateClass; -begin - Result := TStateClass(CurrentState.ClassType); -end; - -procedure TBackgroundUpdate.SetState(const Value: TStateClass); -begin - if Assigned(CurrentState) then - begin - CurrentState.Exit; - end; - - SetStateOnly(Value); - - if Assigned(CurrentState) then - begin - CurrentState.Enter; - end - else - begin - // TODO: #10210 Error log for Unable to set state for Value - end; - -end; - -procedure TBackgroundUpdate.SetStateOnly(const Value: TStateClass); -begin - if Value = IdleState then - begin - CurrentState := FIdle; - end - else if Value = UpdateAvailableState then - begin - CurrentState := FUpdateAvailable; - end - else if Value = DownloadingState then - begin - CurrentState := FDownloading; - end - else if Value = WaitingRestartState then - begin - CurrentState := FWaitingRestart; - end - else if Value = InstallingState then - begin - CurrentState := FInstalling; - end - else if Value = RetryState then - begin - CurrentState := FRetry; - end - else if Value = WaitingPostInstallState then - begin - CurrentState := FWaitingPostInstall; - end; -end; - -function TBackgroundUpdate.ConvertEnumState(const TEnumState: TUpdateState) : TStateClass; -begin - case TEnumState of - usIdle: Result := IdleState; - usUpdateAvailable: Result := UpdateAvailableState; - usDownloading: Result := DownloadingState; - usWaitingRestart: Result := WaitingRestartState; - usInstalling: Result := InstallingState; - usRetry: Result := RetryState; - usWaitingPostInstall: Result := WaitingPostInstallState; - else - // TODO: #10210 Log error unknown state setting to idle - Result := IdleState; - end; -end; - -procedure TBackgroundUpdate.HandleCheck; -begin - CurrentState.HandleCheck; -end; - -function TBackgroundUpdate.HandleKmShell; -begin - Result := CurrentState.HandleKmShell; -end; - -procedure TBackgroundUpdate.HandleDownload; -begin - CurrentState.HandleDownload; -end; - -procedure TBackgroundUpdate.HandleInstall; -begin - CurrentState.HandleInstall; -end; - -procedure TBackgroundUpdate.HandleMSIInstallComplete; -begin - CurrentState.HandleMSIInstallComplete; -end; - -procedure TBackgroundUpdate.HandleAbort; -begin - CurrentState.HandleAbort; -end; - -procedure TBackgroundUpdate.HandleInstallNow; -begin - CurrentState.HandleInstallNow; -end; - -function TBackgroundUpdate.CurrentStateName: string; -begin - Result := CurrentState.StateName; -end; - - - -{ State Class Memebers } -constructor TState.Create(Context: TBackgroundUpdate); -begin - bucStateContext := Context; -end; - -procedure TState.ChangeState(NewState: TStateClass); -begin - bucStateContext.State := NewState; -end; - - -{ IdleState } - -procedure IdleState.Enter; -begin - // Enter UpdateAvailableState - bucStateContext.SetRegistryState(usIdle); -end; - -procedure IdleState.Exit; -begin - -end; - -procedure IdleState.HandleCheck; -var - CheckForUpdates: TRemoteUpdateCheck; - Result : TRemoteUpdateCheckResult; -begin - - { Make a HTTP request out and see if updates are available for now do - this all in the Idle HandleCheck message. But could be broken into an - seperate state of WaitngCheck RESP } - { if Response not OK stay in the idle state and return } - - - // should be false but forcing check for testing - //CheckForUpdates := TRemoteUpdateCheck.Create(True); - CheckForUpdates := TRemoteUpdateCheck.Create(False); - try - Result:= CheckForUpdates.Run; - finally - CheckForUpdates.Free; - end; - - { Response OK and Update is available } - if Result = wucSuccess then - begin - ChangeState(UpdateAvailableState); - end; - // else staty in idle state -end; - -procedure IdleState.HandleDownload; -begin - -end; - -function IdleState.HandleKmShell; -begin - - Result := kmShellContinue; -end; - -procedure IdleState.HandleInstall; -begin - -end; - -procedure IdleState.HandleMSIInstallComplete; -begin - -end; - -procedure IdleState.HandleAbort; -begin - -end; - -procedure IdleState.HandleInstallNow; -begin - bucStateContext.SetRegistryInstallMode(True); - bucStateContext.CurrentState.HandleCheck; -end; - -function IdleState.StateName; -begin - - Result := 'IdleState'; -end; - -{ UpdateAvailableState } - -procedure UpdateAvailableState.Enter; -begin - // Enter UpdateAvailableState - bucStateContext.SetRegistryState(usUpdateAvailable); - if bucStateContext.FAuto then - begin - bucStateContext.CurrentState.HandleDownload; - end; -end; - -procedure UpdateAvailableState.Exit; -begin - // Exit UpdateAvailableState -end; - -procedure UpdateAvailableState.HandleCheck; -begin - -end; - -procedure UpdateAvailableState.HandleDownload; -begin - ChangeState(DownloadingState); -end; - -function UpdateAvailableState.HandleKmShell; -begin - if bucStateContext.FAuto then - begin - bucStateContext.CurrentState.HandleDownload ; - end; - Result := kmShellContinue; -end; - -procedure UpdateAvailableState.HandleInstall; -begin - -end; - -procedure UpdateAvailableState.HandleMSIInstallComplete; -begin - -end; - -procedure UpdateAvailableState.HandleAbort; -begin - -end; - -procedure UpdateAvailableState.HandleInstallNow; -begin - bucStateContext.SetRegistryInstallMode(True); - ChangeState(DownloadingState); -end; - -function UpdateAvailableState.StateName; -begin - - Result := 'UpdateAvailableState'; -end; - -{ DownloadingState } - -procedure DownloadingState.Enter; -var DownloadResult : Boolean; -begin - // Enter DownloadingState - bucStateContext.SetRegistryState(usDownloading); - DownloadResult := DownloadUpdatesBackground; - if DownloadResult then - begin - if HasKeymanRun then - ChangeState(WaitingRestartState) - else - ChangeState(InstallingState); - end - else - begin - ChangeState(RetryState); - end -end; - -procedure DownloadingState.Exit; -begin - // Exit DownloadingState -end; - -procedure DownloadingState.HandleCheck; -begin - -end; - -procedure DownloadingState.HandleDownload; -var DownloadResult : Boolean; -begin - // We are already downloading do nothing -end; - -function DownloadingState.HandleKmShell; -var DownloadResult : Boolean; -begin - DownloadResult := DownloadUpdatesBackground; - // TODO check if keyman is running then send to Waiting Restart - if DownloadResult then - begin - if HasKeymanRun then - begin - ChangeState(WaitingRestartState); - Result := kmShellContinue; - end - else - begin - ChangeState(InstallingState); - Result := kmShellExit; - end; - end - else - begin - ChangeState(RetryState); - Result := kmShellContinue; - end; - -end; - -procedure DownloadingState.HandleInstall; -begin - ChangeState(InstallingState); -end; - -procedure DownloadingState.HandleMSIInstallComplete; -begin - -end; - -procedure DownloadingState.HandleAbort; -begin -end; - -procedure DownloadingState.HandleInstallNow; -begin - bucStateContext.SetRegistryInstallMode(True); - // Continue downloading -end; - -function DownloadingState.StateName; -begin - Result := 'DownloadingState'; -end; - - -function DownloadingState.DownloadUpdatesBackground: Boolean; -var - i: Integer; - DownloadBackGroundSavePath : String; - DownloadResult : Boolean; - DownloadUpdate: TDownloadUpdate; -begin - DownloadUpdate := TDownloadUpdate.Create; - try - DownloadResult := DownloadUpdate.DownloadUpdates; - KL.Log('TBackgroundUpdate.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - Result := DownloadResult; -// #TODO: #10210 workout when we need to refresh kmcom keyboards - -// if Result in [ wucSuccess] then -// begin -// kmcom.Keyboards.Refresh; -// kmcom.Keyboards.Apply; -// kmcom.Packages.Refresh; -// end; - - finally - DownloadUpdate.Free; - end; -end; - -{ WaitingRestartState } - -procedure WaitingRestartState.Enter; -begin - // Enter DownloadingState - bucStateContext.SetRegistryState(usWaitingRestart); -end; - -procedure WaitingRestartState.Exit; -begin - // Exit DownloadingState -end; - -procedure WaitingRestartState.HandleCheck; -begin - -end; - -procedure WaitingRestartState.HandleDownload; -begin - -end; - -function WaitingRestartState.HandleKmShell; -var - SavedPath : String; - Filenames : TStringDynArray; -begin - KL.Log('WaitingRestartState.HandleKmShell Enter'); - // Still can't go if keyman has run - if HasKeymanRun then - begin - KL.Log('WaitingRestartState.HandleKmShell Keyman Has Run'); - Result := kmShellExit; - // Exit; // Exit is not wokring for some reason. - // this else is only here because the exit is not working. - end - else - begin - // Check downloaded cache if available then - SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, FileNames); - if Length(FileNames) = 0 then - begin - KL.Log('WaitingRestartState.HandleKmShell No Files in Download Cache'); - // Return to Idle state and check for Updates state - ChangeState(IdleState); - bucStateContext.CurrentState.HandleCheck; - Result := kmShellExit; - // Exit; // again exit was not working - end - else - begin - KL.Log('WaitingRestartState.HandleKmShell is good to install'); - ChangeState(InstallingState); - Result := kmShellExit; - end; - end; -end; - -procedure WaitingRestartState.HandleInstall; -begin - -end; - -procedure WaitingRestartState.HandleMSIInstallComplete; -begin - -end; - -procedure WaitingRestartState.HandleAbort; -begin - -end; - -procedure WaitingRestartState.HandleInstallNow; -begin - bucStateContext.SetRegistryInstallMode(True); - // Notify User to install - ChangeState(InstallingState); - -end; - -function WaitingRestartState.StateName; -begin - - Result := 'WaitingRestartState'; -end; - -{ InstallingState } - -function InstallingState.DoInstallPackage(Package: TBackgroundUpdateParamsPackage): Boolean; -var - FPackage: IKeymanPackageFile2; -begin - Result := True; - - FPackage := kmcom.Packages.GetPackageFromFile(Package.SavePath) as IKeymanPackageFile2; - FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact - FPackage := nil; - - kmcom.Refresh; - kmcom.Apply; - System.SysUtils.DeleteFile(Package.SavePath); -end; - -procedure InstallingState.DoInstallKeyman; -var - s: string; - FResult: Boolean; -begin - FResult := False; - s := LowerCase(ExtractFileExt(bucStateContext.FParams.Keyman.SavePath)); - if s = '.msi' then - FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+bucStateContext.FParams.Keyman.SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 - else if s = '.exe' then - FResult := TUtilExecute.Shell(0, bucStateContext.FParams.Keyman.SavePath, '', '-au') // I3349 - else - Exit; - if not FResult then - ShowMessage(SysErrorMessage(GetLastError)); -end; - -function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; -var - s: string; - FResult: Boolean; -begin - s := LowerCase(ExtractFileExt(SavePath)); - if s = '.msi' then - FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 - else if s = '.exe' then - begin - KL.Log('TBackgroundUpdate.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); - FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 - end - else - FResult := False; - - if not FResult then - begin - KL.Log('TBackgroundUpdate.InstallingState.DoInstall: Result = '+IntToStr(Ord(FResult))); - // Log messageShowMessage(SysErrorMessage(GetLastError)); - end; - - Result := FResult; -end; - -procedure InstallingState.Enter; -var - SavePath: String; - fileExt : String; - fileName: String; - fileNames: TStringDynArray; -begin - bucStateContext.SetRegistryState(usInstalling); - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - - GetFileNamesInDirectory(SavePath, fileNames); - // for now we only want the exe although excute install can - // handle msi - for fileName in fileNames do - begin - fileExt := LowerCase(ExtractFileExt(fileName)); - if fileExt = '.exe' then - break; - end; - - if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then - begin - KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall OK'); - end - else - begin - // TODO: #10210 clean failed download - // TODO: #10210 Do we do a retry on install? probably not - KL.Log('TBackgroundUpdate.InstallingState.Enter: DoInstall fail'); - ChangeState(IdleState); - end -end; - -procedure InstallingState.Exit; -begin - // Exit DownloadingState -end; - -procedure InstallingState.HandleCheck; -begin - -end; - -procedure InstallingState.HandleDownload; -begin - -end; - -function InstallingState.HandleKmShell; -begin - // Result = exit straight away as we are installing (MSI installer) - // need to just do a no-op keyman will it maybe using kmshell to install - // packages. - Result := kmShellContinue; -end; - -procedure InstallingState.HandleInstall; -begin - -end; - -procedure InstallingState.HandleMSIInstallComplete; -begin - -end; - -procedure InstallingState.HandleAbort; -begin - ChangeState(IdleState); -end; - -procedure InstallingState.HandleInstallNow; -begin - // Do Nothing. Need the UI to let user know installation in progress OR -end; - -function InstallingState.StateName; -begin - - Result := 'InstallingState'; -end; - -{ RetryState } - -procedure RetryState.Enter; -begin - // Enter DownloadingState - bucStateContext.SetRegistryState(usRetry); -end; - -procedure RetryState.Exit; -begin - // Exit DownloadingState -end; - -procedure RetryState.HandleCheck; -begin - -end; - -procedure RetryState.HandleDownload; -begin - -end; - -function RetryState.HandleKmShell; -begin - // #TODO: #10210 Implement retry - Result := kmShellContinue -end; - -procedure RetryState.HandleInstall; -begin - -end; - -procedure RetryState.HandleMSIInstallComplete; -begin - -end; - -procedure RetryState.HandleAbort; -begin - -end; - -procedure RetryState.HandleInstallNow; -begin - bucStateContext.SetRegistryInstallMode(True); - // TODO: #10038 handle retry counts - ChangeState(InstallingState); -end; - -function RetryState.StateName; -begin - - Result := 'RetryState'; -end; - -{ WaitingPostInstallState } - -procedure WaitingPostInstallState.Enter; -begin - // Enter downloading state - bucStateContext.SetRegistryState(usWaitingPostInstall); -end; - -procedure WaitingPostInstallState.Exit; -begin - // Exit downloading state -end; - -procedure WaitingPostInstallState.HandleCheck; -begin - // Handle Check -end; - -procedure WaitingPostInstallState.HandleDownload; -begin - // Handle Download -end; - -function WaitingPostInstallState.HandleKmShell; -begin - // TODO: #10210 have a counter if we get called in this state - // too many time abort. - HandleMSIInstallComplete; - Result := kmShellContinue; -end; - -procedure WaitingPostInstallState.HandleInstall; -begin - // Handle Install -end; - -procedure WaitingPostInstallState.HandleMSIInstallComplete; -var SavePath: string; - FileName: String; - FileNames: TStringDynArray; -begin - KL.Log('WaitingPostInstallState.HandleMSIInstallComplete'); - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - KL.Log('WaitingPostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); - - GetFileNamesInDirectory(SavePath, FileNames); - for FileName in FileNames do - begin - System.SysUtils.DeleteFile(FileName); - end; - ChangeState(IdleState); -end; - -procedure WaitingPostInstallState.HandleAbort; -begin - // Handle Abort -end; - -procedure WaitingPostInstallState.HandleInstallNow; -begin - // Do nothing as files will be cleaned via HandleKmShell -end; - -function WaitingPostInstallState.StateName; -begin - - Result := 'WaitingPostInstallState'; -end; - - - -end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 6d6eb9b426..589bca7582 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -35,6 +35,9 @@ uses Keyman.System.ExecuteHistory, UfrmDownloadProgress; +const + CheckPeriod: Integer = 7; // Days between checking for updates + type EUpdateStateMachine = class(Exception); @@ -93,10 +96,8 @@ type procedure Enter; virtual; abstract; procedure Exit; virtual; abstract; procedure HandleCheck; virtual; abstract; - procedure HandleDownload; virtual; abstract; function HandleKmShell : Integer; virtual; abstract; procedure HandleInstall; virtual; abstract; - procedure HandleMSIInstallComplete; virtual; abstract; procedure HandleAbort; virtual; abstract; procedure HandleInstallNow; virtual; abstract; @@ -111,10 +112,8 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -125,10 +124,8 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -141,10 +138,8 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -155,10 +150,8 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -181,10 +174,8 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -195,44 +186,37 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; end; PostInstallState = class(TState) + private + procedure HandleMSIInstallComplete; public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - procedure HandleDownload; override; function HandleKmShell : Integer; override; procedure HandleInstall; override; - procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; end; + { This class also controls the state flow see } TUpdateStateMachine = class private FForce: Boolean; FAuto: Boolean; FParams: TUpdateStateMachineParams; - FErrorMessage: string; - DownloadTempPath: string; - FShowErrors: Boolean; - - - FDownload: TUpdateStateMachineDownloadParams; CurrentState: TState; @@ -250,7 +234,6 @@ type function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; procedure ShutDown; - { SavePackageUpgradesToDownloadTempPath saves any new package IDs to a single file in the download tempPath. This procedure saves the IDs of any @@ -272,9 +255,7 @@ type procedure HandleCheck; function HandleKmShell : Integer; - procedure HandleDownload; procedure HandleInstall; - procedure HandleMSIInstallComplete; procedure HandleAbort; procedure HandleInstallNow; function CurrentStateName: string; @@ -339,8 +320,6 @@ constructor TUpdateStateMachine.Create(AForce : Boolean); // var TSerailsedState : TUpdateState; // TODO: Remove begin inherited Create; - - FShowErrors := True; FParams.Result := oucUnknown; @@ -403,7 +382,6 @@ begin Application.Terminate; end; - { TOnlineUpdateSharedData } constructor TOnlineUpdateSharedData.Create(AParams: TUpdateStateMachineParams); @@ -417,31 +395,41 @@ begin Result := FParams; end; - function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; var UpdateStr : string; + Registry: TRegistryErrorControlled; begin - Result := False; - with TRegistryErrorControlled.Create do + Registry := TRegistryErrorControlled.Create; + try - RootKey := HKEY_LOCAL_MACHINE; + Registry.RootKey := HKEY_CURRENT_USER; KL.Log('SetRegistryState State Entry'); - if OpenKey(SRegKey_KeymanEngine_LM, True) then + if not Registry.OpenKey(SRegKey_KeymanEngine_LM, True) then begin - UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); - WriteString(SRegValue_Update_State, UpdateStr); - KL.Log('SetRegistryState State is:[' + UpdateStr + ']'); + KL.Log('Failed to open registry key: ' + SRegKey_KeymanEngine_LM); + Exit; end; - Result := True; + + try + UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); + Registry.WriteString(SRegValue_Update_State, UpdateStr); + KL.Log('SetRegistryState State is: [' + UpdateStr + ']'); + Result := True; + except + on E: Exception do + begin + KL.Log('Failed to write to registry: ' + E.Message); + end; + end; + finally - Free; + Registry.Free; end; end; - function TUpdateStateMachine.CheckRegistryState : TUpdateState; // I2329 var UpdateState : TUpdateState; @@ -452,7 +440,7 @@ begin // check the registry value with TRegistryErrorControlled.Create do // I2890 try - RootKey := HKEY_LOCAL_MACHINE; + RootKey := HKEY_CURRENT_USER; if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Update_State) then begin UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), ReadString(SRegValue_Update_State))); @@ -477,7 +465,7 @@ begin Result := False; with TRegistryErrorControlled.Create do try - RootKey := HKEY_LOCAL_MACHINE; + RootKey := HKEY_CURRENT_USER; KL.Log('SetRegistryState State Entry'); if OpenKey(SRegKey_KeymanEngine_LM, True) then begin @@ -503,7 +491,7 @@ begin // check the registry value with TRegistryErrorControlled.Create do // I2890 try - RootKey := HKEY_LOCAL_MACHINE; + RootKey := HKEY_CURRENT_USER; if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Install_Mode) then begin InstallMode := StrToBool(ReadString(SRegValue_Install_Mode)); @@ -520,7 +508,6 @@ begin Result := InstallMode; end; - function TUpdateStateMachine.CheckUpdateSchedule: Boolean; begin try @@ -639,21 +626,11 @@ begin Result := CurrentState.HandleKmShell; end; -procedure TUpdateStateMachine.HandleDownload; -begin - CurrentState.HandleDownload; -end; - procedure TUpdateStateMachine.HandleInstall; begin CurrentState.HandleInstall; end; -procedure TUpdateStateMachine.HandleMSIInstallComplete; -begin - CurrentState.HandleMSIInstallComplete; -end; - procedure TUpdateStateMachine.HandleAbort; begin CurrentState.HandleAbort; @@ -669,8 +646,6 @@ begin Result := CurrentState.StateName; end; - - { State Class Memebers } constructor TState.Create(Context: TUpdateStateMachine); begin @@ -679,10 +654,11 @@ end; procedure TState.ChangeState(NewState: TStateClass); begin + KL.Log('TUpdateStateMachine.ChangeState old' + bucStateContext.CurrentStateName ); bucStateContext.State := NewState; + KL.Log('TUpdateStateMachine.ChangeState new' + bucStateContext.CurrentStateName ); end; - { IdleState } procedure IdleState.Enter; @@ -702,6 +678,11 @@ var Result : TRemoteUpdateCheckResult; begin + {##### For Testing only just advancing to downloading ####} + ChangeState(UpdateAvailableState); + {#### End of Testing ### }; + + { Make a HTTP request out and see if updates are available for now do this all in the Idle HandleCheck message. But could be broken into an @@ -710,33 +691,30 @@ begin // If handle check event force check - CheckForUpdates := TRemoteUpdateCheck.Create(True); - try - Result:= CheckForUpdates.Run; - finally - CheckForUpdates.Free; - end; + //CheckForUpdates := TRemoteUpdateCheck.Create(True); + //try + // Result:= CheckForUpdates.Run; + // finally + // CheckForUpdates.Free; + // end; { Response OK and Update is available } - if Result = wucSuccess then - begin - ChangeState(UpdateAvailableState); - end; + // if Result = wucSuccess then + // begin + // ChangeState(UpdateAvailableState); + // end; + // else staty in idle state end; -procedure IdleState.HandleDownload; -begin - -end; - function IdleState.HandleKmShell; var CheckForUpdates: TRemoteUpdateCheck; UpdateCheckResult : TRemoteUpdateCheckResult; -const CheckPeriod: Integer = 7; // Days between checking for updates +//const CheckPeriod: Integer = 7; // Days between checking for updates begin // Check if auto updates enable and if scheduled time has expired + KL.Log('IdleState.HandleKmShell'); if ConfigCheckContinue then begin CheckForUpdates := TRemoteUpdateCheck.Create(True); @@ -759,11 +737,6 @@ begin end; -procedure IdleState.HandleMSIInstallComplete; -begin - -end; - procedure IdleState.HandleAbort; begin @@ -804,16 +777,11 @@ begin end; -procedure UpdateAvailableState.HandleDownload; -begin - -end; - function UpdateAvailableState.HandleKmShell; begin if bucStateContext.FAuto then begin - ChangeState(DownloadingState);; + ChangeState(DownloadingState); end; Result := kmShellContinue; end; @@ -823,11 +791,6 @@ begin end; -procedure UpdateAvailableState.HandleMSIInstallComplete; -begin - -end; - procedure UpdateAvailableState.HandleAbort; begin @@ -869,16 +832,14 @@ begin end; -procedure DownloadingState.HandleDownload; -var DownloadResult : Boolean; -begin - // We are already downloading do nothing -end; - function DownloadingState.HandleKmShell; var DownloadResult : Boolean; begin - DownloadResult := DownloadUpdatesBackground; + {## for testing log that we would download } + KL.Log('DownloadingState.HandleKmshell test code continue'); + DownloadResult := True; + { End testing} + //DownloadResult := DownloadUpdatesBackground; // TODO check if keyman is running then send to Waiting Restart if DownloadResult then begin @@ -906,11 +867,6 @@ begin ChangeState(InstallingState); end; -procedure DownloadingState.HandleMSIInstallComplete; -begin - -end; - procedure DownloadingState.HandleAbort; begin end; @@ -926,7 +882,6 @@ begin Result := 'DownloadingState'; end; - function DownloadingState.DownloadUpdatesBackground: Boolean; var DownloadBackGroundSavePath : String; @@ -946,7 +901,6 @@ begin // kmcom.Keyboards.Apply; // kmcom.Packages.Refresh; // end; - finally DownloadUpdate.Free; end; @@ -956,7 +910,8 @@ end; procedure WaitingRestartState.Enter; begin - // Enter DownloadingState + // Enter WaitingRestartState + KL.Log('WaitingRestartState.HandleKmShell Enter'); bucStateContext.SetRegistryState(usWaitingRestart); end; @@ -970,11 +925,6 @@ begin end; -procedure WaitingRestartState.HandleDownload; -begin - -end; - function WaitingRestartState.HandleKmShell; var SavedPath : String; @@ -985,7 +935,7 @@ begin if HasKeymanRun then begin KL.Log('WaitingRestartState.HandleKmShell Keyman Has Run'); - Result := kmShellExit; + Result := kmShellContinue; // Exit; // Exit is not wokring for some reason. // this else is only here because the exit is not working. end @@ -1017,11 +967,6 @@ begin end; -procedure WaitingRestartState.HandleMSIInstallComplete; -begin - -end; - procedure WaitingRestartState.HandleAbort; begin @@ -1032,7 +977,6 @@ begin bucStateContext.SetRegistryInstallMode(True); // Notify User to install ChangeState(InstallingState); - end; function WaitingRestartState.StateName; @@ -1042,7 +986,6 @@ begin end; { InstallingState } - function InstallingState.DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; var FPackage: IKeymanPackageFile2; @@ -1137,7 +1080,7 @@ end; procedure InstallingState.Exit; begin - // Exit DownloadingState + end; procedure InstallingState.HandleCheck; @@ -1145,11 +1088,6 @@ begin end; -procedure InstallingState.HandleDownload; -begin - -end; - function InstallingState.HandleKmShell; begin // Result = exit straight away as we are installing (MSI installer) @@ -1163,11 +1101,6 @@ begin end; -procedure InstallingState.HandleMSIInstallComplete; -begin - -end; - procedure InstallingState.HandleAbort; begin ChangeState(IdleState); @@ -1180,7 +1113,6 @@ end; function InstallingState.StateName; begin - Result := 'InstallingState'; end; @@ -1188,13 +1120,12 @@ end; procedure RetryState.Enter; begin - // Enter DownloadingState bucStateContext.SetRegistryState(usRetry); end; procedure RetryState.Exit; begin - // Exit DownloadingState + end; procedure RetryState.HandleCheck; @@ -1202,11 +1133,6 @@ begin end; -procedure RetryState.HandleDownload; -begin - -end; - function RetryState.HandleKmShell; begin // #TODO: #10210 Implement retry @@ -1218,11 +1144,6 @@ begin end; -procedure RetryState.HandleMSIInstallComplete; -begin - -end; - procedure RetryState.HandleAbort; begin @@ -1251,7 +1172,7 @@ end; procedure PostInstallState.Exit; begin - // Exit downloading state + end; procedure PostInstallState.HandleCheck; @@ -1259,11 +1180,6 @@ begin // Handle Check end; -procedure PostInstallState.HandleDownload; -begin - // Handle Download -end; - function PostInstallState.HandleKmShell; begin // TODO: #10210 have a counter if we get called in this state @@ -1351,5 +1267,4 @@ begin end; end; - end. diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 77fe071786..4eea8adb59 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -37,7 +37,7 @@ begin Result := False; UpdateStr := 'usPostInstall'; //KL.Log('SetBackgroundState State Entry'); - if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_LM), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then + if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_CU), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then begin try if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PWideChar(UpdateStr), Length(UpdateStr) * SizeOf(Char)) = ERROR_SUCCESS then From a7736a07d9d2476a225c5df532d464dbf744b407 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:54:45 +1000 Subject: [PATCH 037/124] feat(windows): remove duplicate localisation string id --- windows/src/desktop/kmshell/xml/strings.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 487a818ddf..b963ae63be 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -571,14 +571,6 @@ Diagnostics - - - - Check for Updates - - - - From 98cd1f5b69455ff55c98ecb46a43f1e9b4d6f6de Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:10:44 +1000 Subject: [PATCH 038/124] feat(windows): add handledownload back in Fix all the old style with do code blocks to create the object Add handledownload back in as I realised you need to otherwise you can neatly stop multiple downloads occuring. --- .../main/Keyman.System.UpdateStateMachine.pas | 244 ++++++++---------- windows/src/desktop/kmshell/main/initprog.pas | 9 +- 2 files changed, 110 insertions(+), 143 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 589bca7582..f7cfd9ef28 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -97,7 +97,7 @@ type procedure Exit; virtual; abstract; procedure HandleCheck; virtual; abstract; function HandleKmShell : Integer; virtual; abstract; - procedure HandleInstall; virtual; abstract; + procedure HandleDownload; virtual; abstract; procedure HandleAbort; virtual; abstract; procedure HandleInstallNow; virtual; abstract; @@ -113,19 +113,21 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; end; UpdateAvailableState = class(TState) + private + procedure StartDownloadProcess; public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -139,7 +141,7 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -151,7 +153,7 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -175,7 +177,7 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -187,7 +189,7 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -201,7 +203,7 @@ type procedure Exit; override; procedure HandleCheck; override; function HandleKmShell : Integer; override; - procedure HandleInstall; override; + procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; function StateName: string; override; @@ -244,7 +246,6 @@ type function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; - function SetRegistryInstallMode (InstallMode : Boolean): Boolean; protected property State: TStateClass read GetState write SetState; @@ -255,14 +256,13 @@ type procedure HandleCheck; function HandleKmShell : Integer; - procedure HandleInstall; + procedure HandleDownload; procedure HandleAbort; procedure HandleInstallNow; function CurrentStateName: string; property ShowErrors: Boolean read FShowErrors write FShowErrors; function CheckRegistryState : TUpdateState; - function CheckRegistryInstallMode : Boolean; end; @@ -363,16 +363,17 @@ end; procedure TUpdateStateMachine.SavePackageUpgradesToDownloadTempPath; var i: Integer; + StringList : TStringList; begin - with TStringList.Create do + StringList := TStringList.Create; try for i := 0 to High(FParams.Packages) do if FParams.Packages[i].NewID <> '' then - Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); - if Count > 0 then - SaveToFile(DownloadTempPath + SPackageUpgradeFileName); + StringList.Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); + if StringList.Count > 0 then + StringList.SaveToFile(DownloadTempPath + SPackageUpgradeFileName); finally - Free; + StringList.Free; end; end; @@ -430,98 +431,52 @@ begin end; -function TUpdateStateMachine.CheckRegistryState : TUpdateState; // I2329 +function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 var - UpdateState : TUpdateState; + UpdateState: TUpdateState; + Registry: TRegistryErrorControlled; begin // We will use a registry flag to maintain the state of the background update // check the registry value - with TRegistryErrorControlled.Create do // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try - RootKey := HKEY_CURRENT_USER; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Update_State) then - begin - UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), ReadString(SRegValue_Update_State))); - KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); - end + Registry.RootKey := HKEY_CURRENT_USER; + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and Registry.ValueExists(SRegValue_Update_State) then + begin + UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); + KL.Log('CheckRegistryState State is:[' + Registry.ReadString(SRegValue_Update_State) + ']'); + end else - begin - UpdateState := usIdle; // do we need a unknown state ? - KL.Log('CheckRegistryState State reg value not found default:[' + ReadString(SRegValue_Update_State) + ']'); - end - finally - Free; + begin + UpdateState := usIdle; // do we need a unknown state ? + KL.Log('CheckRegistryState State reg value not found default:[' + Registry.ReadString(SRegValue_Update_State) + ']'); + end; + finally + Registry.Free; end; + Result := UpdateState; end; -function TUpdateStateMachine.SetRegistryInstallMode (InstallMode : Boolean): Boolean; -var - InstallModeStr : string; -begin - - Result := False; - with TRegistryErrorControlled.Create do - try - RootKey := HKEY_CURRENT_USER; - KL.Log('SetRegistryState State Entry'); - if OpenKey(SRegKey_KeymanEngine_LM, True) then - begin - InstallModeStr := BoolToStr(InstallMode, True); - WriteString(SRegValue_Install_Mode, InstallModeStr); - KL.Log('SetRegistryInstallMode is:[' + InstallModeStr + ']'); - end; - Result := True; - finally - Free; - end; - -end; - -function TUpdateStateMachine.CheckRegistryInstallMode : Boolean; -var - InstallMode : Boolean; - -begin - // We will use a registry flag to maintain the install mode background/foreground - - InstallMode := False; - // check the registry value - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_CURRENT_USER; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Install_Mode) then - begin - InstallMode := StrToBool(ReadString(SRegValue_Install_Mode)); - KL.Log('CheckRegistryState State is:[' + ReadString(SRegValue_Update_State) + ']'); - end - else - begin - InstallMode := False; // default to background - KL.Log('CheckRegistryInstallMode reg value not found default:[ False ]'); - end - finally - Free; - end; - Result := InstallMode; -end; - function TUpdateStateMachine.CheckUpdateSchedule: Boolean; +var + RegistryErrorControlled :TRegistryErrorControlled; begin try Result := False; - with TRegistryErrorControlled.Create do + RegistryErrorControlled := TRegistryErrorControlled.Create; + try - if OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then begin - if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) and not FForce then + if RegistryErrorControlled.ValueExists(SRegValue_CheckForUpdates) and not RegistryErrorControlled.ReadBool(SRegValue_CheckForUpdates) and not FForce then begin Result := False; Exit; end; - if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then + if RegistryErrorControlled.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - RegistryErrorControlled.ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then begin Result := False; Exit; @@ -530,7 +485,7 @@ begin Result := True; end; finally - Free; + RegistryErrorControlled.Free; end; except { we will not run the check if an error occurs reading the settings } @@ -626,9 +581,9 @@ begin Result := CurrentState.HandleKmShell; end; -procedure TUpdateStateMachine.HandleInstall; +procedure TUpdateStateMachine.HandleDownload; begin - CurrentState.HandleInstall; + CurrentState.HandleDownload; end; procedure TUpdateStateMachine.HandleAbort; @@ -732,9 +687,9 @@ begin Result := kmShellContinue; end; -procedure IdleState.HandleInstall; +procedure IdleState.HandleDownload; begin - + // Do Nothing end; procedure IdleState.HandleAbort; @@ -744,7 +699,6 @@ end; procedure IdleState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); bucStateContext.CurrentState.HandleCheck; // TODO: How do we notify the command line no update available end; @@ -757,13 +711,25 @@ end; { UpdateAvailableState } + +procedure UpdateAvailableState.StartDownloadProcess; +var DownloadResult, FResult : Boolean; +RootPath: string; +begin + // call seperate process + RootPath := ExtractFilePath(ParamStr(0)); + FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); + if not FResult then + KL.Log('TrmfMain: Executing KMshell for download updated Failed'); +end; + procedure UpdateAvailableState.Enter; begin // Enter UpdateAvailableState bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAuto then begin - ChangeState(DownloadingState); + StartDownloadProcess; end; end; @@ -781,14 +747,16 @@ function UpdateAvailableState.HandleKmShell; begin if bucStateContext.FAuto then begin - ChangeState(DownloadingState); + // we will use a new kmshell process to enable + // the download as background process. + StartDownloadProcess; end; Result := kmShellContinue; end; -procedure UpdateAvailableState.HandleInstall; +procedure UpdateAvailableState.HandleDownload; begin - + ChangeState(DownloadingState); end; procedure UpdateAvailableState.HandleAbort; @@ -798,7 +766,6 @@ end; procedure UpdateAvailableState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); ChangeState(DownloadingState); end; @@ -815,11 +782,28 @@ RootPath: string; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); - // call seperate process - RootPath := ExtractFilePath(ParamStr(0)); - FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), ''); - if not FResult then - KL.Log('TrmfMain: Executing KMshell for download updated Failed'); + {## for testing log that we would download } + KL.Log('DownloadingState.HandleKmshell test code continue'); + DownloadResult := True; + { End testing} + //DownloadResult := DownloadUpdatesBackground; + // TODO check if keyman is running then send to Waiting Restart + if DownloadResult then + begin + if HasKeymanRun then + begin + ChangeState(WaitingRestartState); + end + else + begin + ChangeState(InstallingState); + end; + end + else + begin + ChangeState(RetryState); + end; + end; procedure DownloadingState.Exit; @@ -833,38 +817,17 @@ begin end; function DownloadingState.HandleKmShell; -var DownloadResult : Boolean; begin - {## for testing log that we would download } - KL.Log('DownloadingState.HandleKmshell test code continue'); - DownloadResult := True; - { End testing} - //DownloadResult := DownloadUpdatesBackground; - // TODO check if keyman is running then send to Waiting Restart - if DownloadResult then - begin - if HasKeymanRun then - begin - ChangeState(WaitingRestartState); - Result := kmShellContinue; - end - else - begin - ChangeState(InstallingState); - Result := kmShellExit; - end; - end - else - begin - ChangeState(RetryState); - Result := kmShellContinue; - end; - + // Downloading state, in other process, so continue + Result := kmShellContinue; end; -procedure DownloadingState.HandleInstall; +procedure DownloadingState.HandleDownload; +var DownloadResult, FResult : Boolean; +RootPath: string; begin - ChangeState(InstallingState); + // Enter Already Downloading + KL.Log('DownloadingState.HandleDownload already downloading'); end; procedure DownloadingState.HandleAbort; @@ -873,8 +836,7 @@ end; procedure DownloadingState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); - // Continue downloading + end; function DownloadingState.StateName; @@ -962,7 +924,7 @@ begin end; end; -procedure WaitingRestartState.HandleInstall; +procedure WaitingRestartState.HandleDownload; begin end; @@ -974,8 +936,9 @@ end; procedure WaitingRestartState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); - // Notify User to install + // TODO: Check if keyman has run, and error trying to install + // now when windows needs a restart ask the user if they + // want to restart now, if users says no stay in this (waitingrestart) state ChangeState(InstallingState); end; @@ -1096,7 +1059,7 @@ begin Result := kmShellContinue; end; -procedure InstallingState.HandleInstall; +procedure InstallingState.HandleDownload; begin end; @@ -1139,7 +1102,7 @@ begin Result := kmShellContinue end; -procedure RetryState.HandleInstall; +procedure RetryState.HandleDownload; begin end; @@ -1151,7 +1114,6 @@ end; procedure RetryState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); // TODO: #10038 handle retry counts ChangeState(InstallingState); end; @@ -1182,15 +1144,13 @@ end; function PostInstallState.HandleKmShell; begin - // TODO: #10210 have a counter if we get called in this state - // too many time abort. HandleMSIInstallComplete; Result := kmShellContinue; end; -procedure PostInstallState.HandleInstall; +procedure PostInstallState.HandleDownload; begin - // Handle Install + // Do Nothing end; procedure PostInstallState.HandleMSIInstallComplete; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 284695bfd4..88d08ef4d1 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -83,6 +83,7 @@ type fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, fmBackgroundUpdateCheck, + fmBackgroundDownload, fmApplyInstallNow, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 @@ -254,6 +255,7 @@ begin else if s = '-t' then FMode := fmTextEditor else if s = '-ouc' then FMode := fmOnlineUpdateCheck else if s = '-buc' then FMode := fmBackgroundUpdateCheck + else if s = '-bd' then FMode := fmBackgroundDownload else if s = '-an' then FMode := fmApplyInstallNow else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169 else if s = '-nowelcome' then FNoWelcome := True @@ -443,9 +445,14 @@ begin BUpdateSM.HandleCheck; Exit; end + else if (FMode = fmBackgroundDownload) then + begin + BUpdateSM.HandleDownload; + Exit; + end else if (FMode = fmApplyInstallNow) then begin - BUpdateSM.HandleInstall; + BUpdateSM.HandleInstallNow; Exit; end else From 6a305c168bf54c24bac1e335f84b81c721715811 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:26:02 +1000 Subject: [PATCH 039/124] feat(windows): remove uneeded registry keys --- common/windows/delphi/general/RegistryKeys.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index c20c4baa0f..efd60b4148 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -178,9 +178,7 @@ const SRegValue_AvailableLanguages = 'available languages'; //CU SRegValue_CurrentLanguage = 'current language'; //CU - SRegValue_Install_Update = 'install update'; SRegValue_Update_State = 'update state'; - SRegValue_Install_Mode = 'install mode'; { Privacy } From e2ec08ca9b24925ef1c01acb167c68968ac0846f Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:03:57 +1000 Subject: [PATCH 040/124] feat(windows): remove klogging enable --- common/windows/delphi/general/klog.pas | 2 +- core/tests/unit/kmx/fixtures/meson.build | 4 ++-- core/tests/unit/meson.build | 6 +++--- windows/src/desktop/insthelp/insthelp.dpr | 7 +------ windows/src/desktop/insthelp/insthelp.dproj | 5 ----- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/common/windows/delphi/general/klog.pas b/common/windows/delphi/general/klog.pas index 2756c63893..2787f8b347 100644 --- a/common/windows/delphi/general/klog.pas +++ b/common/windows/delphi/general/klog.pas @@ -23,7 +23,7 @@ unit klog; // I3309 interface -{$DEFINE KLOGGING} +{DEFINE KLOGGING} {$IFDEF KLOGGING} uses diff --git a/core/tests/unit/kmx/fixtures/meson.build b/core/tests/unit/kmx/fixtures/meson.build index 711bae6f80..e40c30ad46 100644 --- a/core/tests/unit/kmx/fixtures/meson.build +++ b/core/tests/unit/kmx/fixtures/meson.build @@ -2,5 +2,5 @@ if node.found() # Note: if node is not available, we cannot build the keyboards; build.sh # emits a warning that the 'ldml' keyboard tests will be skipped; that # includes these tests for now - #subdir('binary') -endif + subdir('binary') +endif \ No newline at end of file diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build index 4b0bb489e5..ee141cc12d 100644 --- a/core/tests/unit/meson.build +++ b/core/tests/unit/meson.build @@ -10,6 +10,6 @@ hextobin_cmd = [node, hextobin_root] subdir('json') subdir('utftest') -#subdir('kmnkbd') -#subdir('kmx') -#subdir('ldml') +subdir('kmnkbd') +subdir('kmx') +subdir('ldml') diff --git a/windows/src/desktop/insthelp/insthelp.dpr b/windows/src/desktop/insthelp/insthelp.dpr index 905e4ac8eb..ab0e47ff7d 100644 --- a/windows/src/desktop/insthelp/insthelp.dpr +++ b/windows/src/desktop/insthelp/insthelp.dpr @@ -8,12 +8,7 @@ uses KeymanVersion in '..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', Keyman.System.InstHelp.KeymanStartTaskUninstall in 'Keyman.System.InstHelp.KeymanStartTaskUninstall.pas', TaskScheduler_TLB in '..\..\global\delphi\winapi\TaskScheduler_TLB.pas', - ErrorControlledRegistry in '..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas', - DebugPaths in '..\..\..\..\common\windows\delphi\general\DebugPaths.pas', - VersionInfo in '..\..\..\..\common\windows\delphi\general\VersionInfo.pas', - Unicode in '..\..\..\..\common\windows\delphi\general\Unicode.pas', - KeymanPaths in '..\..\..\..\common\windows\delphi\general\KeymanPaths.pas'; + UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas'; {$R version.res} {-R manifest.res} diff --git a/windows/src/desktop/insthelp/insthelp.dproj b/windows/src/desktop/insthelp/insthelp.dproj index cfb0ad37fc..45913bd470 100644 --- a/windows/src/desktop/insthelp/insthelp.dproj +++ b/windows/src/desktop/insthelp/insthelp.dproj @@ -98,11 +98,6 @@ - - - - - Cfg_2 Base From ad4bbdf0470f33daad62ab3b500a18e0d803f596 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 Aug 2024 14:15:19 +0700 Subject: [PATCH 041/124] feat(common): add script to run build and tests in Docker container Addresses code review comments. --- resources/docker-images/README.md | 120 ++++-------------------------- resources/docker-images/run.sh | 56 ++++++++++++++ 2 files changed, 72 insertions(+), 104 deletions(-) create mode 100755 resources/docker-images/run.sh diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index 5a241af91f..8c5593e2fb 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -7,7 +7,7 @@ build for the platform. ## Prerequisites You'll need Docker Buildx installed to successfully be able to build the -container images. This is easiest achieved by installing the [official +container images. This is most easily achieved by installing the [official Docker version](https://docs.docker.com/engine/install/ubuntu/). Currently it is not possible to use Podman instead of Docker due to a number @@ -22,9 +22,10 @@ resources/docker-images/build.sh ``` By default this will create 64-bit images for building -Keyman for Android, Keyman for Linux and Keyman for Web. These images -are based on the Ubuntu 24.04 with Node 20 and Emscripten -3.1.44 (for the exact versions, see [`minimum-versions.inc.sh`](../build/minimum-versions.inc.sh)) +Keyman Core, Keyman for Android, Keyman for Linux and +Keyman for Web. These images are based on the Ubuntu 24.04 +with Node 20 and Emscripten 3.1.58 (for the exact versions, +see [`minimum-versions.inc.sh`](../build/minimum-versions.inc.sh)) and are named e.g. `keyman-core-ci:default`. The versions can be changed, e.g. @@ -41,108 +42,19 @@ Once the image is built, it may be used to build parts of Keyman. It is possible to build locally with these images: -- Keyman Core +```shell +# build 'Keyman Core' in docker +resources/docker-images/run.sh core -- core/build.sh --debug build +``` - ```shell - # build 'Keyman Core' in docker - # keep build artifacts separate - mkdir -p $(git rev-parse --show-toplevel)/core/build/docker-core - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - -v $(git rev-parse --show-toplevel)/core/build/docker-core:/home/build/build/core/build \ - keymanapp/keyman-core-ci:default \ - core/build.sh --debug build - ``` - - Note: Since the generated binaries are platform dependent we put them in a container - specific directory. - -- Keyman for Linux - - ```shell - # build 'Keyman for Linux' installation in docker - # keep build artifacts separate - mkdir -p $(git rev-parse --show-toplevel)/linux/build/docker-linux - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - -v $(git rev-parse --show-toplevel)/linux/build/docker-linux:/home/build/build/linux/build \ - -e DESTDIR=/tmp \ - keymanapp/keyman-linux-ci:default \ - linux/build.sh --debug build install - ``` - - Note: Since the generated binaries are platform dependent we put them in a container - specific directory. - -- Keyman Web - - ```shell - # build 'Keyman Web' in docker - docker run --privileged -it --rm \ - -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-web-ci:default \ - web/build.sh --debug - ``` - -- Keyman for Android - - ```shell - # build 'Keyman for Android' in docker - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-android-ci:default \ - android/build.sh --debug - ``` +Note: For Core and Linux we put the generated binaries in a +container specific directory because they are platform dependent. ## Running tests locally -- Keyman Core +To run the tests locally, use the `run.sh` script: - ```shell - # build 'Keyman Core' in docker - # keep build artifacts separate - mkdir -p $(git rev-parse --show-toplevel)/core/build/docker-core - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - -v $(git rev-parse --show-toplevel)/core/build/docker-core:/home/build/build/core/build \ - keymanapp/keyman-core-ci:default \ - core/build.sh --debug test - ``` - - Note: Since the generated binaries are platform dependent we put them in a container - specific directory. - -- Keyman for Linux - - ```shell - # build 'Keyman for Linux' installation in docker - # keep build artifacts separate - mkdir -p $(git rev-parse --show-toplevel)/linux/build/docker-linux - docker run --privileged -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - -v $(git rev-parse --show-toplevel)/linux/build/docker-linux:/home/build/build/linux/build \ - -e DESTDIR=/tmp \ - keymanapp/keyman-linux-ci:default \ - linux/build.sh --debug test - ``` - - Note: this requires the `--privileged` parameter in order for all tests to pass! - - Note: Since the generated binaries are platform dependent we put them in a container - specific directory. - -- Keyman Web - - ```shell - # build 'Keyman Web' in docker - docker run --privileged -it --rm \ - -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-web-ci:default \ - web/build.sh --debug test - ``` - - Note: this requires the `--privileged` parameter in order for all tests to pass! - -- Keyman for Android - - ```shell - # build 'Keyman for Android' in docker - docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \ - keymanapp/keyman-android-ci:default \ - android/build.sh --debug test - ``` +```shell +# Run common/web tests +resources/docker-images/run.sh web -- common/web/build.sh --debug test +``` diff --git a/resources/docker-images/run.sh b/resources/docker-images/run.sh new file mode 100755 index 0000000000..ffe136efff --- /dev/null +++ b/resources/docker-images/run.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Run build.sh script inside of a docker image. Pass the build script and parameters after --." \ + "android" \ + "core" \ + "linux" \ + "web" \ + "--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" + +builder_parse "$@" + +run_android() { + docker run -it --rm -v ${KEYMAN_ROOT}:/home/build/build \ + -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ + keymanapp/keyman-android-ci:default \ + "${builder_extra_params[@]}" +} + +run_core() { + mkdir -p ${KEYMAN_ROOT}/core/build/docker-core + docker run -it --rm -v ${KEYMAN_ROOT}:/home/build/build \ + -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ + keymanapp/keyman-core-ci:default \ + "${builder_extra_params[@]}" +} + +run_linux() { + mkdir -p ${KEYMAN_ROOT}/linux/build/docker-linux + docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ + -v ${KEYMAN_ROOT}/linux/build/docker-linux:/home/build/build/linux/build \ + -e DESTDIR=/tmp \ + keymanapp/keyman-linux-ci:default \ + "${builder_extra_params[@]}" +} + +run_web() { + docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ + keymanapp/keyman-web-ci:default \ + "${builder_extra_params[@]}" +} + +builder_run_action android run_android +builder_run_action core run_core +builder_run_action linux run_linux +builder_run_action web run_web From 4c1aa0f960bd60107dabd9b195965b7e8c3c1700 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:48:43 +1000 Subject: [PATCH 042/124] feat(windows): add basic modal install form --- .../main/Keyman.System.UpdateStateMachine.pas | 18 ++++++++++++++++-- .../desktop/kmshell/main/UfrmStartInstall.dfm | 18 ++++++++++++++++++ .../desktop/kmshell/main/UfrmStartInstall.pas | 13 ++++++------- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index f7cfd9ef28..edcc649da4 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -33,6 +33,7 @@ uses httpuploader, Keyman.System.UpdateCheckResponse, Keyman.System.ExecuteHistory, + UfrmStartInstall, UfrmDownloadProgress; const @@ -891,6 +892,7 @@ function WaitingRestartState.HandleKmShell; var SavedPath : String; Filenames : TStringDynArray; + frmStartInstall : TfrmStartInstall; begin KL.Log('WaitingRestartState.HandleKmShell Enter'); // Still can't go if keyman has run @@ -918,8 +920,19 @@ begin else begin KL.Log('WaitingRestartState.HandleKmShell is good to install'); - ChangeState(InstallingState); - Result := kmShellExit; + // TODO Pop up toast here to ask user if we want to continue + frmStartInstall := TfrmStartInstall.Create(nil); + try + if frmStartInstall.ShowModal = mrOk then + begin + ChangeState(InstallingState); + Result := kmShellExit; + end + else + Result := kmShellContinue; + finally + frmStartInstall.Free; + end; end; end; end; @@ -994,6 +1007,7 @@ begin KL.Log('TUpdateStateMachine.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); // switch -au for auto update in silent mode. // We will need to add the pop up that says install update now yes/no + // This will run the setup executable which will ask for elevated permissions FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 end else diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm index 4414a07c10..66d008bf2c 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm @@ -13,4 +13,22 @@ object frmStartInstall: TfrmStartInstall OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 + object Install: TButton + Left = 168 + Top = 240 + Width = 75 + Height = 25 + Caption = 'Install' + TabOrder = 0 + OnClick = InstallClick + end + object Later: TButton + Left = 288 + Top = 240 + Width = 75 + Height = 25 + Caption = 'Later' + TabOrder = 1 + OnClick = LaterClick + end end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas index dce20b6cce..7b88ddbeab 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -9,11 +9,10 @@ uses type TfrmStartInstall = class(TfrmKeymanBase) - LabelMessage: TLabel; - InstallButton: TButton; - CancelButton: TButton; - procedure InstallButtonClick(Sender: TObject); - procedure CancelButtonClick(Sender: TObject); + Install: TButton; + Later: TButton; + procedure InstallClick(Sender: TObject); + procedure LaterClick(Sender: TObject); private public end; @@ -25,12 +24,12 @@ implementation {$R *.dfm} -procedure TfrmStartInstall.InstallButtonClick(Sender: TObject); +procedure TfrmStartInstall.InstallClick(Sender: TObject); begin ModalResult := mrOk; end; -procedure TfrmStartInstall.CancelButtonClick(Sender: TObject); +procedure TfrmStartInstall.LaterClick(Sender: TObject); begin ModalResult := mrCancel; end; From 8df507d97d13a4b31ec82c57ac20fbae8d49e6be Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:08:51 +1000 Subject: [PATCH 043/124] feat(windows): add menuframe_update image 4 config --- .../src/desktop/kmshell/xml/menuframe_update.jpg | Bin 0 -> 1404 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 windows/src/desktop/kmshell/xml/menuframe_update.jpg diff --git a/windows/src/desktop/kmshell/xml/menuframe_update.jpg b/windows/src/desktop/kmshell/xml/menuframe_update.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6eeb76bfd6f64f987667906903678cbd227dc21b GIT binary patch literal 1404 zcmbVKX;4#F6g~;rNJ1nK5GY98NOfS^h%7}xFbV{rVAuqlfsjQhB!MIdIA9Gb&=$LZ zwjctfRG~~Og#v|EWDx-!s-8V=2B;X z26jM!9k4?z(If|j`i8-(u{Z!o383Ez*IJeK{{@OvrjejhADT)@vRVs3*om-fnl>57 zXaXlOOe8=!4`HrUp_C$AjqoO^LW*PjTah9u71zN7-wjiy3=c(Fgr+U{fe?Zq1h^0n zsgM9kpamzCm1t!TxPlAHexL?1WPk=FC?|mot)(H377in%K}vqG8=)|S&~8@ovsv*A zK`!w~BLQjQ4k}O}P23Og^F|K#y6) zi-y?$^|lJfb%pbWJ!1D!r$$5~K2VU*Ak)ZX5{XQwQYkbhoyla-84MGanW+hj&0;W2 zElt_x919Bz=9;xumK-ZHjs*v62L=tDf<1x22CNJ0(GMb{ONrmhe}h0o!W1gan9e|i zMpGaXjEsmRBistHvrrvKY_i#Uz7NHGznHoqg|kifMFnl+?z#>O(QU&f=XiCtG2L>l zm9@=g2j1ITwr+QEb#s5uL$GJBub;m#AT%sIA`+dvL@JXXI-Kxy9-#(4LPLAObQ0N$bEAgx-rlPXJ?x5avgGJN4=X=ayx;aySMQ| z=kfckK&^7o>2I+{(s|>;rn&Ix&e`XYiLXj7y4;qX@;O*=d{XydorfwUqZ$LLiXEhy zRP6H~4f@_XnvrzoW5T%KX$Knn)U z881h}$Y^;@LR!jjA1UqgZaLRWU;TBhppY9n^VWHybGcq+l3o?vT!z8HHaE9~wM7^t z7LV@rt{JqZ>Mk=`xn5@Zizk9)fe9|~f(q!*6Ic6siIXny@${hh{`42#Ma!KJp!nfC*J z-1dxDR^`y}(%8W0_9&~a)w?$;w)UD|7Hn5J>OMVisIT)Ok5_&nD|Vuk)pOOu=~D5< Yp{(p1BS(7H*d8rVtA$gA18vyQKR^b-kN^Mx literal 0 HcmV?d00001 From d6c532032539bb60cbbd4da3c6297c213c41f6e0 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 29 Aug 2024 08:45:47 +0700 Subject: [PATCH 044/124] chore(linux): address code review comments - use KEYMAN_USE_NVM and KEYMAN_USE_EMSDK - pre-install node (where necessary) to prevent having to do it on each build - adjust to current `master` - fix a few bugs in the Dockerfile - get required node version from package.json (through `shellHelperFunctions.sh`) --- resources/docker-images/android/Dockerfile | 5 +-- resources/docker-images/base/Dockerfile | 8 +++- resources/docker-images/build.sh | 20 +++++----- resources/docker-images/core/Dockerfile | 42 ++++++++++----------- resources/docker-images/linux/Dockerfile | 5 +-- resources/docker-images/web/Dockerfile | 44 +++++++++++----------- resources/shellHelperFunctions.sh | 6 ++- 7 files changed, 68 insertions(+), 62 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index 5cd8fb1e63..1e0dd089b7 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -1,7 +1,7 @@ # Copyright (c) 2024 SIL International. All rights reserved. -ARG BASE_VERSION=latest -FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +ARG BASE_VERSION=default +FROM keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL International." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Android Build Image" @@ -25,7 +25,6 @@ RUN < /usr/bin/bashwrapper echo "export ANDROID_HOME=$DIR_SDK" >> /usr/bin/bashwrapper echo "export JAVA_HOME_${JAVA_VERSION}=/usr/lib/jvm/java-${JAVA_VERSION}-openjdk-amd64" >> /usr/bin/bashwrapper EOF diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index 116d4b8084..a22328d565 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -1,7 +1,7 @@ # Copyright (c) 2024 SIL International. All rights reserved. ARG UBUNTU_VERSION=latest -FROM --platform=amd64 ubuntu:${UBUNTU_VERSION} +FROM ubuntu:${UBUNTU_VERSION} LABEL org.opencontainers.image.authors="SIL International." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" @@ -28,3 +28,9 @@ RUN apt-get -q -y update && \ # Allow build user to use `sudo` RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +RUN < /usr/bin/bashwrapper +#!/bin/bash +export KEYMAN_USE_NVM=1 +export KEYMAN_USE_EMSDK=1 +EOF diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 294d5f463a..d1fdc41eeb 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ . "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh" +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" builder_describe \ "Build docker images" \ @@ -18,8 +19,6 @@ builder_describe \ ":linux" \ ":web" \ "--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ - "--node=NODE_MAJOR Node version (default: ${KEYMAN_MIN_VERSION_NODE_MAJOR})" \ - "--emscripten=EMSCRIPTEN_VERSION Emscripten version (default: ${KEYMAN_MIN_VERSION_EMSCRIPTEN})" \ "--no-cache Force rebuild of docker images" \ "build" @@ -49,11 +48,11 @@ _add_build_args() { _convert_parameters_to_build_args() { build_args=() build_version= + local required_node_version="$(_print_expected_node_version)" - _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" - _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java - _add_build_args NODE_MAJOR KEYMAN_MIN_VERSION_NODE_MAJOR node - _add_build_args EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emscr + _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" + _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java + _add_build_args REQUIRED_NODE_VERSION required_node_version "" if [[ -n "${BASE_VERSION:-}" ]]; then build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}") @@ -61,8 +60,7 @@ _convert_parameters_to_build_args() { } _is_default_values() { - [[ -z "${UBUNTU_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]] && \ - [[ -z "${NODE_MAJOR:-}" ]] && [[ -z "${EMSCRIPTEN_VERSION:-}" ]] + [[ -z "${UBUNTU_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]] } build_action() { @@ -86,14 +84,14 @@ build_action() { OPTION_NO_CACHE="--no-cache" fi - cd "${platform}" || true + cd "${platform}" # shellcheck disable=SC2248 - docker build ${OPTION_NO_CACHE:-} -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . + docker build ${OPTION_NO_CACHE:-} --platform amd64 -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . # If the user didn't specify particular versions we will additionaly create an image # with the tag 'default'. if _is_default_values; then builder_echo debug "Setting default tag for ${platform}" - docker build . -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" + docker build . --platform amd64 -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" fi cd - || true builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 9b7073b7a2..d4a74eda4e 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -1,7 +1,10 @@ # Copyright (c) 2024 SIL International. All rights reserved. +# ARGS used in this file: +# - ARG BASE_VERSION=default +# - ARG REQUIRED_NODE_VERSION=18 -ARG BASE_VERSION -FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +ARG BASE_VERSION=default +FROM keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL International." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" @@ -22,25 +25,14 @@ RUN apt-get install -qy python3 python3-setuptools python3-coverage \ rm /tmp/control #### TMP END -# Install node -ARG NODE_MAJOR -RUN set -eu; \ - NODE_VERSION=$(curl -sSL https://unofficial-builds.nodejs.org/download/release/ | cut -d'>' -f2 | cut -d'/' -f1 | grep v${NODE_MAJOR} | sort -V | tail -1) && \ - echo "Installing node version ${NODE_VERSION}" && \ - curl -fsSLO --compressed "https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" && \ - tar -xJf "node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" -C /usr/local --strip-components=1 --no-same-owner && \ - ln -s /usr/local/bin/node /usr/local/bin/nodejs - -# Install emscripten -ARG EMSCRIPTEN_VERSION -RUN echo "Installing emscripten version ${EMSCRIPTEN_VERSION}" && \ - cd /usr/share && \ - git clone https://github.com/emscripten-core/emsdk.git && \ - cd emsdk && \ - ./emsdk install ${EMSCRIPTEN_VERSION} && \ - ./emsdk activate ${EMSCRIPTEN_VERSION} && \ - echo "#!/bin/bash" > /usr/bin/bashwrapper && \ - echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper +# Install NVM +RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash +RUN <> /usr/bin/bashwrapper +PATH=/home/build/.keyman/node:\$PATH +export NVM_DIR="$HOME/.nvm" +. /home/build/.nvm/nvm.sh +EOF # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper @@ -58,6 +50,14 @@ RUN chmod +x /usr/bin/bashwrapper && \ # now, switch to build user USER build +# Pre-install node +ARG REQUIRED_NODE_VERSION=18 +RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ + export NVM_DIR="/home/build/.nvm" && \ + . /home/build/.nvm/nvm.sh && \ + nvm install "${REQUIRED_NODE_VERSION}" && \ + nvm use "${REQUIRED_NODE_VERSION}" + VOLUME /home/build/build WORKDIR /home/build/build diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile index 6766aa0859..134bc1d1a5 100644 --- a/resources/docker-images/linux/Dockerfile +++ b/resources/docker-images/linux/Dockerfile @@ -1,7 +1,7 @@ # Copyright (c) 2024 SIL International. All rights reserved. -ARG BASE_VERSION -FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +ARG BASE_VERSION=default +FROM keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL International." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Linux Build Image" @@ -29,7 +29,6 @@ RUN LCOV_VERSION=$(dpkg -s lcov | grep Version | cut -d' ' -f2) && \ RUN mkdir -p /var/run/1000 && \ chown build:build /var/run/1000 && \ - echo "#!/bin/bash" > /usr/bin/bashwrapper && \ echo "export XDG_RUNTIME_DIR=/var/run/1000" >> /usr/bin/bashwrapper COPY run-tests.sh /usr/bin/run-tests.sh diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 83aa5432d9..d438ca86ff 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -1,36 +1,28 @@ # Copyright (c) 2024 SIL International. All rights reserved. +# ARGS used in this file: +# - ARG BASE_VERSION=default +# - ARG REQUIRED_NODE_VERSION=18 -ARG BASE_VERSION -FROM --platform=amd64 keymanapp/keyman-base-ci:${BASE_VERSION} +ARG BASE_VERSION=default +FROM keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL International." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman for Web Build Image" USER root -RUN apt-get install -qy git jq xvfb xserver-xephyr metacity +RUN apt-get install -qy git jq xvfb xserver-xephyr metacity libevent-2.1-7t64 COPY run-tests.sh /usr/bin/run-tests.sh -# Install node -ARG NODE_MAJOR -RUN set -eu; \ - NODE_VERSION=$(curl -sSL https://unofficial-builds.nodejs.org/download/release/ | cut -d'>' -f2 | cut -d'/' -f1 | grep v${NODE_MAJOR} | sort -V | tail -1) && \ - echo "Installing node version ${NODE_VERSION}" && \ - curl -fsSLO --compressed "https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" && \ - tar -xJf "node-${NODE_VERSION}-linux-x64-glibc-217.tar.xz" -C /usr/local --strip-components=1 --no-same-owner && \ - ln -s /usr/local/bin/node /usr/local/bin/nodejs - -# Install emscripten -ARG EMSCRIPTEN_VERSION -RUN echo "Installing emscripten version ${EMSCRIPTEN_VERSION}" && \ - cd /usr/share && \ - git clone https://github.com/emscripten-core/emsdk.git && \ - cd emsdk && \ - ./emsdk install ${EMSCRIPTEN_VERSION} && \ - ./emsdk activate ${EMSCRIPTEN_VERSION} && \ - echo "#!/bin/bash" > /usr/bin/bashwrapper && \ - echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper +# Install NVM +RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash +RUN <> /usr/bin/bashwrapper +PATH=/home/build/.keyman/node:\$PATH +export NVM_DIR="$HOME/.nvm" +. /home/build/.nvm/nvm.sh +EOF # Keyman Web RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ @@ -65,6 +57,14 @@ RUN chmod +x /usr/bin/bashwrapper && \ # now, switch to build user USER build +# Pre-install node +ARG REQUIRED_NODE_VERSION=18 +RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ + export NVM_DIR="/home/build/.nvm" && \ + . /home/build/.nvm/nvm.sh && \ + nvm install "${REQUIRED_NODE_VERSION}" && \ + nvm use "${REQUIRED_NODE_VERSION}" + VOLUME /home/build/build WORKDIR /home/build/build diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 7ded208d44..68d498cb4d 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -272,10 +272,14 @@ verify_npm_setup() { popd > /dev/null } +_print_expected_node_version() { +"$JQ" -r '.engines.node' "$KEYMAN_ROOT/package.json" +} + # Use nvm to select a node version according to package.json # see /docs/build/node.md _select_node_version_with_nvm() { - local REQUIRED_NODE_VERSION="$("$JQ" -r '.engines.node' "$KEYMAN_ROOT/package.json")" + local REQUIRED_NODE_VERSION="$(_print_expected_node_version)" if [[ $BUILDER_OS != win ]]; then # launch nvm in a sub process, see _builder_nvm.sh for details From 1772fdf05581390da4156ad23623cad41ddb2235 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 31 Aug 2024 23:06:28 +0700 Subject: [PATCH 045/124] chore(linux): pre-install EMSCRIPTEN Also add test action to build.sh which builds all images and then tests them by running configure,build,test on each. Also add new dependencies to web image which Playwright requires. --- resources/docker-images/base/Dockerfile | 1 - resources/docker-images/build.sh | 22 ++++++++++++++++++---- resources/docker-images/core/Dockerfile | 18 +++++++++++++++++- resources/docker-images/web/Dockerfile | 25 +++++++++++++++++++++++-- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index a22328d565..b7fa3dce72 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -32,5 +32,4 @@ RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers RUN < /usr/bin/bashwrapper #!/bin/bash export KEYMAN_USE_NVM=1 -export KEYMAN_USE_EMSDK=1 EOF diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index d1fdc41eeb..a9c68c4106 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -20,7 +20,8 @@ builder_describe \ ":web" \ "--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ "--no-cache Force rebuild of docker images" \ - "build" + "build" \ + "test" builder_parse "$@" @@ -50,9 +51,10 @@ _convert_parameters_to_build_args() { build_version= local required_node_version="$(_print_expected_node_version)" - _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" - _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java - _add_build_args REQUIRED_NODE_VERSION required_node_version "" + _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" + _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java + _add_build_args REQUIRED_NODE_VERSION required_node_version node + _add_build_args REQUIRED_EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emsdk if [[ -n "${BASE_VERSION:-}" ]]; then build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}") @@ -97,6 +99,13 @@ build_action() { builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" } +test_action() { + local platform=$1 + + builder_echo debug "Testing image for ${platform}" + ./run.sh ${platform} -- ./build.sh configure,build,test:${platform} +} + if builder_has_action build; then build_action base BASE_VERSION="${build_version}" @@ -105,3 +114,8 @@ if builder_has_action build; then builder_run_action build:linux build_action linux builder_run_action build:web build_action web fi + +builder_run_action test:android test_action android +builder_run_action test:core test_action core +builder_run_action test:linux test_action linux +builder_run_action test:web test_action web diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index d4a74eda4e..54f5b92b66 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -34,6 +34,20 @@ export NVM_DIR="$HOME/.nvm" . /home/build/.nvm/nvm.sh EOF +# Pre-install emscripten +USER build +ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 +RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \ + export EMSDK_KEEP_DOWNLOADS=1 && \ + cd /home/build/ && \ + git clone https://github.com/emscripten-core/emsdk.git && \ + cd emsdk && \ + ./emsdk install ${REQUIRED_EMSCRIPTEN_VERSION} && \ + ./emsdk activate ${REQUIRED_EMSCRIPTEN_VERSION} +USER root +RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper && \ + echo "export KEYMAN_USE_EMSDK=1" >> /usr/bin/bashwrapper + # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper @@ -56,7 +70,9 @@ RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ export NVM_DIR="/home/build/.nvm" && \ . /home/build/.nvm/nvm.sh && \ nvm install "${REQUIRED_NODE_VERSION}" && \ - nvm use "${REQUIRED_NODE_VERSION}" + nvm use "${REQUIRED_NODE_VERSION}" && \ + cd /home/build/emsdk/upstream/emscripten && \ + npm install VOLUME /home/build/build WORKDIR /home/build/build diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index d438ca86ff..7adbae6106 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -11,7 +11,12 @@ LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman for Web Build Image" USER root -RUN apt-get install -qy git jq xvfb xserver-xephyr metacity libevent-2.1-7t64 +RUN apt-get install -qy git jq xvfb xserver-xephyr metacity +# For playwright: +RUN apt-get install -qy libevent-2.1-7t64 libxslt1.1 libwoff1 libvpx9 \ + libgstreamer-plugins-bad1.0-0 libwebpdemux2 libharfbuzz-icu0 \ + libenchant-2-2 libsecret-1-0 libhyphen0 libmanette-0.2-0 libflite1 \ + gstreamer1.0-libav COPY run-tests.sh /usr/bin/run-tests.sh @@ -24,6 +29,20 @@ export NVM_DIR="$HOME/.nvm" . /home/build/.nvm/nvm.sh EOF +# Pre-install emscripten +USER build +ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 +RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \ + export EMSDK_KEEP_DOWNLOADS=1 && \ + cd /home/build/ && \ + git clone https://github.com/emscripten-core/emsdk.git && \ + cd emsdk && \ + ./emsdk install ${REQUIRED_EMSCRIPTEN_VERSION} && \ + ./emsdk activate ${REQUIRED_EMSCRIPTEN_VERSION} +USER root +RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper && \ + echo "export KEYMAN_USE_EMSDK=1" >> /usr/bin/bashwrapper + # Keyman Web RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ apt-get -qy install ./google-chrome-stable_current_amd64.deb && \ @@ -63,7 +82,9 @@ RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ export NVM_DIR="/home/build/.nvm" && \ . /home/build/.nvm/nvm.sh && \ nvm install "${REQUIRED_NODE_VERSION}" && \ - nvm use "${REQUIRED_NODE_VERSION}" + nvm use "${REQUIRED_NODE_VERSION}" && \ + cd /home/build/emsdk/upstream/emscripten && \ + npm install VOLUME /home/build/build WORKDIR /home/build/build From 7860ddce5b1302fbb9297f543842e4ae6201cadd Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 2 Sep 2024 20:11:23 +1000 Subject: [PATCH 046/124] feat(windows): add update to strings.xml --- windows/src/desktop/kmshell/xml/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 824ce9dfc7..62125399ff 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -695,6 +695,10 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to + + + + Update From 1667cfed097a8ea618138f5896d6eeb05299d811 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:38:09 +1000 Subject: [PATCH 047/124] feat(windows): clean up ready for review --- common/windows/delphi/general/RegistryKeys.pas | 2 -- .../src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md | 4 ++-- windows/src/desktop/kmshell/main/UfrmMain.pas | 2 -- windows/src/desktop/kmshell/main/initprog.pas | 2 ++ .../insthelper/Keyman.System.Install.EnginePostInstall.pas | 3 +-- windows/src/engine/keyman/main.pas | 3 --- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index efd60b4148..0d1ba5484e 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -162,7 +162,6 @@ const SRegKey_KeymanDesktop_CU = SRegKey_KeymanDesktopRoot_CU; SRegKey_KeymanDesktop_LM = SRegKey_KeymanDesktopRoot_LM; - { Other Keyman Settings } SRegValue_DeadkeyConversionMode = 'deadkey conversion mode'; // CU // I4552 @@ -300,7 +299,6 @@ const SRegKey_KeymanDeveloperRoot_LM = SRegKey_KeymanRoot_LM + '\Keyman Developer'; // LM CU SRegKey_KeymanDeveloper_LM = SRegKey_KeymanDeveloperRoot_LM; // LM CU - SRegKey_IDE_CU = SRegKey_KeymanDeveloper_CU + '\IDE'; // CU SRegKey_IDEDock_CU = SRegKey_IDE_CU + '\Dock'; // CU SRegKey_IDEFiles_CU = SRegKey_IDE_CU + '\Files'; // CU diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md index 59c735eeb2..160d54b2c1 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md +++ b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md @@ -6,6 +6,6 @@ stateDiagram Downloading --> Installing Downloading --> WaitingRestart WaitingRestart --> Installing - Installing --> WaitingPostInstall - WaitingPostInstall --> Idle + Installing --> PostInstall + PostInstall --> Idle ``` diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 5df7c2aa6a..5f16aeb047 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -847,8 +847,6 @@ begin KL.Log('TrmfMain: Executing Update_ApplyNow Failed'); end; - - procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 88d08ef4d1..79f1f60a60 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -253,6 +253,8 @@ begin else if s = '-?' then FMode := fmHelpKMShell else if s = '-h' then FMode := fmHelp else if s = '-t' then FMode := fmTextEditor + //TODO: will remove -ouc not used + // -buc uses the Statemachine can be used for external scripts to force a check else if s = '-ouc' then FMode := fmOnlineUpdateCheck else if s = '-buc' then FMode := fmBackgroundUpdateCheck else if s = '-bd' then FMode := fmBackgroundDownload diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 4eea8adb59..6b0fe419b2 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -36,7 +36,6 @@ begin Result := False; UpdateStr := 'usPostInstall'; - //KL.Log('SetBackgroundState State Entry'); if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_CU), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then begin try @@ -54,7 +53,7 @@ begin end else begin - // couldn't open registry key + // TODO: couldn't open registry key end; end; diff --git a/windows/src/engine/keyman/main.pas b/windows/src/engine/keyman/main.pas index 9f1dbc9b3f..8fcce5c014 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -78,11 +78,8 @@ var hMutex: Cardinal; begin - KL.Log('Keyman RunProgram'); if not ValidateParameters(FCommand) then Exit; - // TODO set atom application running - KL.Log('Calling RecordKeymanStarted'); RecordKeymanStarted; hProgramMutex := CreateMutex(nil, False, 'KeymanEXE70'); From 85a998bb732a8e072b3635a30c86adda91f943eb Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:31:49 +1000 Subject: [PATCH 048/124] feat(windows): rename executehistory module for clarity --- ...tory.pas => Keyman.System.ExecutionHistory.pas} | 14 +++++++++----- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 14 +++++++------- .../main/Keyman.System.UpdateStateMachine.pas | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) rename common/windows/delphi/general/{Keyman.System.ExecuteHistory.pas => Keyman.System.ExecutionHistory.pas} (77%) diff --git a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas similarity index 77% rename from common/windows/delphi/general/Keyman.System.ExecuteHistory.pas rename to common/windows/delphi/general/Keyman.System.ExecutionHistory.pas index e146f8fa69..08fd015c88 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -1,4 +1,12 @@ -unit Keyman.System.ExecuteHistory; +unit Keyman.System.ExecutionHistory; + +{ + Copyright: © SIL International. + + This module provides functionality to track the execution state of the Keyman + engine. It uses a global atom to record whether Keyman has started during the + current session and checks if it has previously run. +} interface @@ -25,7 +33,6 @@ begin if GetLastError <> ERROR_FILE_NOT_FOUND then RaiseLastOSError; atom := GlobalAddAtom(AtomName); - KL.Log('RecordKeymanStarted: True'); Result := True; if atom = 0 then RaiseLastOSError; @@ -47,13 +54,10 @@ begin begin if GetLastError <> ERROR_SUCCESS then RaiseLastOSError; - - KL.Log('HasKeymanRun: Keyman Has Run'); Result := True; end else begin - KL.Log('HasKeymanRun: Keyman Has Run'); Result := False; end; diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 805a79aa58..13cf5a79de 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -183,7 +183,7 @@ uses Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas', Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', - Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas', + Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas', UfrmStartInstall in 'main\UfrmStartInstall.pas' {Form1}; {$R VERSION.RES} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 79061ffe4a..3715f94659 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -359,7 +359,7 @@ - +
    Form1
    dfm @@ -425,12 +425,6 @@ False - - - kmshell.exe - true - - kmshell.exe @@ -443,6 +437,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index f7cfd9ef28..c91fdc858d 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -32,7 +32,7 @@ uses httpuploader, Keyman.System.UpdateCheckResponse, - Keyman.System.ExecuteHistory, + Keyman.System.ExecutionHistory, UfrmDownloadProgress; const From eff8a8828c4bbbfe4de5f252cffd70fa95856c4a Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:07:17 +1000 Subject: [PATCH 049/124] feat(windows): resize and add title for install now Update the title bar to say Keyman update for the install now pop up. Also resized to make it smaller and inline with other windows applications. --- .../desktop/kmshell/main/UfrmStartInstall.dfm | 29 ++++++++++++++----- .../desktop/kmshell/main/UfrmStartInstall.pas | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm index 66d008bf2c..71fcfe2737 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm @@ -1,9 +1,9 @@ object frmStartInstall: TfrmStartInstall Left = 0 Top = 0 - Caption = 'frmStartInstall' - ClientHeight = 299 - ClientWidth = 635 + Caption = 'Keyman Update' + ClientHeight = 225 + ClientWidth = 425 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -13,9 +13,22 @@ object frmStartInstall: TfrmStartInstall OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 + object InstallUpdate: TLabel + Left = 128 + Top = 96 + Width = 175 + Height = 19 + Caption = 'Keyman update available' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + end object Install: TButton - Left = 168 - Top = 240 + Left = 228 + Top = 184 Width = 75 Height = 25 Caption = 'Install' @@ -23,11 +36,11 @@ object frmStartInstall: TfrmStartInstall OnClick = InstallClick end object Later: TButton - Left = 288 - Top = 240 + Left = 336 + Top = 184 Width = 75 Height = 25 - Caption = 'Later' + Caption = 'Close' TabOrder = 1 OnClick = LaterClick end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas index 7b88ddbeab..419491ca1e 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -11,6 +11,7 @@ type TfrmStartInstall = class(TfrmKeymanBase) Install: TButton; Later: TButton; + InstallUpdate: TLabel; procedure InstallClick(Sender: TObject); procedure LaterClick(Sender: TObject); private From 0d6f655d498964da4a2c2d732af63bf946775fb6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 3 Sep 2024 15:44:08 +0200 Subject: [PATCH 050/124] chore(linux): fix build order --- resources/docker-images/README.md | 5 +++++ resources/docker-images/build.sh | 3 ++- resources/docker-images/run.sh | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index 8c5593e2fb..c36503ad6a 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -50,6 +50,11 @@ resources/docker-images/run.sh core -- core/build.sh --debug build Note: For Core and Linux we put the generated binaries in a container specific directory because they are platform dependent. +If you build both with Docker and directly with the build scripts, it is +advisable to run a `git clean -dxf` before switching between the two. The +reason is that the Docker images use a different user, so that paths +will be different. + ## Running tests locally To run the tests locally, use the `run.sh` script: diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index a9c68c4106..5090193237 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -115,7 +115,8 @@ if builder_has_action build; then builder_run_action build:web build_action web fi -builder_run_action test:android test_action android builder_run_action test:core test_action core builder_run_action test:linux test_action linux builder_run_action test:web test_action web +# Android uses artifacts from web, so it has to come after web +builder_run_action test:android test_action android diff --git a/resources/docker-images/run.sh b/resources/docker-images/run.sh index ffe136efff..c85c15e873 100755 --- a/resources/docker-images/run.sh +++ b/resources/docker-images/run.sh @@ -28,7 +28,6 @@ run_android() { } run_core() { - mkdir -p ${KEYMAN_ROOT}/core/build/docker-core docker run -it --rm -v ${KEYMAN_ROOT}:/home/build/build \ -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ keymanapp/keyman-core-ci:default \ @@ -38,6 +37,7 @@ run_core() { run_linux() { mkdir -p ${KEYMAN_ROOT}/linux/build/docker-linux docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ + -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ -v ${KEYMAN_ROOT}/linux/build/docker-linux:/home/build/build/linux/build \ -e DESTDIR=/tmp \ keymanapp/keyman-linux-ci:default \ @@ -46,10 +46,13 @@ run_linux() { run_web() { docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ + -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ keymanapp/keyman-web-ci:default \ "${builder_extra_params[@]}" } +mkdir -p ${KEYMAN_ROOT}/core/build/docker-core + builder_run_action android run_android builder_run_action core run_core builder_run_action linux run_linux From bfe1b45983608be526797c4670710dcfa3418f09 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:51:32 +1000 Subject: [PATCH 051/124] feat(windows): automatic updates option to config reg --- .../windows/delphi/general/RegistryKeys.pas | 2 + oem/firstvoices/windows/src/xml/strings.xml | 5 + windows/src/desktop/kmshell/kmshell.dpr | 3 +- windows/src/desktop/kmshell/kmshell.dproj | 6 +- .../main/Keyman.System.UpdateStateMachine.pas | 131 ++++++++++++++++-- .../main/UImportOlderVersionSettings.pas | 19 +-- .../kmshell/main/UfrmStartInstallNow.dfm | 50 +++++++ .../kmshell/main/UfrmStartInstallNow.pas | 38 +++++ windows/src/desktop/kmshell/main/initprog.pas | 1 + windows/src/desktop/kmshell/xml/strings.xml | 5 + windows/src/desktop/setup/RunTools.pas | 11 +- windows/src/desktop/setup/UfrmRunDesktop.pas | 6 +- windows/src/engine/keyman/keyman.dpr | 2 +- windows/src/engine/keyman/keyman.dproj | 2 +- windows/src/engine/keyman/main.pas | 2 +- .../engine/kmcomapi/util/utilkeymanoption.pas | 5 +- .../delphi/general/KeymanOptionNames.pas | 1 + 17 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm create mode 100644 windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 0d1ba5484e..7e7fe54c38 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -313,8 +313,10 @@ const SRegValue_ActiveProject_Filename = 'project filename'; SRegValue_ActiveProject_SourcePath = 'source path'; + SRegValue_AutomaticUpdates = 'automatic updates'; //CU SRegValue_CheckForUpdates = 'check for updates'; // CU SRegValue_LastUpdateCheckTime = 'last update check time'; // CU + SRegValue_ApplyNow = 'apply now'; // CU Start the install now even thought it will require an update SRegValue_UpdateCheck_UseProxy = 'update check use proxy'; // CU SRegValue_UpdateCheck_ProxyHost = 'update check proxy host'; // CU diff --git a/oem/firstvoices/windows/src/xml/strings.xml b/oem/firstvoices/windows/src/xml/strings.xml index d0708d2b4c..193e2c9f89 100644 --- a/oem/firstvoices/windows/src/xml/strings.xml +++ b/oem/firstvoices/windows/src/xml/strings.xml @@ -388,6 +388,11 @@ Show welcome screen + + + + Automatically download updates ready to install + diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 13cf5a79de..d0915bd865 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -184,7 +184,8 @@ uses Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas', - UfrmStartInstall in 'main\UfrmStartInstall.pas' {Form1}; + UfrmStartInstallNow in 'main\UfrmStartInstallNow.pas', + UfrmStartInstall in 'main\UfrmStartInstall.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 3715f94659..6784e9c6cb 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -360,8 +360,12 @@ + +
    frmInstallNow
    + dfm +
    -
    Form1
    +
    frmStartInstall
    dfm
    diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index d47927dab5..fa8820553c 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -33,6 +33,7 @@ uses httpuploader, Keyman.System.UpdateCheckResponse, UfrmStartInstall, + UfrmStartInstallNow, Keyman.System.ExecutionHistory, UfrmDownloadProgress; @@ -215,7 +216,7 @@ type TUpdateStateMachine = class private FForce: Boolean; - FAuto: Boolean; + FAutomaticUpdate: Boolean; FParams: TUpdateStateMachineParams; FErrorMessage: string; DownloadTempPath: string; @@ -247,6 +248,9 @@ type function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; + function GetAutomaticUpdate: Boolean; + function SetApplyNow(Value : Boolean): Boolean; + function GetApplyNow: Boolean; protected property State: TStateClass read GetState write SetState; @@ -325,7 +329,7 @@ begin FParams.Result := oucUnknown; FForce := AForce; - FAuto := True; // Default to automatically check, download, and install + FAutomaticUpdate := GetAutomaticUpdate; FIdle := IdleState.Create(Self); FUpdateAvailable := UpdateAvailableState.Create(Self); FDownloading := DownloadingState.Create(Self); @@ -408,9 +412,9 @@ begin try Registry.RootKey := HKEY_CURRENT_USER; KL.Log('SetRegistryState State Entry'); - if not Registry.OpenKey(SRegKey_KeymanEngine_LM, True) then + if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then begin - KL.Log('Failed to open registry key: ' + SRegKey_KeymanEngine_LM); + KL.Log('Failed to open registry key: ' + SRegKey_KeymanEngine_CU); Exit; end; @@ -444,7 +448,7 @@ begin Registry := TRegistryErrorControlled.Create; // I2890 try Registry.RootKey := HKEY_CURRENT_USER; - if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and Registry.ValueExists(SRegValue_Update_State) then + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_Update_State) then begin UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); KL.Log('CheckRegistryState State is:[' + Registry.ReadString(SRegValue_Update_State) + ']'); @@ -461,6 +465,77 @@ begin Result := UpdateState; end; +function TUpdateStateMachine.GetAutomaticUpdate: Boolean; // I2329 +var + Registry: TRegistryErrorControlled; + +begin + // check the registry value + Registry := TRegistryErrorControlled.Create; // I2890 + try + Registry.RootKey := HKEY_CURRENT_USER; + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_AutomaticUpdates) then + begin + Result := Registry.ReadBool(SRegValue_AutomaticUpdates); + end + else + begin + Result := True; // Default + end; + finally + Registry.Free; + end; +end; + +function TUpdateStateMachine.SetApplyNow(Value : Boolean): Boolean; +var + Registry: TRegistryErrorControlled; +begin + Result := False; + Registry := TRegistryErrorControlled.Create; + + try + Registry.RootKey := HKEY_CURRENT_USER; + if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then + begin + Exit; + end; + try + Registry.WriteBool(SRegValue_ApplyNow, Value); + Result := True; + except + on E: Exception do + begin + KL.Log('Failed to write to registry: ' + E.Message); + end; + end; + finally + Registry.Free; + end; +end; + +function TUpdateStateMachine.GetApplyNow: Boolean; +var + Registry: TRegistryErrorControlled; +begin + // check the registry value + Registry := TRegistryErrorControlled.Create; + try + Registry.RootKey := HKEY_CURRENT_USER; + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_ApplyNow) then + begin + Result := Registry.ReadBool(SRegValue_ApplyNow); + end + else + begin + Result := False; // Default + end; + finally + Registry.Free; + end; +end; + + function TUpdateStateMachine.CheckUpdateSchedule: Boolean; var RegistryErrorControlled :TRegistryErrorControlled; @@ -728,7 +803,7 @@ procedure UpdateAvailableState.Enter; begin // Enter UpdateAvailableState bucStateContext.SetRegistryState(usUpdateAvailable); - if bucStateContext.FAuto then + if bucStateContext.FAutomaticUpdate then begin StartDownloadProcess; end; @@ -746,7 +821,7 @@ end; function UpdateAvailableState.HandleKmShell; begin - if bucStateContext.FAuto then + if bucStateContext.FAutomaticUpdate then begin // we will use a new kmshell process to enable // the download as background process. @@ -766,8 +841,20 @@ begin end; procedure UpdateAvailableState.HandleInstallNow; +var + frmStartInstallNow : TfrmStartInstallNow; begin - ChangeState(DownloadingState); + // If user decides NOT to install now stay in UpdateAvailable State + frmStartInstallNow := TfrmStartInstallNow.Create(nil); + try + if frmStartInstallNow.ShowModal = mrOk then + begin + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState) + end + finally + frmStartInstallNow.Free; + end; end; function UpdateAvailableState.StateName; @@ -793,10 +880,17 @@ begin begin if HasKeymanRun then begin - ChangeState(WaitingRestartState); + if bucStateContext.GetApplyNow then + begin + bucStateContext.SetApplyNow(False); + ChangeState(InstallingState); + end + else + ChangeState(WaitingRestartState); end else begin + bucStateContext.SetApplyNow(False); ChangeState(InstallingState); end; end @@ -837,7 +931,8 @@ end; procedure DownloadingState.HandleInstallNow; begin - + // Already downloading set the registry apply now + bucStateContext.SetApplyNow(True); end; function DownloadingState.StateName; @@ -948,11 +1043,25 @@ begin end; procedure WaitingRestartState.HandleInstallNow; +var + frmStartInstallNow : TfrmStartInstallNow; begin // TODO: Check if keyman has run, and error trying to install // now when windows needs a restart ask the user if they // want to restart now, if users says no stay in this (waitingrestart) state - ChangeState(InstallingState); + + + // If user decides not to install now stay in WaitingRestart State + frmStartInstallNow := TfrmStartInstallNow.Create(nil); + try + if frmStartInstallNow.ShowModal = mrOk then + begin + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState) + end + finally + frmStartInstallNow.Free; + end; end; function WaitingRestartState.StateName; diff --git a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas index 007d854797..e4cdff54bb 100644 --- a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas +++ b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas @@ -1,18 +1,18 @@ (* Name: UImportOlderVersionSettings Copyright: Copyright (C) 2003-2017 SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 22 Feb 2011 Modified Date: 3 Jun 2014 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 22 Feb 2011 - mcdurdin - I2651 - Install does not set desired default options 22 Feb 2011 - mcdurdin - I2753 - Firstrun crashes because start with windows and auto update check options are set in Engine instead of Desktop 03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors @@ -24,7 +24,7 @@ unit UImportOlderVersionSettings; interface -function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753 +function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates,DoAutomaticUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753 implementation @@ -43,7 +43,7 @@ uses RegistryKeys, UImportOlderKeyboardUtils; -function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753 +function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates,DoAutomaticUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753 var n, I: Integer; v: Integer; @@ -136,6 +136,7 @@ begin if DoStartWithWindows then kmcom.Options['koStartWithWindows'].Value := True; // I2753 if DoCheckForUpdates then kmcom.Options['koCheckForUpdates'].Value := True; // I2753 + if DoAutomaticUpdates then kmcom.Options['koAutomaticUpdate'].Value := True; if DoAutomaticallyReportUsage then kmcom.Options['koAutomaticallyReportUsage'].Value := True; diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm new file mode 100644 index 0000000000..415796d5c8 --- /dev/null +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm @@ -0,0 +1,50 @@ +object frmStartInstallNow: TfrmStartInstallNow + Left = 0 + Top = 0 + Caption = 'Keyman Update' + ClientHeight = 225 + ClientWidth = 425 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + PixelsPerInch = 96 + TextHeight = 13 + object InstallUpdate: TLabel + Left = 69 + Top = 72 + Width = 296 + Height = 38 + Caption = + 'Installing Now will require a Keyman and Windows Restart Continu' + + 'e?' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + WordWrap = True + end + object Install: TButton + Left = 228 + Top = 184 + Width = 75 + Height = 25 + Caption = 'Install' + TabOrder = 0 + OnClick = InstallClick + end + object Later: TButton + Left = 336 + Top = 184 + Width = 75 + Height = 25 + Caption = 'Close' + TabOrder = 1 + OnClick = LaterClick + end +end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas new file mode 100644 index 0000000000..c1d153199b --- /dev/null +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas @@ -0,0 +1,38 @@ +unit UfrmStartInstallNow; + +interface + +uses + + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, UserMessages, StdCtrls, ExtCtrls, UfrmKeymanBase; + +type + TfrmStartInstallNow = class(TfrmKeymanBase) + Install: TButton; + Later: TButton; + InstallUpdate: TLabel; + procedure InstallClick(Sender: TObject); + procedure LaterClick(Sender: TObject); + private + public + end; + +var + frmStartInstall: TfrmStartInstallNow; + +implementation + +{$R *.dfm} + +procedure TfrmStartInstallNow.InstallClick(Sender: TObject); +begin + ModalResult := mrOk; +end; + +procedure TfrmStartInstallNow.LaterClick(Sender: TObject); +begin + ModalResult := mrCancel; +end; + +end. diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 79f1f60a60..d8c19b6309 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -658,6 +658,7 @@ begin Pos('installdefaults', FQuery) > 0, Pos('startwithwindows', FQuery) > 0, Pos('checkforupdates', FQuery) > 0, + Pos('automaticupdates', FQuery) > 0, FDisablePackages, FDefaultUILanguage, Pos('automaticallyreportusage', FQuery) > 0); // I2651, I2753 diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 62125399ff..678a05d690 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -412,6 +412,11 @@ Show welcome screen + + + + Automatically download updates ready to install + diff --git a/windows/src/desktop/setup/RunTools.pas b/windows/src/desktop/setup/RunTools.pas index 341f036c6c..0b654cfeec 100644 --- a/windows/src/desktop/setup/RunTools.pas +++ b/windows/src/desktop/setup/RunTools.pas @@ -82,7 +82,7 @@ type InstallSuccess: Boolean); function InstallMSI(msiLocation: TInstallInfoFileLocation; var InstallDefaults: Boolean; ContinueSetup: Boolean): Boolean; procedure ConfigFirstRun(StartKeyman,StartWithWindows, - CheckForUpdates,StartDisabled,StartWithConfiguration,InstallDefaults, + CheckForUpdates,AutomaticUpdates,StartDisabled,StartWithConfiguration,InstallDefaults, AutomaticallyReportUsage: Boolean); procedure PrepareForReboot(res: Cardinal; InstallDefaults: Boolean); function RestartWindows: Boolean; @@ -101,7 +101,7 @@ type destructor Destroy; override; procedure CheckInternetConnectedState; function DoInstall(Handle: THandle; - StartAfterInstall, StartWithWindows, CheckForUpdates, StartDisabled, + StartAfterInstall, StartWithWindows, CheckForUpdates, AutomaticUpdates, StartDisabled, StartWithConfiguration, InstallDefaults, AutomaticallyReportUsage, ContinueSetup: Boolean): Boolean; procedure LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean = True); procedure LogInfo(const msg: string; ShowDialogIfNotSilent: Boolean = False); @@ -194,7 +194,7 @@ begin end; function TRunTools.DoInstall(Handle: THandle; - StartAfterInstall, StartWithWindows, CheckForUpdates, StartDisabled, + StartAfterInstall, StartWithWindows, CheckForUpdates, AutomaticUpdates, StartDisabled, StartWithConfiguration, InstallDefaults, AutomaticallyReportUsage, ContinueSetup: Boolean): Boolean; var msiLocation: TInstallInfoFileLocation; @@ -224,7 +224,7 @@ begin Exit(False); end; - ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates, + ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates,AutomaticUpdates, StartDisabled,StartWithConfiguration,InstallDefaults,AutomaticallyReportUsage); Result := True; @@ -588,7 +588,7 @@ begin end; end; -procedure TRunTools.ConfigFirstRun(StartKeyman,StartWithWindows,CheckForUpdates, +procedure TRunTools.ConfigFirstRun(StartKeyman,StartWithWindows,CheckForUpdates,AutomaticUpdates, StartDisabled,StartWithConfiguration,InstallDefaults,AutomaticallyReportUsage: Boolean); var i: Integer; @@ -686,6 +686,7 @@ begin if StartWithWindows then s := s + 'StartWithWindows,'; if CheckForUpdates then s := s + 'CheckForUpdates,'; + if AutomaticUpdates then s := s + 'AutomaticUpdates,'; if AutomaticallyReportUsage then s := s + 'AutomaticallyReportUsage,'; if InstallDefaults then diff --git a/windows/src/desktop/setup/UfrmRunDesktop.pas b/windows/src/desktop/setup/UfrmRunDesktop.pas index 293df6a613..3374324d64 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.pas +++ b/windows/src/desktop/setup/UfrmRunDesktop.pas @@ -113,6 +113,7 @@ type FCanUpgrade9: Boolean; FCanUpgrade10: Boolean; FCheckForUpdates: Boolean; + FAutomaticUpdates: Boolean; FStartAfterInstall: Boolean; FStartWithWindows: Boolean; FAutomaticallyReportUsage: Boolean; @@ -523,7 +524,7 @@ begin SetupMSI; // I2644 if GetRunTools.DoInstall(Handle, FStartAfterInstall, FStartWithWindows, FCheckForUpdates, - FInstallInfo.StartDisabled, FInstallInfo.StartWithConfiguration, FInstallDefaults, + FAutomaticUpdates, FInstallInfo.StartDisabled, FInstallInfo.StartWithConfiguration, FInstallDefaults, FAutomaticallyReportUsage, FContinueSetup) then begin if not Silent and not FStartAfterInstall then // I2610 @@ -1032,6 +1033,7 @@ procedure TfrmRunDesktop.GetDefaultSettings; // I2651 begin FStartWithWindows := True; // I2607 FCheckForUpdates := True; // I2609 + FAutomaticUpdates := True; try with CreateHKCURegistry do // I2749 @@ -1041,6 +1043,8 @@ begin FCheckForUpdates := ValueExists(SRegValue_CheckForUpdates) and ReadBool(SRegValue_CheckForUpdates); FStartWithWindows := ValueExists(SRegValue_UpgradeRunKeyman) or (OpenKeyReadOnly('\' + SRegKey_WindowsRun_CU) and ValueExists(SRegValue_WindowsRun_Keyman)); + FAutomaticUpdates := ValueExists(SRegValue_AutomaticUpdates) and ReadBool(SRegValue_AutomaticUpdates); + end else if FCanUpgrade10 and OpenKeyReadOnly(SRegKey_KeymanEngine100_ProductOptions_Desktop_CU) then // I4293 begin diff --git a/windows/src/engine/keyman/keyman.dpr b/windows/src/engine/keyman/keyman.dpr index 2cff0c4b73..dadcca01fc 100644 --- a/windows/src/engine/keyman/keyman.dpr +++ b/windows/src/engine/keyman/keyman.dpr @@ -113,7 +113,7 @@ uses sentry in '..\..\..\..\common\windows\delphi\ext\sentry\sentry.pas', Keyman.System.KeymanSentryClient in '..\..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas', Keyman.System.LocaleStrings in '..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas', - Keyman.System.ExecuteHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecuteHistory.pas'; + Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas'; {$R ICONS.RES} {$R VERSION.RES} diff --git a/windows/src/engine/keyman/keyman.dproj b/windows/src/engine/keyman/keyman.dproj index 71cdb756d3..aa70b2ab7b 100644 --- a/windows/src/engine/keyman/keyman.dproj +++ b/windows/src/engine/keyman/keyman.dproj @@ -238,7 +238,7 @@ - + Cfg_2 diff --git a/windows/src/engine/keyman/main.pas b/windows/src/engine/keyman/main.pas index 8fcce5c014..1b6ed25b30 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -47,7 +47,7 @@ uses UfrmKeyman7Main, UserMessages, Klog, - Keyman.System.ExecuteHistory; + Keyman.System.ExecutionHistory; function ValidateParameters(var FCommand: Integer): Boolean; forward; function PassParametersToRunningInstance(FCommand: Integer): Boolean; forward; diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index ce1b8af33a..ec979eb215 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,14 +121,15 @@ type GroupName: string; end; -const KeymanOptionInfo: array[0..15] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 +const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 // Global options (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koSwitchLanguageForAllApplications; RegistryName: SRegValue_SwitchLanguageForAllApplications; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I2277 // I4393 (opt: koAltGrCtrlAlt; RegistryName: SRegValue_AltGrCtrlAlt; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koShowHints; RegistryName: SRegValue_EnableHints; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), - (opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'), + (opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'), + (opt: koAutomaticUpdate; RegistryName: SRegValue_AutomaticUpdates; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), (opt: koAutomaticallyReportErrors; RegistryName: SRegValue_AutomaticallyReportErrors; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I4393 (opt: koAutomaticallyReportUsage; RegistryName: SRegValue_AutomaticallyReportUsage; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I4393 diff --git a/windows/src/global/delphi/general/KeymanOptionNames.pas b/windows/src/global/delphi/general/KeymanOptionNames.pas index 27aef82ab5..07e0a85f7b 100644 --- a/windows/src/global/delphi/general/KeymanOptionNames.pas +++ b/windows/src/global/delphi/general/KeymanOptionNames.pas @@ -7,6 +7,7 @@ type // General options koKeyboardHotkeysAreToggle, koAltGrCtrlAlt, koReleaseShiftKeysAfterKeyPress, koShowHints, // I1256 + koAutomaticUpdate, // Startup options koTestKeymanFunctioning, koStartWithWindows, From 033cb227fe2f221620fcded57c99619204a59f6e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:37:41 +1000 Subject: [PATCH 052/124] feat(windows): changes update form now --- .../main/Keyman.System.UpdateStateMachine.pas | 65 ++++++++++++------- .../kmshell/main/UfrmStartInstallNow.dfm | 27 +++++--- .../kmshell/main/UfrmStartInstallNow.pas | 3 +- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index fa8820553c..63afb7e4d2 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -843,18 +843,29 @@ end; procedure UpdateAvailableState.HandleInstallNow; var frmStartInstallNow : TfrmStartInstallNow; + InstallNow : Boolean; begin - // If user decides NOT to install now stay in UpdateAvailable State - frmStartInstallNow := TfrmStartInstallNow.Create(nil); - try - if frmStartInstallNow.ShowModal = mrOk then - begin - bucStateContext.SetApplyNow(True); - ChangeState(InstallingState) - end - finally - frmStartInstallNow.Free; + + InstallNow := True; + if HasKeymanRun then + begin + frmStartInstallNow := TfrmStartInstallNow.Create(nil); + try + if frmStartInstallNow.ShowModal = mrOk then + InstallNow := True + else + InstallNow := False; + finally + frmStartInstallNow.Free; + end; end; + // If user decides NOT to install now stay in UpdateAvailable State + if InstallNow = True then + begin + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState) + end; + end; function UpdateAvailableState.StateName; @@ -1043,24 +1054,28 @@ begin end; procedure WaitingRestartState.HandleInstallNow; +// If user decides not to install now stay in WaitingRestart State var frmStartInstallNow : TfrmStartInstallNow; + InstallNow : Boolean; begin - // TODO: Check if keyman has run, and error trying to install - // now when windows needs a restart ask the user if they - // want to restart now, if users says no stay in this (waitingrestart) state - - - // If user decides not to install now stay in WaitingRestart State - frmStartInstallNow := TfrmStartInstallNow.Create(nil); - try - if frmStartInstallNow.ShowModal = mrOk then - begin - bucStateContext.SetApplyNow(True); - ChangeState(InstallingState) - end - finally - frmStartInstallNow.Free; + InstallNow := True; + if HasKeymanRun then + begin + frmStartInstallNow := TfrmStartInstallNow.Create(nil); + try + if frmStartInstallNow.ShowModal = mrOk then + InstallNow := True + else + InstallNow := False; + finally + frmStartInstallNow.Free; + end; + end; + if InstallNow = True then + begin + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState) end; end; diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm index 415796d5c8..ae93b5cd1e 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm @@ -13,14 +13,12 @@ object frmStartInstallNow: TfrmStartInstallNow OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 - object InstallUpdate: TLabel - Left = 69 - Top = 72 - Width = 296 + object UpdateMessage: TLabel + Left = 56 + Top = 88 + Width = 289 Height = 38 - Caption = - 'Installing Now will require a Keyman and Windows Restart Continu' + - 'e?' + Caption = 'Keyman and Windows will be restarted' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -16 @@ -29,12 +27,25 @@ object frmStartInstallNow: TfrmStartInstallNow ParentFont = False WordWrap = True end + object UpdateNow: TLabel + Left = 56 + Top = 40 + Width = 115 + Height = 25 + Caption = 'Update Now' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -21 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + end object Install: TButton Left = 228 Top = 184 Width = 75 Height = 25 - Caption = 'Install' + Caption = 'Update' TabOrder = 0 OnClick = InstallClick end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas index c1d153199b..aee57e9096 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas @@ -11,7 +11,8 @@ type TfrmStartInstallNow = class(TfrmKeymanBase) Install: TButton; Later: TButton; - InstallUpdate: TLabel; + UpdateMessage: TLabel; + UpdateNow: TLabel; procedure InstallClick(Sender: TObject); procedure LaterClick(Sender: TObject); private From 3f1236ab59ec18b27ada9935ceecb497f4a7f165 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:22:21 +1000 Subject: [PATCH 053/124] feat(windows): comments copyright formating --- .../windows/delphi/general/Keyman.System.ExecutionHistory.pas | 2 +- .../src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas | 2 +- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 1 - windows/src/desktop/kmshell/main/UfrmMain.pas | 2 +- windows/src/desktop/kmshell/main/UfrmStartInstall.pas | 4 +++- windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas | 4 +++- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas index 08fd015c88..4d3900659b 100644 --- a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -1,7 +1,7 @@ unit Keyman.System.ExecutionHistory; { - Copyright: © SIL International. + Copyright: © SIL Global. This module provides functionality to track the execution state of the Keyman engine. It uses a global atom to record whether Keyman has started during the diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index f095341c82..dcc6e28faa 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -1,6 +1,6 @@ (* Name: WebUpdateCheck - Copyright: Copyright (C) SIL International. + Copyright: Copyright (C) SIL Global. Documentation: Description: Create Date: 5 Dec 2023 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 63afb7e4d2..74c2433fa1 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -322,7 +322,6 @@ const { TUpdateStateMachine } constructor TUpdateStateMachine.Create(AForce : Boolean); -// var TSerailsedState : TUpdateState; // TODO: Remove begin inherited Create; FShowErrors := True; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 5f16aeb047..0403df9275 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -844,7 +844,7 @@ begin ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); FResult := TUtilExecute.Shell(0, ShellPath, '', '-an'); if not FResult then - KL.Log('TrmfMain: Executing Update_ApplyNow Failed'); + KL.Log('TrmfMain: Executing Update_ApplyNow Failed'); // TODO: Make error log end; procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas index 419491ca1e..f242b86327 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -1,5 +1,7 @@ unit UfrmStartInstall; - +{ + Copyright: © SIL Global. +} interface uses diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas index aee57e9096..6782a166a7 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas @@ -1,5 +1,7 @@ unit UfrmStartInstallNow; - +{ + Copyright: © SIL Global. +} interface uses From 475fd734e6ba6b18b6f0aa1aed85b1ce4c926d11 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 11 Sep 2024 14:29:01 +0200 Subject: [PATCH 054/124] docs(android): document gradle version in `minimum-versions` --- docs/minimum-versions.md | 1 + resources/build/minimum-versions.inc.sh | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md index f582632007..1f0040975e 100644 --- a/docs/minimum-versions.md +++ b/docs/minimum-versions.md @@ -63,6 +63,7 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_MIN_VERSION_NPM | 10.5.1 | | KEYMAN_MIN_VERSION_VISUAL_STUDIO | 2019 | | KEYMAN_VERSION_CLDR | 45 | +| KEYMAN_VERSION_GRADLE | 7.6.4 | | KEYMAN_VERSION_ICU | 73.1 | | KEYMAN_VERSION_ISO639_3 | 2024-05-22 | | KEYMAN_VERSION_JAVA | 11 | diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index 97f2509406..3b5f74a879 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -24,7 +24,8 @@ KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automati KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019 KEYMAN_MIN_VERSION_MESON=1.0.0 -KEYMAN_VERSION_ICU=73.1 # See /core/subprojects/icu-minimal.wrap +KEYMAN_VERSION_GRADLE=7.6.4 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties +KEYMAN_VERSION_ICU=73.1 # See /core/subprojects/icu-minimal.wrap # Language and runtime versions KEYMAN_VERSION_JAVA=11 # We're using Java/OpenJDK 11 From caba09a8d4c4671fc0bcfc6c5d691e4536d159da Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 11 Sep 2024 14:30:22 +0200 Subject: [PATCH 055/124] chore(linux): attempt to fix Android build in Docker These changes bring us one step further, but the build is still failing. --- resources/docker-images/android/Dockerfile | 5 +++-- resources/docker-images/base/Dockerfile | 23 ++++++++++++++++++++++ resources/docker-images/build.sh | 2 +- resources/docker-images/core/Dockerfile | 15 +------------- resources/docker-images/web/Dockerfile | 15 +------------- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index 1e0dd089b7..8c6833c0c1 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -8,6 +8,7 @@ LABEL org.opencontainers.image.title="Keyman Android Build Image" # Keyman for Android SHELL ["/bin/bash", "-c"] + # Starting with Ubuntu 24.04 sdkmanager is no longer available, instead # a version dependent package allows to install the cmdline tools ARG JAVA_VERSION=11 @@ -50,13 +51,13 @@ WORKDIR /home/build/build # Pre-install gradle. This will put files in ~/.gradle which will speed up builds. RUN mkdir -p $HOME/tmp/gradle/wrapper && \ - # KMEA uses gradle-7.5.1-bin + # KMEA uses gradle-7.6.4-bin curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \ curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.properties && \ curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradlew && \ chmod +x $HOME/tmp/gradlew && \ $HOME/tmp/gradlew --quiet && \ - # Some projects use gradle-7.5.1-all, so we pre-install that as well + # Some projects use gradle-7.6.4-all, so we pre-install that as well curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.jar && \ curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties && \ curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradlew && \ diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index b7fa3dce72..14bc0b36cc 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -33,3 +33,26 @@ RUN < /usr/bin/bashwrapper #!/bin/bash export KEYMAN_USE_NVM=1 EOF + +# Install NVM +RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash +RUN <> /usr/bin/bashwrapper +PATH=/home/build/.keyman/node:\$PATH +export NVM_DIR="$HOME/.nvm" +. /home/build/.nvm/nvm.sh +EOF + +RUN chmod +x /usr/bin/bashwrapper && \ + chown -R build:build $HOME + +USER build +# Pre-install node +ARG REQUIRED_NODE_VERSION=unset +RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ + export NVM_DIR="/home/build/.nvm" && \ + . /home/build/.nvm/nvm.sh && \ + nvm install "${REQUIRED_NODE_VERSION}" && \ + nvm use "${REQUIRED_NODE_VERSION}" + +USER root diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 5090193237..a3254a2f52 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -93,7 +93,7 @@ build_action() { # with the tag 'default'. if _is_default_values; then builder_echo debug "Setting default tag for ${platform}" - docker build . --platform amd64 -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" + docker build --platform amd64 -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" . fi cd - || true builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 54f5b92b66..4a678da31c 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -25,15 +25,6 @@ RUN apt-get install -qy python3 python3-setuptools python3-coverage \ rm /tmp/control #### TMP END -# Install NVM -RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash -RUN <> /usr/bin/bashwrapper -PATH=/home/build/.keyman/node:\$PATH -export NVM_DIR="$HOME/.nvm" -. /home/build/.nvm/nvm.sh -EOF - # Pre-install emscripten USER build ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 @@ -65,12 +56,8 @@ RUN chmod +x /usr/bin/bashwrapper && \ USER build # Pre-install node -ARG REQUIRED_NODE_VERSION=18 -RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ - export NVM_DIR="/home/build/.nvm" && \ +RUN export NVM_DIR="/home/build/.nvm" && \ . /home/build/.nvm/nvm.sh && \ - nvm install "${REQUIRED_NODE_VERSION}" && \ - nvm use "${REQUIRED_NODE_VERSION}" && \ cd /home/build/emsdk/upstream/emscripten && \ npm install diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 7adbae6106..f7ad7ee3d9 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -20,15 +20,6 @@ RUN apt-get install -qy libevent-2.1-7t64 libxslt1.1 libwoff1 libvpx9 \ COPY run-tests.sh /usr/bin/run-tests.sh -# Install NVM -RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash -RUN <> /usr/bin/bashwrapper -PATH=/home/build/.keyman/node:\$PATH -export NVM_DIR="$HOME/.nvm" -. /home/build/.nvm/nvm.sh -EOF - # Pre-install emscripten USER build ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 @@ -77,12 +68,8 @@ RUN chmod +x /usr/bin/bashwrapper && \ USER build # Pre-install node -ARG REQUIRED_NODE_VERSION=18 -RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \ - export NVM_DIR="/home/build/.nvm" && \ +RUN export NVM_DIR="/home/build/.nvm" && \ . /home/build/.nvm/nvm.sh && \ - nvm install "${REQUIRED_NODE_VERSION}" && \ - nvm use "${REQUIRED_NODE_VERSION}" && \ cd /home/build/emsdk/upstream/emscripten && \ npm install From e23e112e7a4bdabe24b115d03d287ec853421b86 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 11 Sep 2024 15:54:20 +0200 Subject: [PATCH 056/124] chore(linux): remove temporary code for Core image --- resources/docker-images/build.sh | 4 ---- resources/docker-images/core/.gitignore | 1 - resources/docker-images/core/Dockerfile | 11 ----------- 3 files changed, 16 deletions(-) delete mode 100644 resources/docker-images/core/.gitignore diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index a3254a2f52..6e81d8a27e 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -74,10 +74,6 @@ build_action() { if [[ "${platform}" == "base" ]]; then docker pull --platform "amd64" "ubuntu:${UBUNTU_VERSION:-${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER}}" - ### TMP code - elif [[ "${platform}" == "core" ]]; then - cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}" - ### TMP END elif [[ "${platform}" == "linux" ]]; then cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}" fi diff --git a/resources/docker-images/core/.gitignore b/resources/docker-images/core/.gitignore deleted file mode 100644 index 4db28ac495..0000000000 --- a/resources/docker-images/core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -control diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 4a678da31c..55ca0b1ef2 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -14,17 +14,6 @@ USER root RUN apt-get install -qy git jq llvm meson pkgconf \ xvfb xserver-xephyr metacity -#### TMP until we properly figure out the dependencies needed for Core -#### We should not need to install /tmp/control -# Install dependencies -ADD control /tmp/control -RUN apt-get install -qy python3 python3-setuptools python3-coverage \ - devscripts equivs libdatetime-perl meson pkgconf lcov gcovr xvfb \ - xserver-xephyr metacity mutter dbus-x11 weston xwayland && \ - (yes | mk-build-deps --install /tmp/control) || true && \ - rm /tmp/control -#### TMP END - # Pre-install emscripten USER build ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 From e54f8227860d2c65f190cdbbca455cc9ad370c37 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 11 Sep 2024 16:16:18 +0200 Subject: [PATCH 057/124] chore(linux): cleanup Setting permissions is now done in the base image. --- resources/docker-images/android/Dockerfile | 3 --- resources/docker-images/core/Dockerfile | 3 --- resources/docker-images/linux/Dockerfile | 3 --- resources/docker-images/web/Dockerfile | 3 --- 4 files changed, 12 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index 8c6833c0c1..6ebafb496a 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -40,9 +40,6 @@ else fi EOF -RUN chmod +x /usr/bin/bashwrapper && \ - chown -R build:build $HOME - # now, switch to build user USER build diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 55ca0b1ef2..990143f6e2 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -38,9 +38,6 @@ else fi EOF -RUN chmod +x /usr/bin/bashwrapper && \ - chown -R build:build $HOME - # now, switch to build user USER build diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile index 134bc1d1a5..3fe18c94b9 100644 --- a/resources/docker-images/linux/Dockerfile +++ b/resources/docker-images/linux/Dockerfile @@ -43,9 +43,6 @@ else fi EOF -RUN chmod +x /usr/bin/bashwrapper && \ - chown -R build:build $HOME - # now, switch to build user USER build diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index f7ad7ee3d9..986212fb11 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -61,9 +61,6 @@ else fi EOF -RUN chmod +x /usr/bin/bashwrapper && \ - chown -R build:build $HOME - # now, switch to build user USER build From 5c187db7a6d306b52e36d077a91d857d72d600e8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:40:42 +1000 Subject: [PATCH 058/124] feat(windows): address review comments --- VERSION.md | 2 +- .../Keyman.System.ExecutionHistory.pas | 2 + .../main/Keyman.System.UpdateStateMachine.pas | 65 +++++++------------ .../desktop/kmshell/main/UfrmStartInstall.dfm | 10 +-- .../desktop/kmshell/main/UfrmStartInstall.pas | 34 ++++++---- .../kmshell/main/UfrmStartInstallNow.dfm | 16 ++--- .../kmshell/main/UfrmStartInstallNow.pas | 25 +++---- windows/src/desktop/setup/UfrmRunDesktop.pas | 2 +- 8 files changed, 75 insertions(+), 81 deletions(-) diff --git a/VERSION.md b/VERSION.md index a598c2b1bd..a41e5425c0 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.95 +18.0.95 \ No newline at end of file diff --git a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas index 4d3900659b..eb3bb11df3 100644 --- a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -39,6 +39,7 @@ begin end; except on E: Exception do + // TODO: #10210 convert to sentry error KL.Log(E.ClassName + ': ' + E.Message); end; end; @@ -63,6 +64,7 @@ begin except on E: Exception do + // TODO: #10210 convert to sentry error KL.log(E.ClassName + ': ' + E.Message); end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 74c2433fa1..5cdc681692 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -248,7 +248,7 @@ type function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; - function GetAutomaticUpdate: Boolean; + function GetAutomaticUpdates: Boolean; function SetApplyNow(Value : Boolean): Boolean; function GetApplyNow: Boolean; @@ -328,7 +328,7 @@ begin FParams.Result := oucUnknown; FForce := AForce; - FAutomaticUpdate := GetAutomaticUpdate; + FAutomaticUpdate := GetAutomaticUpdates; FIdle := IdleState.Create(Self); FUpdateAvailable := UpdateAvailableState.Create(Self); FDownloading := DownloadingState.Create(Self); @@ -338,13 +338,12 @@ begin FPostInstall := PostInstallState.Create(Self); // Check the Registry setting. SetStateOnly(ConvertEnumState(CheckRegistryState)); - KL.Log('TUpdateStateMachine.Create'); end; destructor TUpdateStateMachine.Destroy; begin if (FErrorMessage <> '') and FShowErrors then - KL.Log(FErrorMessage); + KL.Log(FErrorMessage); // TODO: #10210 Log to Sentry if FParams.Result = oucShutDown then ShutDown; @@ -357,8 +356,9 @@ begin FRetry.Free; FPostInstall.Free; - KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); - KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + // TODO: #10210 remove debugging comments + //KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); + //KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); inherited Destroy; end; @@ -410,9 +410,10 @@ begin try Registry.RootKey := HKEY_CURRENT_USER; - KL.Log('SetRegistryState State Entry'); + if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then begin + // TODO: #10210 Log to Sentry KL.Log('Failed to open registry key: ' + SRegKey_KeymanEngine_CU); Exit; end; @@ -420,11 +421,11 @@ begin try UpdateStr := GetEnumName(TypeInfo(TUpdateState), Ord(Update)); Registry.WriteString(SRegValue_Update_State, UpdateStr); - KL.Log('SetRegistryState State is: [' + UpdateStr + ']'); Result := True; except on E: Exception do begin + // TODO: #10210 Log to Sentry KL.Log('Failed to write to registry: ' + E.Message); end; end; @@ -450,12 +451,10 @@ begin if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_Update_State) then begin UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); - KL.Log('CheckRegistryState State is:[' + Registry.ReadString(SRegValue_Update_State) + ']'); end else begin UpdateState := usIdle; // do we need a unknown state ? - KL.Log('CheckRegistryState State reg value not found default:[' + Registry.ReadString(SRegValue_Update_State) + ']'); end; finally Registry.Free; @@ -464,7 +463,7 @@ begin Result := UpdateState; end; -function TUpdateStateMachine.GetAutomaticUpdate: Boolean; // I2329 +function TUpdateStateMachine.GetAutomaticUpdates: Boolean; // I2329 var Registry: TRegistryErrorControlled; @@ -473,14 +472,9 @@ begin Registry := TRegistryErrorControlled.Create; // I2890 try Registry.RootKey := HKEY_CURRENT_USER; - if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_AutomaticUpdates) then - begin - Result := Registry.ReadBool(SRegValue_AutomaticUpdates); - end - else - begin - Result := True; // Default - end; + Result := not Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) or + not Registry.ValueExists(SRegValue_AutomaticUpdates) or + Registry.ReadBool(SRegValue_AutomaticUpdates); finally Registry.Free; end; @@ -505,6 +499,7 @@ begin except on E: Exception do begin + // TODO: #10210 Log to Sentry 'Failed to write '+SRegValue_ApplyNow+' to registry: ' + E.Message KL.Log('Failed to write to registry: ' + E.Message); end; end; @@ -521,14 +516,9 @@ begin Registry := TRegistryErrorControlled.Create; try Registry.RootKey := HKEY_CURRENT_USER; - if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_ApplyNow) then - begin - Result := Registry.ReadBool(SRegValue_ApplyNow); - end - else - begin - Result := False; // Default - end; + Result := Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + Registry.ValueExists(SRegValue_ApplyNow) and + Registry.ReadBool(SRegValue_ApplyNow); finally Registry.Free; end; @@ -684,9 +674,7 @@ end; procedure TState.ChangeState(NewState: TStateClass); begin - KL.Log('TUpdateStateMachine.ChangeState old' + bucStateContext.CurrentStateName ); bucStateContext.State := NewState; - KL.Log('TUpdateStateMachine.ChangeState new' + bucStateContext.CurrentStateName ); end; { IdleState } @@ -744,7 +732,6 @@ var //const CheckPeriod: Integer = 7; // Days between checking for updates begin // Check if auto updates enable and if scheduled time has expired - KL.Log('IdleState.HandleKmShell'); if ConfigCheckContinue then begin CheckForUpdates := TRemoteUpdateCheck.Create(True); @@ -795,6 +782,7 @@ begin RootPath := ExtractFilePath(ParamStr(0)); FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); if not FResult then + // TODO: #10210 Log to Sentry KL.Log('TrmfMain: Executing KMshell for download updated Failed'); end; @@ -848,6 +836,9 @@ begin InstallNow := True; if HasKeymanRun then begin + // TODO: UI and non-UI units should be split + // if the unit launches UI then it should be a .UI. unit + //https://github.com/keymanapp/keyman/pull/12375/files#r1751041747 frmStartInstallNow := TfrmStartInstallNow.Create(nil); try if frmStartInstallNow.ShowModal = mrOk then @@ -932,7 +923,6 @@ var DownloadResult, FResult : Boolean; RootPath: string; begin // Enter Already Downloading - KL.Log('DownloadingState.HandleDownload already downloading'); end; procedure DownloadingState.HandleAbort; @@ -959,10 +949,8 @@ begin DownloadUpdate := TDownloadUpdate.Create; try DownloadResult := DownloadUpdate.DownloadUpdates; - KL.Log('TUpdateStateMachine.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); Result := DownloadResult; // #TODO: #10210 workout when we need to refresh kmcom keyboards - // if Result in [ wucSuccess] then // begin // kmcom.Keyboards.Refresh; @@ -979,7 +967,6 @@ end; procedure WaitingRestartState.Enter; begin // Enter WaitingRestartState - KL.Log('WaitingRestartState.HandleKmShell Enter'); bucStateContext.SetRegistryState(usWaitingRestart); end; @@ -999,11 +986,9 @@ var Filenames : TStringDynArray; frmStartInstall : TfrmStartInstall; begin - KL.Log('WaitingRestartState.HandleKmShell Enter'); // Still can't go if keyman has run if HasKeymanRun then begin - KL.Log('WaitingRestartState.HandleKmShell Keyman Has Run'); Result := kmShellContinue; // Exit; // Exit is not wokring for some reason. // this else is only here because the exit is not working. @@ -1015,7 +1000,6 @@ begin GetFileNamesInDirectory(SavedPath, FileNames); if Length(FileNames) = 0 then begin - KL.Log('WaitingRestartState.HandleKmShell No Files in Download Cache'); // Return to Idle state and check for Updates state ChangeState(IdleState); bucStateContext.CurrentState.HandleCheck; // TODO no event here @@ -1024,7 +1008,6 @@ begin end else begin - KL.Log('WaitingRestartState.HandleKmShell is good to install'); // TODO Pop up toast here to ask user if we want to continue frmStartInstall := TfrmStartInstall.Create(nil); try @@ -1074,7 +1057,7 @@ begin if InstallNow = True then begin bucStateContext.SetApplyNow(True); - ChangeState(InstallingState) + ChangeState(InstallingState); end; end; @@ -1127,7 +1110,6 @@ begin FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 else if s = '.exe' then begin - KL.Log('TUpdateStateMachine.InstallingState.DoInstallKeyman SavePath:"'+ SavePath+'"'); // switch -au for auto update in silent mode. // We will need to add the pop up that says install update now yes/no // This will run the setup executable which will ask for elevated permissions @@ -1138,6 +1120,7 @@ begin if not FResult then begin + // TODO: #10210 Log to Sentry KL.Log('TUpdateStateMachine.InstallingState.DoInstall: Result = '+IntToStr(Ord(FResult))); // Log messageShowMessage(SysErrorMessage(GetLastError)); end; @@ -1295,9 +1278,7 @@ var SavePath: string; FileName: String; FileNames: TStringDynArray; begin - KL.Log('PostInstallState.HandleMSIInstallComplete'); SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - KL.Log('PostInstallState.HandleMSIInstallComplete remove SavePath:'+ SavePath); GetFileNamesInDirectory(SavePath, FileNames); for FileName in FileNames do diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm index 71fcfe2737..010059e441 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm @@ -13,7 +13,7 @@ object frmStartInstall: TfrmStartInstall OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 - object InstallUpdate: TLabel + object lblInstallUpdate: TLabel Left = 128 Top = 96 Width = 175 @@ -26,22 +26,22 @@ object frmStartInstall: TfrmStartInstall Font.Style = [] ParentFont = False end - object Install: TButton + object cmdInstall: TButton Left = 228 Top = 184 Width = 75 Height = 25 Caption = 'Install' TabOrder = 0 - OnClick = InstallClick + OnClick = cmdInstallClick end - object Later: TButton + object cmdLater: TButton Left = 336 Top = 184 Width = 75 Height = 25 Caption = 'Close' TabOrder = 1 - OnClick = LaterClick + OnClick = cmdLaterClick end end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas index f242b86327..2e54d14027 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -1,38 +1,48 @@ -unit UfrmStartInstall; +unit UfrmStartInstall; { Copyright: © SIL Global. + // TODO: Localise all the labels and captions. } interface uses - Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, UserMessages, StdCtrls, ExtCtrls, UfrmKeymanBase; + Windows, + Messages, + SysUtils, + Variants, + Classes, + Graphics, + Controls, + Forms, + Dialogs, + UserMessages, + StdCtrls, + ExtCtrls, + UfrmKeymanBase; type TfrmStartInstall = class(TfrmKeymanBase) - Install: TButton; - Later: TButton; - InstallUpdate: TLabel; - procedure InstallClick(Sender: TObject); - procedure LaterClick(Sender: TObject); + cmdInstall: TButton; + cmdLater: TButton; + lblInstallUpdate: TLabel; + procedure cmdInstallClick(Sender: TObject); + procedure cmdLaterClick(Sender: TObject); private public end; -var - frmStartInstall: TfrmStartInstall; implementation {$R *.dfm} -procedure TfrmStartInstall.InstallClick(Sender: TObject); +procedure TfrmStartInstall.cmdInstallClick(Sender: TObject); begin ModalResult := mrOk; end; -procedure TfrmStartInstall.LaterClick(Sender: TObject); +procedure TfrmStartInstall.cmdLaterClick(Sender: TObject); begin ModalResult := mrCancel; end; diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm index ae93b5cd1e..289745efd5 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm @@ -13,11 +13,11 @@ object frmStartInstallNow: TfrmStartInstallNow OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 - object UpdateMessage: TLabel + object lblUpdateMessage: TLabel Left = 56 Top = 88 - Width = 289 - Height = 38 + Width = 274 + Height = 19 Caption = 'Keyman and Windows will be restarted' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -27,7 +27,7 @@ object frmStartInstallNow: TfrmStartInstallNow ParentFont = False WordWrap = True end - object UpdateNow: TLabel + object lblUpdateNow: TLabel Left = 56 Top = 40 Width = 115 @@ -40,22 +40,22 @@ object frmStartInstallNow: TfrmStartInstallNow Font.Style = [] ParentFont = False end - object Install: TButton + object cmdInstall: TButton Left = 228 Top = 184 Width = 75 Height = 25 Caption = 'Update' TabOrder = 0 - OnClick = InstallClick + OnClick = cmdInstallClick end - object Later: TButton + object cmdLater: TButton Left = 336 Top = 184 Width = 75 Height = 25 Caption = 'Close' TabOrder = 1 - OnClick = LaterClick + OnClick = cmdLaterClick end end diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas index 6782a166a7..2895502e8e 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas @@ -1,6 +1,7 @@ -unit UfrmStartInstallNow; +unit UfrmStartInstallNow; { Copyright: © SIL Global. + // TODO: Localise all the labels and captions. } interface @@ -11,29 +12,29 @@ uses type TfrmStartInstallNow = class(TfrmKeymanBase) - Install: TButton; - Later: TButton; - UpdateMessage: TLabel; - UpdateNow: TLabel; - procedure InstallClick(Sender: TObject); - procedure LaterClick(Sender: TObject); + cmdInstall: TButton; + cmdLater: TButton; + lblUpdateMessage: TLabel; + lblUpdateNow: TLabel; + procedure cmdInstallClick(Sender: TObject); + procedure cmdLaterClick(Sender: TObject); private public end; -var - frmStartInstall: TfrmStartInstallNow; - implementation {$R *.dfm} -procedure TfrmStartInstallNow.InstallClick(Sender: TObject); + +// TODO remove events as they are properties on the buttons + +procedure TfrmStartInstallNow.cmdInstallClick(Sender: TObject); begin ModalResult := mrOk; end; -procedure TfrmStartInstallNow.LaterClick(Sender: TObject); +procedure TfrmStartInstallNow.cmdLaterClick(Sender: TObject); begin ModalResult := mrCancel; end; diff --git a/windows/src/desktop/setup/UfrmRunDesktop.pas b/windows/src/desktop/setup/UfrmRunDesktop.pas index 3374324d64..ded19ae75c 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.pas +++ b/windows/src/desktop/setup/UfrmRunDesktop.pas @@ -1043,7 +1043,7 @@ begin FCheckForUpdates := ValueExists(SRegValue_CheckForUpdates) and ReadBool(SRegValue_CheckForUpdates); FStartWithWindows := ValueExists(SRegValue_UpgradeRunKeyman) or (OpenKeyReadOnly('\' + SRegKey_WindowsRun_CU) and ValueExists(SRegValue_WindowsRun_Keyman)); - FAutomaticUpdates := ValueExists(SRegValue_AutomaticUpdates) and ReadBool(SRegValue_AutomaticUpdates); + FAutomaticUpdates := not ValueExists(SRegValue_AutomaticUpdates) or ReadBool(SRegValue_AutomaticUpdates); end else if FCanUpgrade10 and OpenKeyReadOnly(SRegKey_KeymanEngine100_ProductOptions_Desktop_CU) then // I4293 From 06de2b0475d66408ac56e41c8ad6f96f742884a8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:05:05 +1000 Subject: [PATCH 059/124] feat(windows): change automatic update description --- windows/src/desktop/kmshell/xml/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 678a05d690..850f27930c 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -415,7 +415,7 @@ - Automatically download updates ready to install + Automatically check for updates and download From b4f52be7fd29e959882fdb7df2e7f2c24d59fe0e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:58:31 +1000 Subject: [PATCH 060/124] feat(windows): rename UI forms to convention --- windows/src/desktop/kmshell/kmshell.dpr | 4 ++-- windows/src/desktop/kmshell/kmshell.dproj | 16 ++++++++-------- windows/src/desktop/kmshell/kmshell.res | Bin 0 -> 7036 bytes ...eyman.Configuration.UI.UfrmStartInstall.dfm} | 0 ...eyman.Configuration.UI.UfrmStartInstall.pas} | 2 +- ...an.Configuration.UI.UfrmStartInstallNow.dfm} | 0 ...an.Configuration.UI.UfrmStartInstallNow.pas} | 2 +- .../main/Keyman.System.UpdateStateMachine.pas | 4 ++-- .../engine/kmcomapi/util/utilkeymanoption.pas | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 windows/src/desktop/kmshell/kmshell.res rename windows/src/desktop/kmshell/main/{UfrmStartInstall.dfm => Keyman.Configuration.UI.UfrmStartInstall.dfm} (100%) rename windows/src/desktop/kmshell/main/{UfrmStartInstall.pas => Keyman.Configuration.UI.UfrmStartInstall.pas} (93%) rename windows/src/desktop/kmshell/main/{UfrmStartInstallNow.dfm => Keyman.Configuration.UI.UfrmStartInstallNow.dfm} (100%) rename windows/src/desktop/kmshell/main/{UfrmStartInstallNow.pas => Keyman.Configuration.UI.UfrmStartInstallNow.pas} (93%) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index d0915bd865..87f9fbf6bf 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -184,8 +184,8 @@ uses Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas', Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas', Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas', - UfrmStartInstallNow in 'main\UfrmStartInstallNow.pas', - UfrmStartInstall in 'main\UfrmStartInstall.pas'; + Keyman.Configuration.UI.UfrmStartInstallNow in 'main\Keyman.Configuration.UI.UfrmStartInstallNow.pas' {frmInstallNow}, + Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall}; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 6784e9c6cb..5be4729400 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -360,11 +360,11 @@ - +
    frmInstallNow
    dfm
    - +
    frmStartInstall
    dfm
    @@ -429,12 +429,6 @@ False - - - kmshell.exe - true - - kmshell.exe @@ -447,6 +441,12 @@ true + + + kmshell.exe + true + + 1 diff --git a/windows/src/desktop/kmshell/kmshell.res b/windows/src/desktop/kmshell/kmshell.res new file mode 100644 index 0000000000000000000000000000000000000000..80494425e9cab438c239cd54b9fc76a6f09be330 GIT binary patch literal 7036 zcmeI1O=uj+6~~`s1$k^NbP8*qI-STYftKc5qh&^3VeiJH_(Np%aST1vJ<_0=?#}c` zvr-7?duj_CG}I-$&KHnP18_(G8&z@qlu4zXR1QI{GkPl`2#uixx3jkxFD@ zWQz)9g9!^JOmRdtfh&#}IHE9Jgm1vl!>2+nylr@LiO?vR;3D`iKLg4pgH|YyS-ZWu zeeTdFO`k=0>QqK#x5W5)j8n$=Chj>X6^y83WRAl||1$brT7q4|izw9*?=AGZ@Zjfm z5J@ZUZa?{)opIk2k(eTmZl0s-!;^7DL3Uc%B>I(jCuX7Q$drrN^p5#M1OFWO6ggKg zj^|xyu8HB{)|&Vf+AuC=`owrdhiC_dwN}|6!av&BkQMG|(&6DDY8H55a^=Yicez%% zQ+A~Ns zfZqYX4SpSb4*WFuD0mON2i^nkf%m|B;Q4ie*E75x;pO5rBlrgRGw^lrN8k^@^XNOE zw?VIi&Vil=9mV+P;E%!Yg5Lt~f?ohX2R;iv13m(N1pHO-7-UIJ}_rN2d69n`OZ`UKdT`u0PnHi!6_%rZz@JHYe!0&+Xv3%$p{4w}l z@LS+r@C)F3EFX+pr+x67;FrPQ20sG6SAy!^IrwAnyWqFLBh%gj`1dEM?lr)lfv;iyLa#C_3PL4>eVZH`SK;bc=3XsK7C3%J3I8` z$rIY%-lhi+9?<&wI$4%Qxm=Fs=jUm9dYUFDCn*|@()H`t>DskxG&D4X713EnWg+^x zDCC=shU}0wHBdx{u!Si**p?>lAYccoaKY}yz9^!`+@g)jZ#?IkEm3dMbM6o!qQaVf zE;`tWO<@ZcX-Zp;0oB*xLJYU;=f1fKAxQRz)&Ig*uOdd_{pEoBL??aKmy4ViaNr^YeBhV0vjVJ{@+mO5hC3$b^g*JuwLFl_9@ z@J9oCudw033T(MS6ByWZ+K(0rdSN?5r~L|V*o?!bH>dron1Dxt%(lWah(g$}`&9w3 z4O!R$09n{Rz_PR@z_PUE6fFBT148y~0Kz-a5rdQiT_xcS-G7^ZGSy#k#8428Jl73~ zxO#sM~c|#sh0*HqQBzd0c z(E0Sy33T`ehm*Du+I1;+j?ktDatF}TTXH+l)Z67YAkaJI7U1jMaublcl>IXnpR}5J z5W4Rz^ta*;T_%e4VDK(o0MG+jp5a>lVg!Ce#NmwxY`hhgoS%DHZOW{3V4{~*Ul#y# z>?d^8zKru6&U#GB7rhm2sq+~Sx&Y)%Vg}~%53%t1P#iGPFAB*Dcb-a|A6dr;O~G&Y zvh;rs&^&(%|Nj93Iq>fSnsQ5TkD(>E=`Em5Zr1~#UGC5&&>?r~40Os}I)LDy&B;R% zdH*TjCJ|g1m*)-9U7Et4d=J-6@vCGIcew3*FHiNu!^7fq&%bW<;r!3{i^RXt^pm1U zjsK@`0hZMGyu`Mw-6yb-ARhniaQbTNyPn8deq4%G*ZKTFj_( zvtMc{RdK7jW#}c_GBfI$>8dv-uUty&u4|Tar8NZuSZ+pLsaT)6`6aWgyIQ%Juh_0# zsA_q;{F&~SM^_`NQr4|v!E~$h9kU5{W>#b0J4j5$%vUZcO4=|T(=rh0+9b@jj_Q;j zm+XfU1eyh2EmqeA&py>P2M<&$TCu3w?6mYUI`0M#FuG8*47=uz-n7eQ+tO~@R<&Z6 z+?GQ#cgfK(zgmj3k}DPSzctrp?PU|mO(rH1xnw*NkLUA+h*4;fCEZ$F(HAlCuUnl? z#m<|qYgeW!`K4mj%vV<`f*A83c`Qgm$1|s!-N=o|RkgC>REZ?7l4xAV6 zq;lm_D6KmV-c5{STcRVB{)MK^E}2U9mXb3|c1^)bVZd}1u!_6lICiC~6zq!PFCt~W zh>wz@X^1KmO8bOq-2Y0+8X5JEW;7g$#1rFMBoR+)@mM~g>4}1=g~KVsG|iY1D}?`~ zj(za-k4AdW9VsNollk$amI|8@Egp?cX!wZc zv{cLp7m`NU(8qtw+>vnasZ(aSFs?_UTCR|bA>n39L;4e1JXJ6fVLg)2Cq%+Sp%AaW zv99Hp9_v`9yv)afFFF|57l_j0d;b!Cx4na%W)^=_xsShr-9>8(^>6Uks9F34OP2OM Ifp>}j0>X0-NdN!< literal 0 HcmV?d00001 diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm similarity index 100% rename from windows/src/desktop/kmshell/main/UfrmStartInstall.dfm rename to windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas similarity index 93% rename from windows/src/desktop/kmshell/main/UfrmStartInstall.pas rename to windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 2e54d14027..6628ea570e 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -1,4 +1,4 @@ -unit UfrmStartInstall; +unit Keyman.Configuration.UI.UfrmStartInstall; { Copyright: © SIL Global. // TODO: Localise all the labels and captions. diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm similarity index 100% rename from windows/src/desktop/kmshell/main/UfrmStartInstallNow.dfm rename to windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm diff --git a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas similarity index 93% rename from windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas rename to windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas index 2895502e8e..9603dec5d6 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas @@ -1,4 +1,4 @@ -unit UfrmStartInstallNow; +unit Keyman.Configuration.UI.UfrmStartInstallNow; { Copyright: © SIL Global. // TODO: Localise all the labels and captions. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 5cdc681692..3e22b1f7fe 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -32,8 +32,8 @@ uses httpuploader, Keyman.System.UpdateCheckResponse, - UfrmStartInstall, - UfrmStartInstallNow, + Keyman.Configuration.UI.UfrmStartInstall, + Keyman.Configuration.UI.UfrmStartInstallNow, Keyman.System.ExecutionHistory, UfrmDownloadProgress; diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index 5ceafbc07e..8a18f3be26 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,7 +121,7 @@ type GroupName: string; end; -const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 +const KeymanOptionInfo: array[0..17] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 // Global options (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), From 4ee3f5075c173c39ff206f6c2a57d5f251c45d16 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:16:03 +1000 Subject: [PATCH 061/124] feat(windows): add handle firstrun event --- .../main/Keyman.System.UpdateStateMachine.pas | 66 +++++++++++++------ .../main/UImportOlderVersionSettings.pas | 11 ++++ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 3e22b1f7fe..d8feabfdd8 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -102,6 +102,7 @@ type procedure HandleDownload; virtual; abstract; procedure HandleAbort; virtual; abstract; procedure HandleInstallNow; virtual; abstract; + procedure HandleFirstRun; virtual; // For convenience function StateName: string; virtual; abstract; @@ -182,6 +183,7 @@ type procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; + procedure HandleFirstRun; override; function StateName: string; override; end; @@ -198,8 +200,6 @@ type end; PostInstallState = class(TState) - private - procedure HandleMSIInstallComplete; public procedure Enter; override; procedure Exit; override; @@ -208,11 +208,12 @@ type procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; + procedure HandleFirstRun; override; function StateName: string; override; end; - { This class also controls the state flow see } + { This class also controls the state flow} TUpdateStateMachine = class private FForce: Boolean; @@ -236,6 +237,7 @@ type procedure SetState(const Value: TStateClass); procedure SetStateOnly(const Value: TStateClass); function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; + procedure HandleMSIInstallComplete; procedure ShutDown; { @@ -264,6 +266,7 @@ type procedure HandleDownload; procedure HandleAbort; procedure HandleInstallNow; + procedure HandleFirstRun; function CurrentStateName: string; property ShowErrors: Boolean read FShowErrors write FShowErrors; @@ -636,6 +639,22 @@ begin end; end; + +procedure TUpdateStateMachine.HandleMSIInstallComplete; +var SavePath: string; + FileName: String; + FileNames: TStringDynArray; +begin + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + + GetFileNamesInDirectory(SavePath, FileNames); + for FileName in FileNames do + begin + System.SysUtils.DeleteFile(FileName); + end; + CurrentState.ChangeState(IdleState); +end; + procedure TUpdateStateMachine.HandleCheck; begin CurrentState.HandleCheck; @@ -661,6 +680,11 @@ begin CurrentState.HandleInstallNow; end; +procedure TUpdateStateMachine.HandleFirstRun; +begin + CurrentState.HandleFirstRun; +end; + function TUpdateStateMachine.CurrentStateName: string; begin Result := CurrentState.StateName; @@ -677,6 +701,12 @@ begin bucStateContext.State := NewState; end; +// base implmentation to be overiden +procedure TState.HandleFirstRun; +begin + +end; + { IdleState } procedure IdleState.Enter; @@ -891,7 +921,6 @@ begin end else begin - bucStateContext.SetApplyNow(False); ChangeState(InstallingState); end; end @@ -1194,6 +1223,12 @@ begin // Do Nothing. Need the UI to let user know installation in progress OR end; +procedure InstallingState.HandleFirstRun; +begin + bucStateContext.HandleMSIInstallComplete; + //Result := kmShellContinue; +end; + function InstallingState.StateName; begin Result := 'InstallingState'; @@ -1264,7 +1299,7 @@ end; function PostInstallState.HandleKmShell; begin - HandleMSIInstallComplete; + bucStateContext.HandleMSIInstallComplete; Result := kmShellContinue; end; @@ -1273,21 +1308,6 @@ begin // Do Nothing end; -procedure PostInstallState.HandleMSIInstallComplete; -var SavePath: string; - FileName: String; - FileNames: TStringDynArray; -begin - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - - GetFileNamesInDirectory(SavePath, FileNames); - for FileName in FileNames do - begin - System.SysUtils.DeleteFile(FileName); - end; - ChangeState(IdleState); -end; - procedure PostInstallState.HandleAbort; begin // Handle Abort @@ -1298,6 +1318,12 @@ begin // Do nothing as files will be cleaned via HandleKmShell end; +procedure PostInstallState.HandleFirstRun; +begin + bucStateContext.HandleMSIInstallComplete; + //Result := kmShellContinue; +end; + function PostInstallState.StateName; begin diff --git a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas index e4cdff54bb..c2f95b38ef 100644 --- a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas +++ b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas @@ -41,6 +41,7 @@ uses keymanapi_TLB, ErrorControlledRegistry, RegistryKeys, + Keyman.System.UpdateStateMachine, UImportOlderKeyboardUtils; function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates,DoAutomaticUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753 @@ -48,7 +49,17 @@ var n, I: Integer; v: Integer; p: string; + UpdateSM : TUpdateStateMachine; begin + // send event to statemachine (should result in setting state to idle) + UpdateSM := TUpdateStateMachine.Create(False); + try + UpdateSM.HandleFirstRun; + Exit; + finally + UpdateSM.Free; + end; + { Copy over all the user settings and set defaults for version 8.0: http://blog.tavultesoft.com/2011/02/keyman-desktop-80-default-options.html } if DoDefaults then // I2753 From c98827162891e6d895c878639d57484167382e2b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:12:21 +1000 Subject: [PATCH 062/124] feat(windows): formatting review suggestions Co-authored-by: Marc Durdin --- .../Keyman.System.ExecutionHistory.pas | 12 ++-- .../windows/delphi/general/RegistryKeys.pas | 2 +- oem/firstvoices/windows/src/xml/strings.xml | 2 +- ...yman.Configuration.UI.UfrmStartInstall.pas | 7 ++- ...n.Configuration.UI.UfrmStartInstallNow.pas | 9 +-- .../main/Keyman.System.UpdateStateMachine.pas | 56 +++++++++---------- 6 files changed, 43 insertions(+), 45 deletions(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas index eb3bb11df3..52fda002dc 100644 --- a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -1,12 +1,12 @@ -unit Keyman.System.ExecutionHistory; - { - Copyright: © SIL Global. + Keyman is copyright (C) SIL Global. MIT License. This module provides functionality to track the execution state of the Keyman engine. It uses a global atom to record whether Keyman has started during the current session and checks if it has previously run. } +unit Keyman.System.ExecutionHistory; + interface @@ -17,9 +17,11 @@ function RecordKeymanStarted : Boolean; function HasKeymanRun : Boolean; implementation + uses - System.SysUtils,KLog, - Winapi.Windows; + System.SysUtils, + Winapi.Windows, + KLog; function RecordKeymanStarted : Boolean; var diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 511f36e6d9..5bcbb96792 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -317,7 +317,7 @@ const SRegValue_AutomaticUpdates = 'automatic updates'; //CU SRegValue_CheckForUpdates = 'check for updates'; // CU SRegValue_LastUpdateCheckTime = 'last update check time'; // CU - SRegValue_ApplyNow = 'apply now'; // CU Start the install now even thought it will require an update + SRegValue_ApplyNow = 'apply now'; // CU Start the install now even though it will require an update SRegValue_UpdateCheck_UseProxy = 'update check use proxy'; // CU SRegValue_UpdateCheck_ProxyHost = 'update check proxy host'; // CU diff --git a/oem/firstvoices/windows/src/xml/strings.xml b/oem/firstvoices/windows/src/xml/strings.xml index bbb79c8869..692a582dd7 100644 --- a/oem/firstvoices/windows/src/xml/strings.xml +++ b/oem/firstvoices/windows/src/xml/strings.xml @@ -391,7 +391,7 @@ - Automatically download updates ready to install + Automatically download updates in the background, for installation later diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 6628ea570e..7e2de18a03 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -1,8 +1,9 @@ -unit Keyman.Configuration.UI.UfrmStartInstall; { - Copyright: © SIL Global. - // TODO: Localise all the labels and captions. + Keyman is copyright (C) SIL Global. MIT License. + + // TODO-WINDOWS-UPDATES: Localise all the labels and captions. } +unit Keyman.Configuration.UI.UfrmStartInstall; interface uses diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas index 9603dec5d6..e5d0c2e501 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas @@ -1,8 +1,9 @@ -unit Keyman.Configuration.UI.UfrmStartInstallNow; { - Copyright: © SIL Global. - // TODO: Localise all the labels and captions. + Keyman is copyright (C) SIL Global. MIT License. + + // TODO-WINDOWS-UPDATES: Localise all the labels and captions. } +unit Keyman.Configuration.UI.UfrmStartInstallNow; interface uses @@ -27,7 +28,7 @@ implementation {$R *.dfm} -// TODO remove events as they are properties on the buttons +// TODO-WINDOWS-UPDATES: remove events as they are properties on the buttons procedure TfrmStartInstallNow.cmdInstallClick(Sender: TObject); begin diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 3e22b1f7fe..263702c37a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1,20 +1,8 @@ -(* - Name: UpdateStateMachine - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 2 Nov 2023 - - Modified Date: 2 Nov 2023 - Authors: rcruickshank - Related Files: - Dependencies: - - Bugs: - Todo: +{ + Keyman is copyright (C) SIL Global. MIT License. + Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md - History: -*) +} unit Keyman.System.UpdateStateMachine; interface @@ -25,17 +13,17 @@ uses System.UITypes, System.IOUtils, System.Types, + System.TypInfo, Vcl.Forms, - TypInfo, - KeymanPaths, - utilkmshell, httpuploader, + KeymanPaths, Keyman.System.UpdateCheckResponse, Keyman.Configuration.UI.UfrmStartInstall, Keyman.Configuration.UI.UfrmStartInstallNow, Keyman.System.ExecutionHistory, - UfrmDownloadProgress; + UfrmDownloadProgress, + utilkmshell; const CheckPeriod: Integer = 7; // Days between checking for updates @@ -82,11 +70,12 @@ type StartPosition: Integer; end; - // Forward declaration - TUpdateStateMachine = class; - { State Classes Update } + // Forward declaration + TUpdateStateMachine = class; - TStateClass = class of TState; + { State Classes Update } + + TStateClass = class of TState; TState = class abstract private @@ -282,9 +271,11 @@ type public constructor Create(AParams: TUpdateStateMachineParams); function Params: TUpdateStateMachineParams; - end; - // Private Utility functions - function ConfigCheckContinue: Boolean; +end; + +// Private Utility functions +function ConfigCheckContinue: Boolean; + implementation uses @@ -305,7 +296,7 @@ uses Upload_Settings, utildir, utilexecute, - OnlineUpdateCheckMessages, // todo create own messages + OnlineUpdateCheckMessages, // TODO-WINDOWS-UPDATES: create own messages UfrmOnlineUpdateIcon, UfrmOnlineUpdateNewVersion, utilsystem, @@ -372,10 +363,16 @@ begin StringList := TStringList.Create; try for i := 0 to High(FParams.Packages) do + begin if FParams.Packages[i].NewID <> '' then + begin StringList.Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); + end; + end; if StringList.Count > 0 then + begin StringList.SaveToFile(DownloadTempPath + SPackageUpgradeFileName); + end; finally StringList.Free; end; @@ -538,12 +535,10 @@ begin begin if RegistryErrorControlled.ValueExists(SRegValue_CheckForUpdates) and not RegistryErrorControlled.ReadBool(SRegValue_CheckForUpdates) and not FForce then begin - Result := False; Exit; end; if RegistryErrorControlled.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - RegistryErrorControlled.ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then begin - Result := False; Exit; end; // Else Time to check for updates @@ -1318,7 +1313,6 @@ begin begin if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then begin - Result := False; Exit; end; if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then From ac38649ffbd77e08244016b9ac732dfa0b6a394c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:31:47 +1000 Subject: [PATCH 063/124] feat(windows): More formatting fixes. Co-authored-by: Marc Durdin --- ...yman.Configuration.UI.UfrmStartInstall.pas | 27 ++++---- .../main/Keyman.System.DownloadUpdate.pas | 66 +++++++------------ .../main/Keyman.System.RemoteUpdateCheck.pas | 7 -- ...eyman.System.Install.EnginePostInstall.pas | 2 +- 4 files changed, 39 insertions(+), 63 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 7e2de18a03..dddcedb668 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -7,20 +7,19 @@ unit Keyman.Configuration.UI.UfrmStartInstall; interface uses - - Windows, - Messages, - SysUtils, - Variants, - Classes, - Graphics, - Controls, - Forms, - Dialogs, - UserMessages, - StdCtrls, - ExtCtrls, - UfrmKeymanBase; + System.Classes, + System.SysUtils, + System.Variants, + Vcl.Controls, + Vcl.Dialogs, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.Graphics, + Vcl.StdCtrls, + Winapi.Messages, + Winapi.Windows, + UfrmKeymanBase, + UserMessages; type TfrmStartInstall = class(TfrmKeymanBase) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index dcc6e28faa..aa4304d7e8 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -1,21 +1,6 @@ (* - Name: WebUpdateCheck - Copyright: Copyright (C) SIL Global. - Documentation: - Description: - Create Date: 5 Dec 2023 - - Modified Date: - Authors: rcruickshank - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: -*) - + * Keyman is copyright (C) SIL Global. MIT License. + *) unit Keyman.System.DownloadUpdate; interface @@ -28,7 +13,7 @@ uses OnlineUpdateCheck; const - CheckPeriod: Integer = 7; // Days between checking for updates + DaysBetweenCheckingForUpdates: Integer = 7; // Days between checking for updates type TDownloadUpdateParams = record @@ -88,7 +73,7 @@ uses System.Types, System.StrUtils; - // temp wrapper for converting showmessage to logs don't know where + // TODO-WINDOWS-UPDATES: temp wrapper for converting showmessage to logs don't know where // if not using klog procedure LogMessage(LogMessage: string); begin @@ -142,9 +127,10 @@ var Result := True; end else // I2742 + begin // If it fails we set to false but will try the other files - Result := False; - Exit; + Exit(False); + end; finally http.Free; end; @@ -181,20 +167,20 @@ begin // 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 - 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; + Params.Packages[i].Install := False; // Download failed but install other files + end + else + Inc(downloadCount); + FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; + end; // Keyman Installer if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 begin - // TODO: #10210 convert to error log. + // TODO-WINDOWS-UPDATES: #10210 convert to error log. LogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); end else @@ -249,23 +235,21 @@ begin begin Inc(VerifyDownloads.TotalDownloads); Inc(VerifyDownloads.TotalSize, Params.Packages[i].DownloadSize); - if Not MatchStr(Params.Packages[i].FileName, FileNames) then - begin - Result := False; - Exit; - end; + if not MatchStr(Params.Packages[i].FileName, FileNames) then + begin + Exit(False); + end; Params.Packages[i].SavePath := SavedPath + Params.Packages[i].FileName; end; // Add the Keyman installer Inc(FDownload.TotalDownloads); Inc(FDownload.TotalSize, Params.InstallSize); // Check if the Keyman installer downloaded - if Not MatchStr(Params.FileName, FileNames) then - begin - Result := False; - Exit; - end; - // TODO verify filesizes match so we know we don't have partial downloades. + if not MatchStr(Params.FileName, FileNames) then + begin + Exit(False); + end; + // TODO-WINDOWS-UPDATES: verify filesizes match so we know we don't have partial downloades. Result := True; end else diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index b1738bad93..ba3e8b62eb 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -231,18 +231,12 @@ begin begin if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then begin - Result := False; Exit; end; if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then begin Result := True; - end - else - begin - Result := False; end; - Exit; end; finally registry.Free; @@ -253,7 +247,6 @@ begin begin Result := False; LogMessage(E.Message); - Exit; end; end; end; diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 6b0fe419b2..31846bf8e1 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -94,7 +94,7 @@ begin end; Result := ERROR_SUCCESS; - // TODO better error checking on the registry key update + // TODO-WINDOWS-UPDATES: better error checking on the registry key update UpdateState; finally From 141bfda66accf0df66f7429831d15de01879235b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:42:11 +1000 Subject: [PATCH 064/124] feat(windows): remove redundant time since last update Remove the extra time since last update check and let remote update check control it. Also remove the events on the buttons as and use the button modal property --- ...yman.Configuration.UI.UfrmStartInstall.dfm | 4 +- ...yman.Configuration.UI.UfrmStartInstall.pas | 11 -- ...n.Configuration.UI.UfrmStartInstallNow.dfm | 4 +- ...n.Configuration.UI.UfrmStartInstallNow.pas | 31 +++-- .../main/Keyman.System.DownloadUpdate.pas | 14 +-- .../main/Keyman.System.UpdateStateMachine.pas | 112 ++---------------- ...eyman.System.Install.EnginePostInstall.pas | 7 +- 7 files changed, 42 insertions(+), 141 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index 010059e441..9084afe1b6 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -32,8 +32,8 @@ object frmStartInstall: TfrmStartInstall Width = 75 Height = 25 Caption = 'Install' + ModalResult = 1 TabOrder = 0 - OnClick = cmdInstallClick end object cmdLater: TButton Left = 336 @@ -41,7 +41,7 @@ object frmStartInstall: TfrmStartInstall Width = 75 Height = 25 Caption = 'Close' + ModalResult = 8 TabOrder = 1 - OnClick = cmdLaterClick end end diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index dddcedb668..8571c0b515 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -26,8 +26,6 @@ type cmdInstall: TButton; cmdLater: TButton; lblInstallUpdate: TLabel; - procedure cmdInstallClick(Sender: TObject); - procedure cmdLaterClick(Sender: TObject); private public end; @@ -37,14 +35,5 @@ implementation {$R *.dfm} -procedure TfrmStartInstall.cmdInstallClick(Sender: TObject); -begin - ModalResult := mrOk; -end; - -procedure TfrmStartInstall.cmdLaterClick(Sender: TObject); -begin - ModalResult := mrCancel; -end; end. diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm index 289745efd5..eae5493040 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm @@ -46,8 +46,8 @@ object frmStartInstallNow: TfrmStartInstallNow Width = 75 Height = 25 Caption = 'Update' + ModalResult = 1 TabOrder = 0 - OnClick = cmdInstallClick end object cmdLater: TButton Left = 336 @@ -55,7 +55,7 @@ object frmStartInstallNow: TfrmStartInstallNow Width = 75 Height = 25 Caption = 'Close' + ModalResult = 8 TabOrder = 1 - OnClick = cmdLaterClick end end diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas index e5d0c2e501..5f9c9caefc 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas @@ -1,15 +1,25 @@ { Keyman is copyright (C) SIL Global. MIT License. - + // TODO-WINDOWS-UPDATES: Localise all the labels and captions. } unit Keyman.Configuration.UI.UfrmStartInstallNow; interface uses - - Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, - Dialogs, UserMessages, StdCtrls, ExtCtrls, UfrmKeymanBase; + System.Classes, + System.SysUtils, + System.Variants, + Vcl.Controls, + Vcl.Dialogs, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.Graphics, + Vcl.StdCtrls, + Winapi.Messages, + Winapi.Windows, + UfrmKeymanBase, + UserMessages; type TfrmStartInstallNow = class(TfrmKeymanBase) @@ -17,8 +27,6 @@ type cmdLater: TButton; lblUpdateMessage: TLabel; lblUpdateNow: TLabel; - procedure cmdInstallClick(Sender: TObject); - procedure cmdLaterClick(Sender: TObject); private public end; @@ -28,16 +36,5 @@ implementation {$R *.dfm} -// TODO-WINDOWS-UPDATES: remove events as they are properties on the buttons - -procedure TfrmStartInstallNow.cmdInstallClick(Sender: TObject); -begin - ModalResult := mrOk; -end; - -procedure TfrmStartInstallNow.cmdLaterClick(Sender: TObject); -begin - ModalResult := mrCancel; -end; end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index aa4304d7e8..57c8a2cad0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -7,9 +7,9 @@ interface uses System.Classes, System.SysUtils, - KeymanPaths, httpuploader, Keyman.System.UpdateCheckResponse, + KeymanPaths, OnlineUpdateCheck; const @@ -59,19 +59,19 @@ implementation uses + System.StrUtils, + System.Types, + ErrorControlledRegistry, GlobalProxySettings, - KLog, keymanapi_TLB, KeymanVersion, Keyman.System.UpdateCheckStorage, + KLog, kmint, - ErrorControlledRegistry, + OnlineUpdateCheckMessages, RegistryKeys, Upload_Settings, - OnlineUpdateCheckMessages, - utilkmshell, - System.Types, - System.StrUtils; + utilkmshell; // TODO-WINDOWS-UPDATES: temp wrapper for converting showmessage to logs don't know where // if not using klog diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 263702c37a..ea19ab7066 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1,6 +1,6 @@ { Keyman is copyright (C) SIL Global. MIT License. - + Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md } unit Keyman.System.UpdateStateMachine; @@ -25,9 +25,6 @@ uses UfrmDownloadProgress, utilkmshell; -const - CheckPeriod: Integer = 7; // Days between checking for updates - type EUpdateStateMachine = class(Exception); @@ -234,7 +231,6 @@ type tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; function GetAutomaticUpdates: Boolean; @@ -273,9 +269,6 @@ type function Params: TUpdateStateMachineParams; end; -// Private Utility functions -function ConfigCheckContinue: Boolean; - implementation uses @@ -521,43 +514,6 @@ begin end; end; - -function TUpdateStateMachine.CheckUpdateSchedule: Boolean; -var - RegistryErrorControlled :TRegistryErrorControlled; -begin - try - Result := False; - RegistryErrorControlled := TRegistryErrorControlled.Create; - - try - if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then - begin - if RegistryErrorControlled.ValueExists(SRegValue_CheckForUpdates) and not RegistryErrorControlled.ReadBool(SRegValue_CheckForUpdates) and not FForce then - begin - Exit; - end; - if RegistryErrorControlled.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - RegistryErrorControlled.ReadDateTime(SRegValue_LastUpdateCheckTime) < 1) and not FForce then - begin - Exit; - end; - // Else Time to check for updates - Result := True; - end; - finally - RegistryErrorControlled.Free; - end; - except - { we will not run the check if an error occurs reading the settings } - on E:Exception do - begin - Result := False; - FErrorMessage := E.Message; - Exit; - end; - end; -end; - function TUpdateStateMachine.GetState: TStateClass; begin Result := TStateClass(CurrentState.ClassType); @@ -724,22 +680,20 @@ function IdleState.HandleKmShell; var CheckForUpdates: TRemoteUpdateCheck; UpdateCheckResult : TRemoteUpdateCheckResult; -//const CheckPeriod: Integer = 7; // Days between checking for updates begin - // Check if auto updates enable and if scheduled time has expired - if ConfigCheckContinue then + // Remote manages the last check time therfore + // we will allow it to return early if it hasn't reached + // the configured time between checks. + CheckForUpdates := TRemoteUpdateCheck.Create(False); + try + UpdateCheckResult:= CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; + { Response OK and Update is available } + if UpdateCheckResult = wucSuccess then begin - CheckForUpdates := TRemoteUpdateCheck.Create(True); - try - UpdateCheckResult:= CheckForUpdates.Run; - finally - CheckForUpdates.Free; - end; - { Response OK and Update is available } - if UpdateCheckResult = wucSuccess then - begin - ChangeState(UpdateAvailableState); - end; + ChangeState(UpdateAvailableState); end; Result := kmShellContinue; end; @@ -1299,44 +1253,4 @@ begin Result := 'PostInstallState'; end; -// Private Functions: -function ConfigCheckContinue: Boolean; -var - registry: TRegistryErrorControlled; -begin -{ Verify that it has been at least CheckPeriod days since last update check } - Result := False; - try - registry := TRegistryErrorControlled.Create; // I2890 - try - if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then - begin - if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then - begin - Exit; - end; - if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then - begin - Result := True; - end - else - begin - Result := False; - end; - Exit; - end; - finally - registry.Free; - end; - except - { we will not run the check if an error occurs reading the settings } - on E:Exception do - begin - Result := False; - LogMessage(E.Message); - Exit; - end; - end; -end; - end. diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 31846bf8e1..4871d93976 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -36,7 +36,8 @@ begin Result := False; UpdateStr := 'usPostInstall'; - if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(SRegKey_KeymanEngine_CU), 0, KEY_ALL_ACCESS, hk) = ERROR_SUCCESS then + + if RegCreateKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_KeymanEngine_CU), 0, NULL, KEY_ALL_ACCESS, NULL, &hk, NULL) = ERROR_SUCCESS then begin try if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PWideChar(UpdateStr), Length(UpdateStr) * SizeOf(Char)) = ERROR_SUCCESS then @@ -45,7 +46,7 @@ begin end else begin - // error log + // TODO-WINDOWS-UPDATES: error log end; finally RegCloseKey(hk); @@ -53,7 +54,7 @@ begin end else begin - // TODO: couldn't open registry key + // TODO-WINDOWS-UPDATES: error log creating key end; end; From 3cf1fe5895276a053ffaaa96e7c73ce596e1bf36 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:03:47 +1000 Subject: [PATCH 065/124] feat(windows): use FStateInstance array instead Use FStateInstance array instead of invidual StateClass flags --- .../main/Keyman.System.UpdateStateMachine.pas | 109 +++++++----------- 1 file changed, 42 insertions(+), 67 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index ea19ab7066..3328b76fac 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -211,17 +211,13 @@ type CurrentState: TState; // State object for performance (could lazy create?) - FIdle: IdleState; - FUpdateAvailable: UpdateAvailableState; - FDownloading: DownloadingState; - FWaitingRestart: WaitingRestartState; - FInstalling: InstallingState; - FRetry: RetryState; - FPostInstall: PostInstallState; + + FStateInstance: array[TUpdateState] of TState; + function GetState: TStateClass; procedure SetState(const Value: TStateClass); - procedure SetStateOnly(const Value: TStateClass); - function ConvertEnumState(const TEnumState: TUpdateState): TStateClass; + procedure SetStateOnly(const enumState: TUpdateState); + function ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; procedure ShutDown; { @@ -313,18 +309,22 @@ begin FForce := AForce; FAutomaticUpdate := GetAutomaticUpdates; - FIdle := IdleState.Create(Self); - FUpdateAvailable := UpdateAvailableState.Create(Self); - FDownloading := DownloadingState.Create(Self); - FWaitingRestart := WaitingRestartState.Create(Self); - FInstalling := InstallingState.Create(Self); - FRetry := RetryState.Create(Self); - FPostInstall := PostInstallState.Create(Self); + + FStateInstance[usIdle] := IdleState.Create(Self); + FStateInstance[usUpdateAvailable] := UpdateAvailableState.Create(Self); + FStateInstance[usDownloading] := DownloadingState.Create(Self); + FStateInstance[usWaitingRestart] := WaitingRestartState.Create(Self); + FStateInstance[usInstalling] := InstallingState.Create(Self); + FStateInstance[usRetry] := RetryState.Create(Self); + FStateInstance[usPostInstall] := PostInstallState.Create(Self); + // Check the Registry setting. - SetStateOnly(ConvertEnumState(CheckRegistryState)); + SetStateOnly(CheckRegistryState); end; destructor TUpdateStateMachine.Destroy; +var + lpState: TUpdateState; begin if (FErrorMessage <> '') and FShowErrors then KL.Log(FErrorMessage); // TODO: #10210 Log to Sentry @@ -332,13 +332,10 @@ begin if FParams.Result = oucShutDown then ShutDown; - FIdle.Free; - FUpdateAvailable.Free; - FDownloading.Free; - FWaitingRestart.Free; - FInstalling.Free; - FRetry.Free; - FPostInstall.Free; + for lpState := Low(TUpdateState) to High(TUpdateState) do + begin + FStateInstance[lpState].Free; + end; // TODO: #10210 remove debugging comments //KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); @@ -440,11 +437,12 @@ begin Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_Update_State) then begin + // TODO-WINDOWS-UPDATES Check if value in register is valid UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); end else begin - UpdateState := usIdle; // do we need a unknown state ? + UpdateState := usIdle; end; finally Registry.Free; @@ -526,7 +524,7 @@ begin CurrentState.Exit; end; - SetStateOnly(Value); + SetStateOnly(ConvertStateToEnum(Value)); if Assigned(CurrentState) then begin @@ -539,52 +537,29 @@ begin end; -procedure TUpdateStateMachine.SetStateOnly(const Value: TStateClass); +procedure TUpdateStateMachine.SetStateOnly(const enumState: TUpdateState); begin - if Value = IdleState then - begin - CurrentState := FIdle; - end - else if Value = UpdateAvailableState then - begin - CurrentState := FUpdateAvailable; - end - else if Value = DownloadingState then - begin - CurrentState := FDownloading; - end - else if Value = WaitingRestartState then - begin - CurrentState := FWaitingRestart; - end - else if Value = InstallingState then - begin - CurrentState := FInstalling; - end - else if Value = RetryState then - begin - CurrentState := FRetry; - end - else if Value = PostInstallState then - begin - CurrentState := FPostInstall; - end; + CurrentState := FStateInstance[enumState]; end; -function TUpdateStateMachine.ConvertEnumState(const TEnumState: TUpdateState) : TStateClass; +function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; begin - case TEnumState of - usIdle: Result := IdleState; - usUpdateAvailable: Result := UpdateAvailableState; - usDownloading: Result := DownloadingState; - usWaitingRestart: Result := WaitingRestartState; - usInstalling: Result := InstallingState; - usRetry: Result := RetryState; - usPostInstall: Result := PostInstallState; + if StateClass = IdleState then + Result := usIdle + else if StateClass = UpdateAvailableState then + Result := usUpdateAvailable + else if StateClass = DownloadingState then + Result := usDownloading + else if StateClass = WaitingRestartState then + Result := usWaitingRestart + else if StateClass = InstallingState then + Result := usInstalling + else if StateClass = RetryState then + Result := usRetry + else if StateClass = PostInstallState then + Result := usPostInstall else - // TODO: #10210 Log error unknown state setting to idle - Result := IdleState; - end; + KL.Log('Unknown StateClass'); // TODO-WINDOWS-UPDATES end; procedure TUpdateStateMachine.HandleCheck; From f2d0e268a7712293e60f9cf8338249ea42bc3429 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:59:32 +1000 Subject: [PATCH 066/124] feat(windows): remove TUpdateStateMachineParams There was some dead code from before the RemoteUpdateCheck refactor was created. This is removed in this commit --- .../Keyman.System.ExecutionHistory.pas | 49 +++++-------------- .../windows/delphi/general/RegistryKeys.pas | 8 +-- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas index 52fda002dc..33dff40780 100644 --- a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -27,49 +27,26 @@ function RecordKeymanStarted : Boolean; var atom: WORD; begin - Result := False; - try - atom := GlobalFindAtom(AtomName); - if atom = 0 then - begin - if GetLastError <> ERROR_FILE_NOT_FOUND then - RaiseLastOSError; - atom := GlobalAddAtom(AtomName); - Result := True; - if atom = 0 then - RaiseLastOSError; - end; - except - on E: Exception do - // TODO: #10210 convert to sentry error - KL.Log(E.ClassName + ': ' + E.Message); - end; + atom := GlobalAddAtom(AtomName); + if atom = 0 then + begin + // TODO-WINDOWS-UPDATES: #10210 log to sentry + Result := False; + end + else + Result := True; end; function HasKeymanRun : Boolean; -var - atom: WORD; begin - Result := False; - try - atom := GlobalFindAtom(AtomName); - if atom <> 0 then + Result := GlobalFindAtom(AtomName) <> 0; + if not Result then + begin + if GetLastError <> ERROR_FILE_NOT_FOUND then begin - if GetLastError <> ERROR_SUCCESS then - RaiseLastOSError; - Result := True; - end - else - begin - Result := False; + // TODO-WINDOWS-UPDATES: log to Sentry end; - - except - on E: Exception do - // TODO: #10210 convert to sentry error - KL.log(E.ClassName + ': ' + E.Message); end; - end; end. diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 5bcbb96792..bacca938c6 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -175,10 +175,10 @@ const SRegValue_CharMapSourceData = 'charmap source data'; // LM - SRegValue_AvailableLanguages = 'available languages'; //CU - SRegValue_CurrentLanguage = 'current language'; //CU + SRegValue_AvailableLanguages = 'available languages'; // CU + SRegValue_CurrentLanguage = 'current language'; // CU - SRegValue_Update_State = 'update state'; + SRegValue_Update_State = 'update state'; // CU { Privacy } @@ -317,7 +317,7 @@ const SRegValue_AutomaticUpdates = 'automatic updates'; //CU SRegValue_CheckForUpdates = 'check for updates'; // CU SRegValue_LastUpdateCheckTime = 'last update check time'; // CU - SRegValue_ApplyNow = 'apply now'; // CU Start the install now even though it will require an update + SRegValue_ApplyNow = 'apply now'; // CU Start the install now even though it will require an restart SRegValue_UpdateCheck_UseProxy = 'update check use proxy'; // CU SRegValue_UpdateCheck_ProxyHost = 'update check proxy host'; // CU From 544b955570969a17b63fdf902533ca85b1679404 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:24:51 +1000 Subject: [PATCH 067/124] feat(windows): add error checking - private class --- .../main/Keyman.System.DownloadUpdate.pas | 119 +-- .../main/Keyman.System.UpdateStateMachine.pas | 735 +++++++----------- 2 files changed, 328 insertions(+), 526 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index 57c8a2cad0..9a3a27995b 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -26,31 +26,32 @@ type private FShowErrors: Boolean; FDownload: TDownloadUpdateParams; - FErrorMessage: string; - { - Performs updates download in the background, without displaying a GUI - progress bar. - @params SavePath The path where the downloaded files will be saved. - Result A Boolean value indicating the overall result of the - download process. - } + (** + * + * Performs updates download in the background, without displaying a GUI + * progress bar. + * @params SavePath The path where the downloaded files will be saved. + * Result A Boolean value indicating the overall result of the + * download process. + *) procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); public constructor Create; destructor Destroy; override; - { - Performs updates download in the background, without displaying a GUI - progress bar. This function is similar to DownloadUpdates, but it runs in - the background. + (** + * Performs updates download in the background, without displaying a GUI + * progress bar. This function is similar to DownloadUpdates, but it runs in + * the background. - @returns True if all updates were successfully downloaded, False if any - download failed. - } + * @returns True if all updates were successfully downloaded, False if any + * download failed. + *) function DownloadUpdates : Boolean; - function CheckAllFilesDownloaded : Boolean; + // TODO-WINDOWS-UPDATES: verify filesizes match the ucr metadata so we know we don't have partial downloades. + //function VerifyAllFilesDownloaded : Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; end; @@ -90,18 +91,15 @@ end; destructor TDownloadUpdate.Destroy; begin - if (FErrorMessage <> '') and FShowErrors then - LogMessage(FErrorMessage); - - KL.Log('TDownloadUpdate.Destroy: FErrorMessage = '+FErrorMessage); inherited Destroy; end; procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); var - i, downloadCount: Integer; + i : Integer; http: THttpUploader; fs: TFileStream; + InstallerDownloaded: Boolean; function DownloadFile(const url, savepath: string): Boolean; begin @@ -150,47 +148,38 @@ begin 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; - - // Add the Keyman installer - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.InstallSize); // Keyboard Packages FDownload.StartPosition := 0; 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; 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); + end; FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; end; // Keyman Installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 begin // TODO-WINDOWS-UPDATES: #10210 convert to error log. LogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); + InstallerDownloaded := False; end else begin - Inc(downloadCount) + InstallerDownloaded := True; end; - // There needs to be at least one file successfully downloaded to return - // True that files were downloaded - if downloadCount > 0 then + // If installer has downloaded that is success even + // if zero packages where downloaded. + if InstallerDownloaded then Result := True; end; @@ -211,52 +200,4 @@ begin Result := False; end; -function TDownloadUpdate.CheckAllFilesDownloaded: Boolean; -var - i : Integer; - SavedPath : String; - DownloadResult : Boolean; - Params: TUpdateCheckResponse; - VerifyDownloads : TDownloadUpdateParams; - FileNames : TStringDynArray; - -begin - SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, FileNames); - if Length(FileNames) = 0 then - begin - Result := False; - Exit; - end; - - if TUpdateCheckStorage.LoadUpdateCacheData(Params) then - begin - for i := 0 to High(Params.Packages) do - begin - Inc(VerifyDownloads.TotalDownloads); - Inc(VerifyDownloads.TotalSize, Params.Packages[i].DownloadSize); - if not MatchStr(Params.Packages[i].FileName, FileNames) then - begin - Exit(False); - end; - Params.Packages[i].SavePath := SavedPath + Params.Packages[i].FileName; - end; - // Add the Keyman installer - Inc(FDownload.TotalDownloads); - Inc(FDownload.TotalSize, Params.InstallSize); - // Check if the Keyman installer downloaded - if not MatchStr(Params.FileName, FileNames) then - begin - Exit(False); - end; - // TODO-WINDOWS-UPDATES: verify filesizes match so we know we don't have partial downloades. - Result := True; - end - else - begin - Result := False; - end; - -end; - end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 3328b76fac..eaf6b50bd8 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1,71 +1,32 @@ -{ - Keyman is copyright (C) SIL Global. MIT License. - - Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md -} +(* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md + *) unit Keyman.System.UpdateStateMachine; interface uses - System.Classes, System.SysUtils, System.UITypes, System.IOUtils, System.Types, System.TypInfo, - Vcl.Forms, httpuploader, KeymanPaths, - Keyman.System.UpdateCheckResponse, Keyman.Configuration.UI.UfrmStartInstall, Keyman.Configuration.UI.UfrmStartInstallNow, Keyman.System.ExecutionHistory, - UfrmDownloadProgress, + Keyman.System.UpdateCheckResponse, utilkmshell; type EUpdateStateMachine = class(Exception); - TUpdateStateMachineResult = (oucUnknown, oucShutDown, oucSuccess, oucNoUpdates, oucUpdatesAvailable, oucFailure, oucOffline); - - TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, usInstalling, usRetry, usPostInstall); - - { Keyboard Package Params } - TUpdateStateMachineParamsPackage = record - ID: string; - NewID: string; - Description: string; - OldVersion, NewVersion: string; - DownloadURL: string; - SavePath: string; - FileName: string; - DownloadSize: Integer; - Install: Boolean; - end; - { Main Keyman Program } - TUpdateStateMachineParamsKeyman = record - OldVersion, NewVersion: string; - DownloadURL: string; - SavePath: string; - FileName: string; - DownloadSize: Integer; - Install: Boolean; - end; - - TUpdateStateMachineParams = record - Keyman: TUpdateStateMachineParamsKeyman; - Packages: array of TUpdateStateMachineParamsPackage; - Result: TUpdateStateMachineResult; - end; - - TUpdateStateMachineDownloadParams = record - Owner: TfrmDownloadProgress; - TotalSize: Integer; - TotalDownloads: Integer; - StartPosition: Integer; - end; + TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, + usInstalling, usRetry, usPostInstall); // Forward declaration TUpdateStateMachine = class; @@ -84,27 +45,100 @@ type procedure Enter; virtual; abstract; procedure Exit; virtual; abstract; procedure HandleCheck; virtual; abstract; - function HandleKmShell : Integer; virtual; abstract; + function HandleKmShell: Integer; virtual; abstract; procedure HandleDownload; virtual; abstract; procedure HandleAbort; virtual; abstract; procedure HandleInstallNow; virtual; abstract; + end; - // For convenience - function StateName: string; virtual; abstract; + { This class also controls the state flow see + ../BackgroundUpdateStateDiagram.md } + TUpdateStateMachine = class + private + FForce: Boolean; + FAutomaticUpdate: Boolean; + FErrorMessage: string; + FShowErrors: Boolean; + + CurrentState: TState; + // State object for performance (could lazy create?) + + FStateInstance: array [TUpdateState] of TState; + + function GetState: TStateClass; + procedure SetState(const Value: TStateClass); + procedure SetStateOnly(const enumState: TUpdateState); + function ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; + function IsCurrentStateAssigned: Boolean; + + function SetRegistryState(Update: TUpdateState): Boolean; + function GetAutomaticUpdates: Boolean; + function SetApplyNow(Value: Boolean): Boolean; + function GetApplyNow: Boolean; + + protected + property State: TStateClass read GetState write SetState; + + public + constructor Create(AForce: Boolean); + destructor Destroy; override; + + procedure HandleCheck; + function HandleKmShell: Integer; + procedure HandleDownload; + procedure HandleAbort; + procedure HandleInstallNow; + function CurrentStateName: string; + + property ShowErrors: Boolean read FShowErrors write FShowErrors; + function CheckRegistryState: TUpdateState; end; - // Derived classes for each state +implementation + +uses + + Winapi.Windows, + Winapi.WinINet, + ErrorControlledRegistry, + + GlobalProxySettings, + Keyman.System.DownloadUpdate, + Keyman.System.RemoteUpdateCheck, + KLog, + RegistryKeys, + utilexecute; + +const + SPackageUpgradeFilename = 'upgrade_packages.inf'; + kmShellContinue = 0; + kmShellExit = 1; + + { State Class Memebers } + +constructor TState.Create(Context: TUpdateStateMachine); +begin + bucStateContext := Context; +end; + +procedure TState.ChangeState(newState: TStateClass); +begin + bucStateContext.State := newState; +end; + +type + +// Derived classes for each state IdleState = class(TState) public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; UpdateAvailableState = class(TState) @@ -114,11 +148,10 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; DownloadingState = class(TState) @@ -128,11 +161,10 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; WaitingRestartState = class(TState) @@ -140,35 +172,33 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; InstallingState = class(TState) private - procedure DoInstallKeyman; overload; - function DoInstallKeyman(SavePath: string) : Boolean; overload; - { - Installs the Keyman file using either msiexec.exe or the setup launched in - a separate shell. - @params Package The package to be installed. + (** + * Installs the Keyman setup file using separate shell. + * + * @params SavePath The path to the downloaded files. + * + * @returns True if the installation is successful, False otherwise. + *) + + function DoInstallKeyman(SavePath: string): Boolean; overload; - @returns True if the installation is successful, False otherwise. - } - function DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; RetryState = class(TState) @@ -176,136 +206,31 @@ type procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; PostInstallState = class(TState) private - procedure HandleMSIInstallComplete; + procedure HandleMSIInstallComplete; public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell : Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; - function StateName: string; override; end; + { TUpdateStateMachine } - { This class also controls the state flow see } - TUpdateStateMachine = class - private - FForce: Boolean; - FAutomaticUpdate: Boolean; - FParams: TUpdateStateMachineParams; - FErrorMessage: string; - DownloadTempPath: string; - FShowErrors: Boolean; - FDownload: TUpdateStateMachineDownloadParams; - - CurrentState: TState; - // State object for performance (could lazy create?) - - FStateInstance: array[TUpdateState] of TState; - - function GetState: TStateClass; - procedure SetState(const Value: TStateClass); - procedure SetStateOnly(const enumState: TUpdateState); - function ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; - - procedure ShutDown; - { - SavePackageUpgradesToDownloadTempPath saves any new package IDs to a - single file in the download tempPath. This procedure saves the IDs of any - new packages to a file named "upgrade_packages.inf" in the download - tempPath. - } - procedure SavePackageUpgradesToDownloadTempPath; - - function SetRegistryState (Update : TUpdateState): Boolean; - function GetAutomaticUpdates: Boolean; - function SetApplyNow(Value : Boolean): Boolean; - function GetApplyNow: Boolean; - - protected - property State: TStateClass read GetState write SetState; - - public - constructor Create(AForce: Boolean); - destructor Destroy; override; - - procedure HandleCheck; - function HandleKmShell : Integer; - procedure HandleDownload; - procedure HandleAbort; - procedure HandleInstallNow; - function CurrentStateName: string; - - property ShowErrors: Boolean read FShowErrors write FShowErrors; - function CheckRegistryState : TUpdateState; - - end; - - IOnlineUpdateSharedData = interface - ['{7442A323-C1E3-404B-BEEA-5B24A52BBB0E}'] - function Params: TUpdateStateMachineParams; - end; - - TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) - private - FParams: TUpdateStateMachineParams; - public - constructor Create(AParams: TUpdateStateMachineParams); - function Params: TUpdateStateMachineParams; -end; - -implementation - -uses - Winapi.Shlobj, - System.WideStrUtils, - Vcl.Dialogs, - Winapi.ShellApi, - Winapi.Windows, - Winapi.WinINet, - - GlobalProxySettings, - KLog, - keymanapi_TLB, - KeymanVersion, - kmint, - ErrorControlledRegistry, - RegistryKeys, - Upload_Settings, - utildir, - utilexecute, - OnlineUpdateCheckMessages, // TODO-WINDOWS-UPDATES: create own messages - UfrmOnlineUpdateIcon, - UfrmOnlineUpdateNewVersion, - utilsystem, - utiluac, - versioninfo, - Keyman.System.RemoteUpdateCheck, - Keyman.System.DownloadUpdate; - -const - SPackageUpgradeFilename = 'upgrade_packages.inf'; - kmShellContinue = 0; - kmShellExit = 1; - -{ TUpdateStateMachine } - -constructor TUpdateStateMachine.Create(AForce : Boolean); +constructor TUpdateStateMachine.Create(AForce: Boolean); begin inherited Create; FShowErrors := True; - FParams.Result := oucUnknown; FForce := AForce; FAutomaticUpdate := GetAutomaticUpdates; @@ -329,67 +254,21 @@ begin if (FErrorMessage <> '') and FShowErrors then KL.Log(FErrorMessage); // TODO: #10210 Log to Sentry - if FParams.Result = oucShutDown then - ShutDown; - for lpState := Low(TUpdateState) to High(TUpdateState) do begin FStateInstance[lpState].Free; end; // TODO: #10210 remove debugging comments - //KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); - //KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); + // KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); + // KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); inherited Destroy; end; - -procedure TUpdateStateMachine.SavePackageUpgradesToDownloadTempPath; +function TUpdateStateMachine.SetRegistryState(Update: TUpdateState): Boolean; var - i: Integer; - StringList : TStringList; -begin - StringList := TStringList.Create; - try - for i := 0 to High(FParams.Packages) do - begin - if FParams.Packages[i].NewID <> '' then - begin - StringList.Add(FParams.Packages[i].NewID+'='+FParams.Packages[i].ID); - end; - end; - if StringList.Count > 0 then - begin - StringList.SaveToFile(DownloadTempPath + SPackageUpgradeFileName); - end; - finally - StringList.Free; - end; -end; - -procedure TUpdateStateMachine.ShutDown; -begin - if Assigned(Application) then - Application.Terminate; -end; - -{ TOnlineUpdateSharedData } - -constructor TOnlineUpdateSharedData.Create(AParams: TUpdateStateMachineParams); -begin - inherited Create; - FParams := AParams; -end; - -function TOnlineUpdateSharedData.Params: TUpdateStateMachineParams; -begin - Result := FParams; -end; - -function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; -var - UpdateStr : string; + UpdateStr: string; Registry: TRegistryErrorControlled; begin Result := False; @@ -423,26 +302,39 @@ begin end; -function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 +function TUpdateStateMachine.CheckRegistryState: TUpdateState; var UpdateState: TUpdateState; Registry: TRegistryErrorControlled; - + StateValue: string; + EnumValue: Integer; begin - // We will use a registry flag to maintain the state of the background update + // Default to Idle state if any issues occur + UpdateState := usIdle; + Registry := TRegistryErrorControlled.Create; - // check the registry value - Registry := TRegistryErrorControlled.Create; // I2890 try Registry.RootKey := HKEY_CURRENT_USER; - if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Registry.ValueExists(SRegValue_Update_State) then + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + Registry.ValueExists(SRegValue_Update_State) then begin - // TODO-WINDOWS-UPDATES Check if value in register is valid - UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); - end - else - begin - UpdateState := usIdle; + try + StateValue := Registry.ReadString(SRegValue_Update_State); + EnumValue := GetEnumValue(TypeInfo(TUpdateState), StateValue); + + // Bounds Check EnumValue against TUpdateState + if (EnumValue >= Ord(Low(TUpdateState))) and (EnumValue <= Ord(High(TUpdateState))) then + UpdateState := TUpdateState(EnumValue) + else + UpdateState := usIdle; // Default if out of bounds + except + on E: Exception do + begin + // TODO: #10210 Log to Sentry + KL.Log('Failed to write to registry: ' + E.Message); + UpdateState := usIdle; + end; + end; end; finally Registry.Free; @@ -451,24 +343,33 @@ begin Result := UpdateState; end; -function TUpdateStateMachine.GetAutomaticUpdates: Boolean; // I2329 +function TUpdateStateMachine.GetAutomaticUpdates: Boolean; // I2329 var Registry: TRegistryErrorControlled; begin // check the registry value - Registry := TRegistryErrorControlled.Create; // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try Registry.RootKey := HKEY_CURRENT_USER; - Result := not Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) or - not Registry.ValueExists(SRegValue_AutomaticUpdates) or - Registry.ReadBool(SRegValue_AutomaticUpdates); + try + Result := not Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) or + not Registry.ValueExists(SRegValue_AutomaticUpdates) or + Registry.ReadBool(SRegValue_AutomaticUpdates); + except + on E: Exception do + begin + // TODO: #10210 Log to Sentry + KL.Log('Failed to read registery: ' + E.Message); + Result := False; + end; + end; finally Registry.Free; end; end; -function TUpdateStateMachine.SetApplyNow(Value : Boolean): Boolean; +function TUpdateStateMachine.SetApplyNow(Value: Boolean): Boolean; var Registry: TRegistryErrorControlled; begin @@ -504,9 +405,17 @@ begin Registry := TRegistryErrorControlled.Create; try Registry.RootKey := HKEY_CURRENT_USER; - Result := Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and - Registry.ValueExists(SRegValue_ApplyNow) and - Registry.ReadBool(SRegValue_ApplyNow); + try + Result := Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + Registry.ValueExists(SRegValue_ApplyNow) and + Registry.ReadBool(SRegValue_ApplyNow); + except + on E: Exception do + begin + KL.Log('Failed to read registry: ' + E.Message); + Result := False; + end; + end; finally Registry.Free; end; @@ -514,7 +423,14 @@ end; function TUpdateStateMachine.GetState: TStateClass; begin - Result := TStateClass(CurrentState.ClassType); + if Assigned(CurrentState) then + Result := TStateClass(CurrentState.ClassType) + else + begin + // TODO: #10210 Log to Sentry + KL.Log('Error CurrentState was uninitiallised: ' ); + Result := nil; + end; end; procedure TUpdateStateMachine.SetState(const Value: TStateClass); @@ -532,7 +448,7 @@ begin end else begin - // TODO: #10210 Error log for Unable to set state for Value + // TODO: #10210 Error log for Unable to set state for Value end; end; @@ -542,7 +458,7 @@ begin CurrentState := FStateInstance[enumState]; end; -function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; +function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass) : TUpdateState; begin if StateClass = IdleState then Result := usIdle @@ -559,48 +475,65 @@ begin else if StateClass = PostInstallState then Result := usPostInstall else - KL.Log('Unknown StateClass'); // TODO-WINDOWS-UPDATES + begin + // TODO: #10210 Log to Sentry + Result := usIdle; + KL.Log('Unknown StateClass'); // TODO-WINDOWS-UPDATES + end; +end; + +function TUpdateStateMachine.IsCurrentStateAssigned: Boolean; +begin + if Assigned(CurrentState) then + Result := True + else + begin + // TODO: #10210 Log to Sentry + KL.Log('Unexpected Error: Current state is not assigned.'); + Result := False; + end; end; procedure TUpdateStateMachine.HandleCheck; begin + if not IsCurrentStateAssigned then + Exit; CurrentState.HandleCheck; end; -function TUpdateStateMachine.HandleKmShell; +function TUpdateStateMachine.HandleKmShell: Integer; begin + if not IsCurrentStateAssigned then + Exit(kmShellContinue); Result := CurrentState.HandleKmShell; end; procedure TUpdateStateMachine.HandleDownload; begin + if not IsCurrentStateAssigned then + Exit; CurrentState.HandleDownload; end; procedure TUpdateStateMachine.HandleAbort; begin + if not IsCurrentStateAssigned then + Exit; CurrentState.HandleAbort; end; procedure TUpdateStateMachine.HandleInstallNow; begin + if not IsCurrentStateAssigned then + Exit; CurrentState.HandleInstallNow; end; function TUpdateStateMachine.CurrentStateName: string; begin - Result := CurrentState.StateName; -end; - -{ State Class Memebers } -constructor TState.Create(Context: TUpdateStateMachine); -begin - bucStateContext := Context; -end; - -procedure TState.ChangeState(NewState: TStateClass); -begin - bucStateContext.State := NewState; + if not IsCurrentStateAssigned then + Exit('Undefined'); + Result := CurrentState.ClassName; end; { IdleState } @@ -622,11 +555,9 @@ var Result : TRemoteUpdateCheckResult; begin - {##### For Testing only just advancing to downloading ####} + { ##### For Testing only just advancing to downloading #### } ChangeState(UpdateAvailableState); - {#### End of Testing ### }; - - + { #### End of Testing ### }; { Make a HTTP request out and see if updates are available for now do this all in the Idle HandleCheck message. But could be broken into an @@ -635,33 +566,33 @@ begin // If handle check event force check - //CheckForUpdates := TRemoteUpdateCheck.Create(True); - //try - // Result:= CheckForUpdates.Run; - // finally - // CheckForUpdates.Free; - // end; + // CheckForUpdates := TRemoteUpdateCheck.Create(True); + // try + // Result:= CheckForUpdates.Run; + // finally + // CheckForUpdates.Free; + // end; { Response OK and Update is available } - // if Result = wucSuccess then - // begin - // ChangeState(UpdateAvailableState); - // end; + // if Result = wucSuccess then + // begin + // ChangeState(UpdateAvailableState); + // end; // else staty in idle state end; function IdleState.HandleKmShell; var - CheckForUpdates: TRemoteUpdateCheck; - UpdateCheckResult : TRemoteUpdateCheckResult; + CheckForUpdates: TRemoteUpdateCheck; + UpdateCheckResult: TRemoteUpdateCheckResult; begin // Remote manages the last check time therfore // we will allow it to return early if it hasn't reached // the configured time between checks. CheckForUpdates := TRemoteUpdateCheck.Create(False); try - UpdateCheckResult:= CheckForUpdates.Run; + UpdateCheckResult := CheckForUpdates.Run; finally CheckForUpdates.Free; end; @@ -675,7 +606,7 @@ end; procedure IdleState.HandleDownload; begin - // Do Nothing + // Do Nothing end; procedure IdleState.HandleAbort; @@ -686,21 +617,15 @@ end; procedure IdleState.HandleInstallNow; begin bucStateContext.CurrentState.HandleCheck; - // TODO: How do we notify the command line no update available -end; - -function IdleState.StateName; -begin - - Result := 'IdleState'; + // TODO: How do we notify the command line no update available end; { UpdateAvailableState } - procedure UpdateAvailableState.StartDownloadProcess; -var DownloadResult, FResult : Boolean; -RootPath: string; +var + FResult: Boolean; + RootPath: string; begin // call seperate process RootPath := ExtractFilePath(ParamStr(0)); @@ -743,7 +668,7 @@ end; procedure UpdateAvailableState.HandleDownload; begin - ChangeState(DownloadingState); + ChangeState(DownloadingState); end; procedure UpdateAvailableState.HandleAbort; @@ -753,8 +678,8 @@ end; procedure UpdateAvailableState.HandleInstallNow; var - frmStartInstallNow : TfrmStartInstallNow; - InstallNow : Boolean; + frmStartInstallNow: TfrmStartInstallNow; + InstallNow: Boolean; begin InstallNow := True; @@ -762,7 +687,7 @@ begin begin // TODO: UI and non-UI units should be split // if the unit launches UI then it should be a .UI. unit - //https://github.com/keymanapp/keyman/pull/12375/files#r1751041747 + // https://github.com/keymanapp/keyman/pull/12375/files#r1751041747 frmStartInstallNow := TfrmStartInstallNow.Create(nil); try if frmStartInstallNow.ShowModal = mrOk then @@ -782,30 +707,25 @@ begin end; -function UpdateAvailableState.StateName; -begin - Result := 'UpdateAvailableState'; -end; - { DownloadingState } procedure DownloadingState.Enter; -var DownloadResult, FResult : Boolean; -RootPath: string; +var + DownloadResult: Boolean; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); - {## for testing log that we would download } + { ## for testing log that we would download } KL.Log('DownloadingState.HandleKmshell test code continue'); DownloadResult := True; - { End testing} - //DownloadResult := DownloadUpdatesBackground; + { End testing } + DownloadResult := DownloadUpdatesBackground; // TODO check if keyman is running then send to Waiting Restart if DownloadResult then begin if HasKeymanRun then begin - if bucStateContext.GetApplyNow then + if bucStateContext.GetApplyNow then begin bucStateContext.SetApplyNow(False); ChangeState(InstallingState); @@ -843,8 +763,6 @@ begin end; procedure DownloadingState.HandleDownload; -var DownloadResult, FResult : Boolean; -RootPath: string; begin // Enter Already Downloading end; @@ -859,28 +777,22 @@ begin bucStateContext.SetApplyNow(True); end; -function DownloadingState.StateName; -begin - Result := 'DownloadingState'; -end; - function DownloadingState.DownloadUpdatesBackground: Boolean; var - DownloadBackGroundSavePath : String; - DownloadResult : Boolean; + DownloadResult: Boolean; DownloadUpdate: TDownloadUpdate; begin DownloadUpdate := TDownloadUpdate.Create; try DownloadResult := DownloadUpdate.DownloadUpdates; Result := DownloadResult; -// #TODO: #10210 workout when we need to refresh kmcom keyboards -// if Result in [ wucSuccess] then -// begin -// kmcom.Keyboards.Refresh; -// kmcom.Keyboards.Apply; -// kmcom.Packages.Refresh; -// end; + // #TODO: #10210 workout when we need to refresh kmcom keyboards + // if Result in [ wucSuccess] then + // begin + // kmcom.Keyboards.Refresh; + // kmcom.Keyboards.Apply; + // kmcom.Packages.Refresh; + // end; finally DownloadUpdate.Free; end; @@ -906,9 +818,9 @@ end; function WaitingRestartState.HandleKmShell; var - SavedPath : String; - Filenames : TStringDynArray; - frmStartInstall : TfrmStartInstall; + SavedPath: String; + Filenames: TStringDynArray; + frmStartInstall: TfrmStartInstall; begin // Still can't go if keyman has run if HasKeymanRun then @@ -920,15 +832,16 @@ begin else begin // Check downloaded cache if available then - SavedPath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, FileNames); - if Length(FileNames) = 0 then + SavedPath := IncludeTrailingPathDelimiter + (TKeymanPaths.KeymanUpdateCachePath); + GetFileNamesInDirectory(SavedPath, Filenames); + if Length(Filenames) = 0 then begin - // Return to Idle state and check for Updates state - ChangeState(IdleState); - bucStateContext.CurrentState.HandleCheck; // TODO no event here - Result := kmShellExit; - // Exit; // again exit was not working + // Return to Idle state and check for Updates state + ChangeState(IdleState); + bucStateContext.CurrentState.HandleCheck; // TODO no event here + Result := kmShellExit; + // Exit; // again exit was not working end else begin @@ -962,8 +875,8 @@ end; procedure WaitingRestartState.HandleInstallNow; // If user decides not to install now stay in WaitingRestart State var - frmStartInstallNow : TfrmStartInstallNow; - InstallNow : Boolean; + frmStartInstallNow: TfrmStartInstallNow; + InstallNow: Boolean; begin InstallNow := True; if HasKeymanRun then @@ -985,68 +898,31 @@ begin end; end; -function WaitingRestartState.StateName; -begin - - Result := 'WaitingRestartState'; -end; - -{ InstallingState } -function InstallingState.DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; -var - FPackage: IKeymanPackageFile2; -begin - Result := True; - - FPackage := kmcom.Packages.GetPackageFromFile(Package.SavePath) as IKeymanPackageFile2; - FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact - FPackage := nil; - - kmcom.Refresh; - kmcom.Apply; - System.SysUtils.DeleteFile(Package.SavePath); -end; - -procedure InstallingState.DoInstallKeyman; -var - s: string; - FResult: Boolean; -begin - FResult := False; - s := LowerCase(ExtractFileExt(bucStateContext.FParams.Keyman.SavePath)); - if s = '.msi' then - FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+bucStateContext.FParams.Keyman.SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 - else if s = '.exe' then - FResult := TUtilExecute.Shell(0, bucStateContext.FParams.Keyman.SavePath, '', '-au') // I3349 - else - Exit; - if not FResult then - ShowMessage(SysErrorMessage(GetLastError)); -end; - -function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; +function InstallingState.DoInstallKeyman(SavePath: string): Boolean; var s: string; FResult: Boolean; begin s := LowerCase(ExtractFileExt(SavePath)); if s = '.msi' then - FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "'+SavePath+'" AUTOLAUNCHPRODUCT=1') // I3349 + FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "' + SavePath + + '" AUTOLAUNCHPRODUCT=1') // I3349 else if s = '.exe' then begin // switch -au for auto update in silent mode. // We will need to add the pop up that says install update now yes/no - // This will run the setup executable which will ask for elevated permissions - FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 + // This will run the setup executable which will ask for elevated permissions + FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 end else FResult := False; if not FResult then begin - // TODO: #10210 Log to Sentry - KL.Log('TUpdateStateMachine.InstallingState.DoInstall: Result = '+IntToStr(Ord(FResult))); - // Log messageShowMessage(SysErrorMessage(GetLastError)); + // TODO: #10210 Log to Sentry + KL.Log('TUpdateStateMachine.InstallingState.DoInstall: Result = ' + + IntToStr(Ord(FResult))); + // Log message ShowMessage(SysErrorMessage(GetLastError)); end; Result := FResult; @@ -1055,34 +931,35 @@ end; procedure InstallingState.Enter; var SavePath: String; - fileExt : String; + fileExt: String; fileName: String; - fileNames: TStringDynArray; + Filenames: TStringDynArray; begin - bucStateContext.SetRegistryState(usInstalling); - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, fileNames); - // for now we only want the exe although excute install can - // handle msi - for fileName in fileNames do - begin - fileExt := LowerCase(ExtractFileExt(fileName)); - if fileExt = '.exe' then - break; - end; + bucStateContext.SetRegistryState(usInstalling); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then - begin - KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall OK'); - end - else - begin - // TODO: #10210 clean failed download - // TODO: #10210 Do we do a retry on install? probably not - KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); - ChangeState(IdleState); - end + GetFileNamesInDirectory(SavePath, Filenames); + // for now we only want the exe although excute install can + // handle msi + for fileName in Filenames do + begin + fileExt := LowerCase(ExtractFileExt(fileName)); + if fileExt = '.exe' then + break; + end; + + if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then + begin + KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall OK'); + end + else + begin + // TODO: #10210 clean failed download + // TODO: #10210 Do we do a retry on install? probably not + KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); + ChangeState(IdleState); + end end; procedure InstallingState.Exit; @@ -1118,11 +995,6 @@ begin // Do Nothing. Need the UI to let user know installation in progress OR end; -function InstallingState.StateName; -begin - Result := 'InstallingState'; -end; - { RetryState } procedure RetryState.Enter; @@ -1162,12 +1034,6 @@ begin ChangeState(InstallingState); end; -function RetryState.StateName; -begin - - Result := 'RetryState'; -end; - { PostInstallState } procedure PostInstallState.Enter; @@ -1198,18 +1064,19 @@ begin end; procedure PostInstallState.HandleMSIInstallComplete; -var SavePath: string; - FileName: String; - FileNames: TStringDynArray; +var + SavePath: string; + fileName: String; + Filenames: TStringDynArray; begin - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, FileNames); - for FileName in FileNames do - begin - System.SysUtils.DeleteFile(FileName); - end; - ChangeState(IdleState); + GetFileNamesInDirectory(SavePath, Filenames); + for fileName in Filenames do + begin + System.SysUtils.DeleteFile(fileName); + end; + ChangeState(IdleState); end; procedure PostInstallState.HandleAbort; @@ -1222,10 +1089,4 @@ begin // Do nothing as files will be cleaned via HandleKmShell end; -function PostInstallState.StateName; -begin - - Result := 'PostInstallState'; -end; - end. From 7be490a67b042afa459c703976894de46e6f0a47 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:33:18 +1000 Subject: [PATCH 068/124] feat(windows): review comment suggestions Co-authored-by: Marc Durdin --- .../kmshell/main/Keyman.System.DownloadUpdate.pas | 13 +++---------- .../Keyman.System.Install.EnginePostInstall.pas | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index 9a3a27995b..373ba513e5 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -12,9 +12,6 @@ uses KeymanPaths, OnlineUpdateCheck; -const - DaysBetweenCheckingForUpdates: Integer = 7; // Days between checking for updates - type TDownloadUpdateParams = record TotalSize: Integer; @@ -170,17 +167,13 @@ begin begin // TODO-WINDOWS-UPDATES: #10210 convert to error log. LogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); - InstallerDownloaded := False; end else begin - InstallerDownloaded := True; - end; - - // If installer has downloaded that is success even - // if zero packages where downloaded. - if InstallerDownloaded then + // If installer has downloaded that is success even + // if zero packages were downloaded. Result := True; + end; end; function TDownloadUpdate.DownloadUpdates: Boolean; diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 4871d93976..56d33c8339 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -40,7 +40,7 @@ begin if RegCreateKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_KeymanEngine_CU), 0, NULL, KEY_ALL_ACCESS, NULL, &hk, NULL) = ERROR_SUCCESS then begin try - if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PWideChar(UpdateStr), Length(UpdateStr) * SizeOf(Char)) = ERROR_SUCCESS then + if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PChar(UpdateStr), (Length(UpdateStr)+1) * SizeOf(Char)) = ERROR_SUCCESS then begin Result := True; end From e239ec710a5e13cdabd7865f14bc05ddbf9c78ad Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:20:12 +1000 Subject: [PATCH 069/124] feat(windows): Review comments --- .../main/Keyman.System.DownloadUpdate.pas | 24 +--- .../main/Keyman.System.RemoteUpdateCheck.pas | 134 ++++++++++-------- .../main/Keyman.System.UpdateStateMachine.pas | 53 +++---- windows/src/desktop/kmshell/main/initprog.pas | 3 +- 4 files changed, 105 insertions(+), 109 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index 373ba513e5..f91c8f6ea4 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -25,26 +25,19 @@ type FDownload: TDownloadUpdateParams; (** * - * Performs updates download in the background, without displaying a GUI - * progress bar. + * Performs updates download in the background. * @params SavePath The path where the downloaded files will be saved. - * Result A Boolean value indicating the overall result of the + * + *@returns A Boolean value indicating the overall result of the * download process. *) - procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); + function DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse): Boolean; public constructor Create; destructor Destroy; override; - (** - * Performs updates download in the background, without displaying a GUI - * progress bar. This function is similar to DownloadUpdates, but it runs in - * the background. - * @returns True if all updates were successfully downloaded, False if any - * download failed. - *) function DownloadUpdates : Boolean; // TODO-WINDOWS-UPDATES: verify filesizes match the ucr metadata so we know we don't have partial downloades. @@ -91,12 +84,11 @@ begin inherited Destroy; end; -procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; var Result: Boolean); +function TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse): Boolean; var i : Integer; http: THttpUploader; fs: TFileStream; - InstallerDownloaded: Boolean; function DownloadFile(const url, savepath: string): Boolean; begin @@ -179,15 +171,13 @@ end; function TDownloadUpdate.DownloadUpdates: Boolean; var DownloadBackGroundSavePath : String; - DownloadResult : Boolean; ucr: TUpdateCheckResponse; begin DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then begin - DoDownloadUpdates(DownloadBackGroundSavePath, ucr, DownloadResult); - KL.Log('DownloadUpdates.DownloadUpdatesBackground: DownloadResult = '+IntToStr(Ord(DownloadResult))); - Result := DownloadResult; + Result := DoDownloadUpdates(DownloadBackGroundSavePath, ucr); + KL.Log('DownloadUpdates.DownloadUpdates: DownloadResult = '+IntToStr(Ord(Result))); end else Result := False; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index ba3e8b62eb..9e4b0b007e 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -1,12 +1,13 @@ -{ +(** * 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 + * for Windows updates. +*) +unit Keyman.System.RemoteUpdateCheck; // I3306 interface + uses System.Classes, System.SysUtils, @@ -35,35 +36,41 @@ type FRemoteResult: TRemoteUpdateCheckResult; FErrorMessage: string; FShowErrors: Boolean; - { - Performs an online update check, including package retrieval and version - query. - - This function checks if a week has passed since the last update check. It - utilizes the kmcom API to retrieve the current packages. The function then - performs an HTTP request to query the remote versions of these packages. - The resulting information is stored in the FParams variable. Additionally, - the function handles the main Keyman install package. - - @returns A TBackgroundUpdateResult indicating the result of the update - check. - } + (** + * Performs an online query of both the main keyman package and + * the keyboard packages. It utilizes the kmcom API to retrieve the current + * packages. The function then performs an HTTP request to query the remote + * versions of these packages. + * The resulting information is stored in the TUpdateCheckResponse + * variable and seralized to disk. + * + * @returns A TBackgroundUpdateResult indicating the result of the update + * check. + *) function DoRun: TRemoteUpdateCheckResult; public - constructor Create(AForce : Boolean); + constructor Create(AForce: Boolean); destructor Destroy; override; function Run: TRemoteUpdateCheckResult; property ShowErrors: Boolean read FShowErrors write FShowErrors; end; procedure LogMessage(LogMessage: string); + +(** + * This function checks if a week or CheckPeriod time has passed since the last + * update check. + * + * @returns True if it has been longer then CheckPeriod time since last update +*) function ConfigCheckContinue: Boolean; implementation uses System.WideStrUtils, + System.Win.Registry, Winapi.Windows, Winapi.WinINet, @@ -98,8 +105,9 @@ begin if (FErrorMessage <> '') and FShowErrors then LogMessage(FErrorMessage); - KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = '+FErrorMessage); - KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = '+IntToStr(Ord(FRemoteResult))); + KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = ' + FErrorMessage); + KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = ' + + IntToStr(Ord(FRemoteResult))); inherited Destroy; end; @@ -116,12 +124,12 @@ var i: Integer; ucr: TUpdateCheckResponse; pkg: IKeymanPackage; - registry: TRegistryErrorControlled; + Registry: TRegistryErrorControlled; http: THttpUploader; - proceed : boolean; + proceed: Boolean; begin - {FProxyHost := ''; - FProxyPort := 0;} + { FProxyHost := ''; + FProxyPort := 0; } { Check if user is currently online } if not InternetGetConnectedState(@flags, 0) then @@ -132,20 +140,20 @@ begin proceed := ConfigCheckContinue; if not proceed and not FForce then - begin - Result := wucNoUpdates; - Exit; - end; - + begin + Result := wucNoUpdates; + Exit; + end; try - http := THTTPUploader.Create(nil); + http := THttpUploader.Create(nil); try http.Fields.Add('version', ansistring(CKeymanVersionInfo.Version)); http.Fields.Add('tier', ansistring(CKeymanVersionInfo.Tier)); - if FForce - then http.Fields.Add('manual', '1') - else http.Fields.Add('manual', '0'); + if FForce then + http.Fields.Add('manual', '1') + else + http.Fields.Add('manual', '0'); for i := 0 to kmcom.Packages.Count - 1 do begin @@ -154,8 +162,10 @@ begin // 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. - http.Fields.Add(AnsiString('packageid_'+IntToStr(i)), AnsiString(pkg.ID)); - http.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; @@ -167,7 +177,7 @@ begin http.Request.HostName := API_Server; http.Request.Protocol := API_Protocol; http.Request.UrlPath := API_Path_UpdateCheck_Windows; - //OnStatus := + // OnStatus := http.Upload; if http.Response.StatusCode = 200 then begin @@ -188,62 +198,66 @@ begin http.Free; end; except - on E:EHTTPUploader do + 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]); + 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 + on E: Exception do begin FErrorMessage := E.Message; Result := wucFailure; end; end; - registry := TRegistryErrorControlled.Create; // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try - if registry.OpenKey(SRegKey_KeymanDesktop_CU, True) then - registry.WriteDateTime(SRegValue_LastUpdateCheckTime, Now); + if Registry.OpenKey(SRegKey_KeymanDesktop_CU, True) then + Registry.WriteDateTime(SRegValue_LastUpdateCheckTime, Now); finally - registry.Free; + Registry.Free; end; 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; +// temp wrapper for converting showmessage to logs don't know where +// if nt using klog +procedure LogMessage(LogMessage: string); +begin + KL.Log(LogMessage); +end; function ConfigCheckContinue: Boolean; var - registry: TRegistryErrorControlled; + Registry: TRegistryErrorControlled; begin -{ Verify that it has been at least CheckPeriod days since last update check } + { Verify that it has been at least CheckPeriod days since last update check } Result := False; try - registry := TRegistryErrorControlled.Create; // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try - if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + if Registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then begin - if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then + if Registry.ValueExists(SRegValue_CheckForUpdates) and + not Registry.ReadBool(SRegValue_CheckForUpdates) then begin Exit; end; - if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + if Registry.ValueExists(SRegValue_LastUpdateCheckTime) and + (Now - Registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > + CheckPeriod) then begin Result := True; end; end; finally - registry.Free; + Registry.Free; end; except - { we will not run the check if an error occurs reading the settings } - on E:Exception do + on E: ERegistryException do begin Result := False; LogMessage(E.Message); diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index eaf6b50bd8..a3c262d2bc 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -99,6 +99,7 @@ implementation uses + System.Win.Registry, Winapi.Windows, Winapi.WinINet, ErrorControlledRegistry, @@ -119,6 +120,7 @@ const constructor TState.Create(Context: TUpdateStateMachine); begin + inherited Create; bucStateContext := Context; end; @@ -256,7 +258,7 @@ begin for lpState := Low(TUpdateState) to High(TUpdateState) do begin - FStateInstance[lpState].Free; + FreeAndNil(FStateInstance[lpState]); end; // TODO: #10210 remove debugging comments @@ -289,7 +291,7 @@ begin Registry.WriteString(SRegValue_Update_State, UpdateStr); Result := True; except - on E: Exception do + on E: ERegistryException do begin // TODO: #10210 Log to Sentry KL.Log('Failed to write to registry: ' + E.Message); @@ -328,7 +330,7 @@ begin else UpdateState := usIdle; // Default if out of bounds except - on E: Exception do + on E: ERegistryException do begin // TODO: #10210 Log to Sentry KL.Log('Failed to write to registry: ' + E.Message); @@ -357,7 +359,7 @@ begin not Registry.ValueExists(SRegValue_AutomaticUpdates) or Registry.ReadBool(SRegValue_AutomaticUpdates); except - on E: Exception do + on E: ERegistryException do begin // TODO: #10210 Log to Sentry KL.Log('Failed to read registery: ' + E.Message); @@ -386,7 +388,7 @@ begin Registry.WriteBool(SRegValue_ApplyNow, Value); Result := True; except - on E: Exception do + on E: ERegistryException do begin // TODO: #10210 Log to Sentry 'Failed to write '+SRegValue_ApplyNow+' to registry: ' + E.Message KL.Log('Failed to write to registry: ' + E.Message); @@ -410,7 +412,7 @@ begin Registry.ValueExists(SRegValue_ApplyNow) and Registry.ReadBool(SRegValue_ApplyNow); except - on E: Exception do + on E: ERegistryException do begin KL.Log('Failed to read registry: ' + E.Message); Result := False; @@ -556,29 +558,28 @@ var begin { ##### For Testing only just advancing to downloading #### } - ChangeState(UpdateAvailableState); + //ChangeState(UpdateAvailableState); + // will keep here as there are more PR's #12621 { #### End of Testing ### }; - { Make a HTTP request out and see if updates are available for now do - this all in the Idle HandleCheck message. But could be broken into an - seperate state of WaitngCheck RESP } + { // // TODO-WINDOWS-UPDATES Check how long a check takes then determine + if it needs to be broken into a seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - // If handle check event force check - // CheckForUpdates := TRemoteUpdateCheck.Create(True); - // try - // Result:= CheckForUpdates.Run; - // finally - // CheckForUpdates.Free; - // end; + // Handle_check event force check + CheckForUpdates := TRemoteUpdateCheck.Create(True); + try + Result:= CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; { Response OK and Update is available } - // if Result = wucSuccess then - // begin - // ChangeState(UpdateAvailableState); - // end; - + if Result = wucSuccess then + begin + ChangeState(UpdateAvailableState); + end; // else staty in idle state end; @@ -786,13 +787,6 @@ begin try DownloadResult := DownloadUpdate.DownloadUpdates; Result := DownloadResult; - // #TODO: #10210 workout when we need to refresh kmcom keyboards - // if Result in [ wucSuccess] then - // begin - // kmcom.Keyboards.Refresh; - // kmcom.Keyboards.Apply; - // kmcom.Packages.Refresh; - // end; finally DownloadUpdate.Free; end; @@ -845,7 +839,6 @@ begin end else begin - // TODO Pop up toast here to ask user if we want to continue frmStartInstall := TfrmStartInstall.Create(nil); try if frmStartInstall.ShowModal = mrOk then diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index d8c19b6309..065d26802a 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -438,8 +438,7 @@ begin 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. + BUpdateSM := TUpdateStateMachine.Create(False); try if (FMode = fmBackgroundUpdateCheck) then From 883092e2178e4eccd084a973c98c07e50d2f8d55 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:53:45 +1000 Subject: [PATCH 070/124] feat(windows): fix installHelper --- .../insthelper/Keyman.System.Install.EnginePostInstall.pas | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 56d33c8339..6c756bbbac 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -29,15 +29,13 @@ end; function UpdateState: Boolean; var UpdateStr : UnicodeString; - UpdatePBytes : PByte; hk: Winapi.Windows.HKEY; - updateData: Cardinal; begin Result := False; UpdateStr := 'usPostInstall'; - if RegCreateKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_KeymanEngine_CU), 0, NULL, KEY_ALL_ACCESS, NULL, &hk, NULL) = ERROR_SUCCESS then + if RegCreateKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_KeymanEngine_CU), 0, nil, 0, KEY_ALL_ACCESS, nil, &hk, nil) = ERROR_SUCCESS then begin try if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PChar(UpdateStr), (Length(UpdateStr)+1) * SizeOf(Char)) = ERROR_SUCCESS then @@ -54,7 +52,7 @@ begin end else begin - // TODO-WINDOWS-UPDATES: error log creating key + //TODO-WINDOWS-UPDATES: error log creating key end; end; From ab3227fb00ff44bf317588623d3c930ae0c4bbce Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:29:14 +1000 Subject: [PATCH 071/124] feat(windows): review comments --- windows/src/desktop/kmshell/main/UfrmMain.pas | 4 ++-- windows/src/desktop/kmshell/main/initprog.pas | 2 +- windows/src/engine/keyman/main.pas | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index 0403df9275..cb206e0291 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -797,7 +797,7 @@ begin Free; end; end; -// TODO: #10210 Remove Update +// TODO-WINDOWS-UPDATES: #10210 Remove Update procedure TfrmMain.Support_UpdateCheck; begin with TOnlineUpdateCheck.Create(Self, True, False) do @@ -838,7 +838,7 @@ end; procedure TfrmMain.Update_ApplyNow; var - ShellPath, s: WideString; + ShellPath, s: string; FResult: Boolean; begin ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 065d26802a..b5a84792ce 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -253,7 +253,7 @@ begin else if s = '-?' then FMode := fmHelpKMShell else if s = '-h' then FMode := fmHelp else if s = '-t' then FMode := fmTextEditor - //TODO: will remove -ouc not used + //TODO-WINDOWS-UPDATES: will remove -ouc not used // -buc uses the Statemachine can be used for external scripts to force a check else if s = '-ouc' then FMode := fmOnlineUpdateCheck else if s = '-buc' then FMode := fmBackgroundUpdateCheck diff --git a/windows/src/engine/keyman/main.pas b/windows/src/engine/keyman/main.pas index 1b6ed25b30..79db9c8baa 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -40,14 +40,14 @@ uses System.Win.Registry, GetOsVersion, + Keyman.System.ExecutionHistory, Keyman.System.Security, Keyman.Winapi.VersionHelpers, KeymanVersion, + Klog, RegistryKeys, UfrmKeyman7Main, - UserMessages, - Klog, - Keyman.System.ExecutionHistory; + UserMessages; function ValidateParameters(var FCommand: Integer): Boolean; forward; function PassParametersToRunningInstance(FCommand: Integer): Boolean; forward; From ba2b3016bc30f4a6787968c6c73f1cc9b7f326d1 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:55:55 +1000 Subject: [PATCH 072/124] feat(windows): Wip dont want to lose --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 75024cfe33..8c029735be 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -733,7 +733,7 @@ begin if InstallNow = True then begin bucStateContext.SetApplyNow(True); - ChangeState(InstallingState) + ChangeState(InstallingState); // TODO: Aeroplane bug find this should start download first? "StartDownloadProcess;" end; end; From caef322cc4a89b9974582e3765208b73910a26e8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:25:42 +1000 Subject: [PATCH 073/124] feat(windows): remove retry state retry immediately Remove the retry state as the process will retry in the same process. Future we could consider adding a retry state that would retry on the next time kmshell is started for any reason. --- .../main/Keyman.System.UpdateStateMachine.pas | 151 ++++-------------- .../main/UImportOlderVersionSettings.pas | 1 - 2 files changed, 30 insertions(+), 122 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 8c029735be..b196f755ad 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -26,7 +26,7 @@ type EUpdateStateMachine = class(Exception); TUpdateState = (usIdle, usUpdateAvailable, usDownloading, usWaitingRestart, - usInstalling, usRetry, usPostInstall); + usInstalling); // Forward declaration TUpdateStateMachine = class; @@ -74,6 +74,8 @@ type procedure HandleMSIInstallComplete; function SetRegistryState(Update: TUpdateState): Boolean; + //function SetIncRegistryCount: Boolean; + //function ClearRegistryCount: Boolean; function GetAutomaticUpdates: Boolean; function SetApplyNow(Value: Boolean): Boolean; function GetApplyNow: Boolean; @@ -207,29 +209,6 @@ type procedure HandleFirstRun; override; end; - RetryState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - function HandleKmShell: Integer; override; - procedure HandleDownload; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - end; - - PostInstallState = class(TState) - public - procedure Enter; override; - procedure Exit; override; - procedure HandleCheck; override; - function HandleKmShell: Integer; override; - procedure HandleDownload; override; - procedure HandleAbort; override; - procedure HandleInstallNow; override; - procedure HandleFirstRun; override; - end; - { TUpdateStateMachine } constructor TUpdateStateMachine.Create(AForce: Boolean); @@ -245,8 +224,6 @@ begin FStateInstance[usDownloading] := DownloadingState.Create(Self); FStateInstance[usWaitingRestart] := WaitingRestartState.Create(Self); FStateInstance[usInstalling] := InstallingState.Create(Self); - FStateInstance[usRetry] := RetryState.Create(Self); - FStateInstance[usPostInstall] := PostInstallState.Create(Self); // Check the Registry setting. SetStateOnly(CheckRegistryState); @@ -475,10 +452,6 @@ begin Result := usWaitingRestart else if StateClass = InstallingState then Result := usInstalling - else if StateClass = RetryState then - Result := usRetry - else if StateClass = PostInstallState then - Result := usPostInstall else begin // TODO: #10210 Log to Sentry @@ -565,7 +538,9 @@ end; // base implmentation to be overiden procedure TState.HandleFirstRun; begin - + // If Handle First run hits base implementation + // something is wrong log sentry error + bucStateContext.HandleMSIInstallComplete; end; { IdleState } @@ -662,8 +637,11 @@ begin RootPath := ExtractFilePath(ParamStr(0)); FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); if not FResult then + begin // TODO: #10210 Log to Sentry KL.Log('TrmfMain: Executing KMshell for download updated Failed'); + ChangeState(IdleState); + end; end; procedure UpdateAvailableState.Enter; @@ -743,6 +721,7 @@ end; procedure DownloadingState.Enter; var DownloadResult: Boolean; + RetryCount: Integer; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); @@ -750,9 +729,26 @@ begin KL.Log('DownloadingState.HandleKmshell test code continue'); //DownloadResult := True; { End testing } - DownloadResult := DownloadUpdatesBackground; - // TODO check if keyman is running then send to Waiting Restart - if DownloadResult then + RetryCount := 0; + DownloadResult := False; + + while (not DownloadResult) and (RetryCount < 3) do + begin + DownloadResult := DownloadUpdatesBackground; + if not DownloadResult then + Inc(RetryCount); + end; + + if (not DownloadResult) then + begin + // Failed three times in this process return to the + // IdleState to wait 7 days before trying again + ChangeState(IdleState); + // TODO: Future could go to a RetryState which serialized the a retry count + // to disk. Then it could try launch the download again on the next + // kmshell start event. + end + else begin if HasKeymanRun then begin @@ -769,10 +765,6 @@ begin ChangeState(InstallingState); end; end - else - begin - ChangeState(RetryState); - end; end; @@ -1023,89 +1015,6 @@ begin //Result := kmShellContinue; end; -{ RetryState } - -procedure RetryState.Enter; -begin - bucStateContext.SetRegistryState(usRetry); -end; - -procedure RetryState.Exit; -begin - -end; - -procedure RetryState.HandleCheck; -begin - -end; - -function RetryState.HandleKmShell; -begin - // #TODO: #10210 Implement retry - Result := kmShellContinue -end; - -procedure RetryState.HandleDownload; -begin - -end; - -procedure RetryState.HandleAbort; -begin - -end; - -procedure RetryState.HandleInstallNow; -begin - // TODO: #10038 handle retry counts - ChangeState(InstallingState); -end; - -{ PostInstallState } - -procedure PostInstallState.Enter; -begin - // Enter downloading state - bucStateContext.SetRegistryState(usPostInstall); -end; - -procedure PostInstallState.Exit; -begin - -end; - -procedure PostInstallState.HandleCheck; -begin - // Handle Check -end; - -function PostInstallState.HandleKmShell; -begin - bucStateContext.HandleMSIInstallComplete; - Result := kmShellContinue; -end; - -procedure PostInstallState.HandleDownload; -begin - // Do Nothing -end; - -procedure PostInstallState.HandleAbort; -begin - // Handle Abort -end; - -procedure PostInstallState.HandleInstallNow; -begin - // Do nothing as files will be cleaned via HandleKmShell -end; - -procedure PostInstallState.HandleFirstRun; -begin - bucStateContext.HandleMSIInstallComplete; - //Result := kmShellContinue; -end; // Private Functions: function ConfigCheckContinue: Boolean; diff --git a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas index c2f95b38ef..48a8293f50 100644 --- a/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas +++ b/windows/src/desktop/kmshell/main/UImportOlderVersionSettings.pas @@ -55,7 +55,6 @@ begin UpdateSM := TUpdateStateMachine.Create(False); try UpdateSM.HandleFirstRun; - Exit; finally UpdateSM.Free; end; From 4023aca115db0387c8765fbfd822c46769d27172 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:55:45 +1000 Subject: [PATCH 074/124] feat(windows): convert the klog errors to sentry Update all the errors logged using KLog to sentry messages --- .../main/Keyman.System.UpdateStateMachine.pas | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index b196f755ad..83959f0377 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -13,6 +13,7 @@ uses System.IOUtils, System.Types, System.TypInfo, + Sentry.Client, httpuploader, KeymanPaths, @@ -110,6 +111,7 @@ uses ErrorControlledRegistry, GlobalProxySettings, + Keyman.System.KeymanSentryClient, Keyman.System.DownloadUpdate, Keyman.System.RemoteUpdateCheck, KLog, @@ -234,7 +236,7 @@ var lpState: TUpdateState; begin if (FErrorMessage <> '') and FShowErrors then - KL.Log(FErrorMessage); // TODO: #10210 Log to Sentry + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, '"+FErrorMessage+"'); for lpState := Low(TUpdateState) to High(TUpdateState) do begin @@ -261,8 +263,7 @@ begin if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then begin - // TODO: #10210 Log to Sentry - KL.Log('Failed to open registry key: ' + SRegKey_KeymanEngine_CU); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Failed to open registry key: "'+SRegKey_KeymanEngine_CU+'"'); Exit; end; @@ -273,8 +274,7 @@ begin except on E: ERegistryException do begin - // TODO: #10210 Log to Sentry - KL.Log('Failed to write to registry: ' + E.Message); + TKeymanSentryClient.ReportHandledException(E, 'Failed to write install state machine state'); end; end; @@ -312,8 +312,7 @@ begin except on E: ERegistryException do begin - // TODO: #10210 Log to Sentry - KL.Log('Failed to write to registry: ' + E.Message); + TKeymanSentryClient.ReportHandledException(E, 'Failed to read install state machine state'); UpdateState := usIdle; end; end; @@ -341,8 +340,7 @@ begin except on E: ERegistryException do begin - // TODO: #10210 Log to Sentry - KL.Log('Failed to read registery: ' + E.Message); + TKeymanSentryClient.ReportHandledException(E, 'Failed to read automatic updates'); Result := False; end; end; @@ -370,8 +368,7 @@ begin except on E: ERegistryException do begin - // TODO: #10210 Log to Sentry 'Failed to write '+SRegValue_ApplyNow+' to registry: ' + E.Message - KL.Log('Failed to write to registry: ' + E.Message); + TKeymanSentryClient.ReportHandledException(E, 'Failed to write apply now'); end; end; finally @@ -409,8 +406,7 @@ begin Result := TStateClass(CurrentState.ClassType) else begin - // TODO: #10210 Log to Sentry - KL.Log('Error CurrentState was uninitiallised: ' ); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); Result := nil; end; end; @@ -454,9 +450,8 @@ begin Result := usInstalling else begin - // TODO: #10210 Log to Sentry Result := usIdle; - KL.Log('Unknown StateClass'); // TODO-WINDOWS-UPDATES + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Unknown State Machine class'); end; end; @@ -466,8 +461,7 @@ begin Result := True else begin - // TODO: #10210 Log to Sentry - KL.Log('Unexpected Error: Current state is not assigned.'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); Result := False; end; end; @@ -539,7 +533,8 @@ end; procedure TState.HandleFirstRun; begin // If Handle First run hits base implementation - // something is wrong log sentry error + // something is wrong. + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Handle first run called in state:"'+Self.ClassName+'"'); bucStateContext.HandleMSIInstallComplete; end; @@ -638,8 +633,7 @@ begin FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); if not FResult then begin - // TODO: #10210 Log to Sentry - KL.Log('TrmfMain: Executing KMshell for download updated Failed'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to download updated Failed'); ChangeState(IdleState); end; end; @@ -933,10 +927,7 @@ begin if not FResult then begin - // TODO: #10210 Log to Sentry - KL.Log('TUpdateStateMachine.InstallingState.DoInstall: Result = ' + - IntToStr(Ord(FResult))); - // Log message ShowMessage(SysErrorMessage(GetLastError)); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"'+IntToStr(Ord(FResult))+'"'); end; Result := FResult; From 981e7d1090b6d391992ccbfdbad51802a3e7b333 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:07:57 +1000 Subject: [PATCH 075/124] feat(windows): remove sm state from insthelper Remove the update of state from the insthelper as configure first run will now set it back to idle. --- ...eyman.System.Install.EnginePostInstall.pas | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas index 6c756bbbac..bdf602ae05 100644 --- a/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas +++ b/windows/src/engine/insthelper/Keyman.System.Install.EnginePostInstall.pas @@ -25,38 +25,6 @@ begin Result := code; end; - -function UpdateState: Boolean; -var - UpdateStr : UnicodeString; - hk: Winapi.Windows.HKEY; -begin - - Result := False; - UpdateStr := 'usPostInstall'; - - if RegCreateKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_KeymanEngine_CU), 0, nil, 0, KEY_ALL_ACCESS, nil, &hk, nil) = ERROR_SUCCESS then - begin - try - if RegSetValueEx(hk, PChar(SRegValue_Update_State), 0, REG_SZ, PChar(UpdateStr), (Length(UpdateStr)+1) * SizeOf(Char)) = ERROR_SUCCESS then - begin - Result := True; - end - else - begin - // TODO-WINDOWS-UPDATES: error log - end; - finally - RegCloseKey(hk); - end; - end - else - begin - //TODO-WINDOWS-UPDATES: error log creating key - end; -end; - - { Add permission for ALL APPLICATION PACKAGES to read %ProgramData%\Keyman folder } @@ -93,8 +61,6 @@ begin end; Result := ERROR_SUCCESS; - // TODO-WINDOWS-UPDATES: better error checking on the registry key update - UpdateState; finally if not CloseHandle(hFile) then From c365f85e606e2ec2be6735190fddb734961dad2e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:18:16 +1000 Subject: [PATCH 076/124] feat(windows): initiall commit add install pkgs --- .../main/Keyman.System.UpdateCheckStorage.pas | 34 +++ .../main/Keyman.System.UpdateStateMachine.pas | 234 +++++++++++++++--- 2 files changed, 231 insertions(+), 37 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas index f1a156bfa0..e5e66f061f 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -13,6 +13,8 @@ type class function HasUpdates: Boolean; static; class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static; class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static; + class function HasKeyboardPackages(const data: TUpdateCheckResponse): Boolean; static; + class function HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean; static; end; implementation @@ -49,4 +51,36 @@ begin data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version); end; +class function TUpdateCheckStorage.HasKeyboardPackages(const data: TUpdateCheckResponse): Boolean; +var + i : Integer; + fileName : string; + f: TSearchRec; +begin + Result := False; + for i := 0 to High(data.Packages) do + begin + fileName := data.Packages[i].FileName; + if FindFirst(fileName + '*.k??', 0, f) = 0 then + Result := True; + System.SysUtils.FindClose(f); + end; +end; + +class function TUpdateCheckStorage.HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean; +var + i : Integer; + fileName : string; + f: TSearchRec; +begin + Result := False; + for i := 0 to High(data.Packages) do + begin + fileName := data.Packages[i].FileName; + if FindFirst(fileName + '*.exe', 0, f) = 0 then + Result := True; + System.SysUtils.FindClose(f); + end; +end; + end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 83959f0377..5f5abe50c4 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -50,6 +50,7 @@ type procedure HandleDownload; virtual; abstract; procedure HandleAbort; virtual; abstract; procedure HandleInstallNow; virtual; abstract; + procedure HandleInstallPackages; virtual; procedure HandleFirstRun; virtual; end; @@ -93,6 +94,7 @@ type procedure HandleDownload; procedure HandleAbort; procedure HandleInstallNow; + procedure HandleInstallPackages; procedure HandleFirstRun; function CurrentStateName: string; @@ -111,12 +113,16 @@ uses ErrorControlledRegistry, GlobalProxySettings, + kmint, + keymanapi_TLB, Keyman.System.KeymanSentryClient, Keyman.System.DownloadUpdate, Keyman.System.RemoteUpdateCheck, + Keyman.System.UpdateCheckStorage, KLog, RegistryKeys, - utilexecute; + utilexecute, + utiluac; const SPackageUpgradeFilename = 'upgrade_packages.inf'; @@ -165,7 +171,6 @@ type DownloadingState = class(TState) private - function DownloadUpdatesBackground: Boolean; procedure Enter; override; procedure Exit; override; @@ -198,7 +203,19 @@ type * @returns True if the installation is successful, False otherwise. *) - function DoInstallKeyman(SavePath: string): Boolean; overload; + function DoInstallKeyman: Boolean; overload; + + (** + * Installs the Keyman Keyboard files using separate shell. + * + * @params SavePath The path to the downloaded files. + * + * @returns True if the installation is successful, False otherwise. + *) + + function DoInstallPackages(Params: TUpdateCheckResponse): Boolean; + function DoInstallPackage(PackageFileName: String): Boolean; + procedure CheckInstallPackageElevation; public procedure Enter; override; @@ -208,6 +225,7 @@ type procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; + procedure HandleInstallPackages; override; procedure HandleFirstRun; override; end; @@ -517,11 +535,17 @@ begin CurrentState.HandleInstallNow; end; +procedure TUpdateStateMachine.HandleInstallPackages; +begin + CurrentState.HandleInstallPackages; +end; + procedure TUpdateStateMachine.HandleFirstRun; begin CurrentState.HandleFirstRun; end; + function TUpdateStateMachine.CurrentStateName: string; begin if not IsCurrentStateAssigned then @@ -530,6 +554,12 @@ begin end; // base implmentation to be overiden + +procedure TState.HandleInstallPackages; +begin + // Do Nothing +end; + procedure TState.HandleFirstRun; begin // If Handle First run hits base implementation @@ -720,11 +750,11 @@ begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); { ## for testing log that we would download } - KL.Log('DownloadingState.HandleKmshell test code continue'); - //DownloadResult := True; + KL.Log('DownloadingState.Enter test code continue'); + DownloadResult := True; { End testing } RetryCount := 0; - DownloadResult := False; + //DownloadResult := False; while (not DownloadResult) and (RetryCount < 3) do begin @@ -906,65 +936,163 @@ begin end; end; -function InstallingState.DoInstallKeyman(SavePath: string): Boolean; + + +// Installing packages needs to be elevated +procedure InstallingState.CheckInstallPackageElevation; var - s: string; - FResult: Boolean; + SavePath: String; + fileExt: String; + fileName: String; + Filenames: TStringDynArray; + executeResult: Boolean; + ucr: TUpdateCheckResponse; + hasPackages, hasKeymanInstall, requiresAdmin : Boolean; begin - s := LowerCase(ExtractFileExt(SavePath)); - if s = '.msi' then - FResult := TUtilExecute.Shell(0, 'msiexec.exe', '', '/qb /i "' + SavePath + - '" AUTOLAUNCHPRODUCT=1') // I3349 - else if s = '.exe' then + if not kmcom.SystemInfo.IsAdministrator then begin - // switch -au for auto update in silent mode. - // We will need to add the pop up that says install update now yes/no - // This will run the setup executable which will ask for elevated permissions - FResult := TUtilExecute.Shell(0, SavePath, '', '-au') // I3349 + if CanElevate then + begin + executeResult := WaitForElevatedConfiguration(0, '-ou') <> 0; + if executeResult then + begin + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install keyboard packages failed:"'+IntToStr(Ord(executeResult))+'"'); + // even though package install failed still install Keyman + DoInstallKeyman; + end; + end + else + begin + // TODO: How do we alert the user that package requires a user with admin rights + //ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); + end; end + else + begin + HandleInstallPackages; // we can install packages straight away + end; +end; + + + +/////////////////////////////////////////////////////////////////////////////// + +function InstallingState.DoInstallKeyman: Boolean; +var + FResult: Boolean; + SavePath: String; + fileExt: String; + fileName: String; + Filenames: TStringDynArray; + ucr: TUpdateCheckResponse; + found : Boolean; +begin + + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + GetFileNamesInDirectory(SavePath, Filenames); + found := False; + for fileName in Filenames do + begin + fileExt := LowerCase(ExtractFileExt(fileName)); + if fileExt = '.exe' then + begin + found := True; + break; + end; + end; + + // switch -au for auto update in silent mode. + // We will need to add the pop up that says install update now yes/no + // This will run the setup executable which will ask for elevated permissions + if found then + FResult := TUtilExecute.Shell(0, SavePath + ExtractFileName(fileName), '', '-au') else FResult := False; if not FResult then begin + bucStateContext.HandleMSIInstallComplete; + KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); + ChangeState(IdleState); TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"'+IntToStr(Ord(FResult))+'"'); end; Result := FResult; end; + +function InstallingState.DoInstallPackage(PackageFileName: String): Boolean; +var + FPackage: IKeymanPackageFile2; +begin + Result := True; + + FPackage := kmcom.Packages.GetPackageFromFile(PackageFileName) as IKeymanPackageFile2; + FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact + FPackage := nil; + + kmcom.Refresh; + kmcom.Apply; + System.SysUtils.DeleteFile(PackageFileName); + +end; + +function InstallingState.DoInstallPackages(Params: TUpdateCheckResponse): Boolean; +var + i : Integer; + SavePath: String; + PackageFullPath: String; +begin + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + for i := 0 to High(Params.Packages) do + begin + PackageFullPath := SavePath + Params.Packages[i].FileName; + if not DoInstallPackage(PackageFullPath) then // I2742 + begin + // Package did install log or error + KL.Log('Installing Package failed'+ PackageFullPath); + end; + end; + Result := True; +end; + + + + + procedure InstallingState.Enter; var SavePath: String; fileExt: String; fileName: String; Filenames: TStringDynArray; + ucr: TUpdateCheckResponse; + hasPackages, hasKeymanInstall : Boolean; begin bucStateContext.SetRegistryState(usInstalling); SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, Filenames); - // for now we only want the exe although excute install can - // handle msi - for fileName in Filenames do + // TODO: epic-update-windows + // Check if there are also packages to install if so + if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin - fileExt := LowerCase(ExtractFileExt(fileName)); - if fileExt = '.exe' then - break; + hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); + hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); end; - if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then + if hasPackages then begin - KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall OK'); - end - else + CheckInstallPackageElevation; + Exit; + end; + // only reach here if + if hasKeymanInstall then begin - // TODO: #10210 clean failed download - // TODO: #10210 Do we do a retry on install? probably not - KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); - ChangeState(IdleState); - end + DoInstallKeyman; + Exit; + end; + end; procedure InstallingState.Exit; @@ -1000,13 +1128,45 @@ begin // Do Nothing. Need the UI to let user know installation in progress OR end; -procedure InstallingState.HandleFirstRun; +procedure InstallingState.HandleInstallPackages; +var + SavePath: String; + fileExt: String; + fileName: String; + Filenames: TStringDynArray; + ucr: TUpdateCheckResponse; + hasPackages, hasKeymanInstall : Boolean; begin - bucStateContext.HandleMSIInstallComplete; - //Result := kmShellContinue; + // This event should only be reached in elevated process if not then + // move on to just installing Keyman packages. + if not kmcom.SystemInfo.IsAdministrator then + begin + DoInstallKeyman; + Exit; + end; + // TODO: epic-update-windows + // Check if there are also packages to install if so + if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then + DoInstallPackages(ucr); + + DoInstallKeyman; end; +procedure InstallingState.HandleFirstRun; +begin + + bucStateContext.HandleMSIInstallComplete; + // TODO: epic-windows-updates + // clean up MSI install files only, don't change state to idle + // if packages to install and keyman hasn't started + // goto packages ready to install + // then kmShellContinue + + + //Result := kmShellContinue; +end; + // Private Functions: function ConfigCheckContinue: Boolean; var From b9efa0d5aed2ee765721777be498723c00959aa8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:12:14 +1000 Subject: [PATCH 077/124] feat(windows): format file structure --- .../main/Keyman.System.UpdateStateMachine.pas | 210 +++++++++--------- windows/src/desktop/kmshell/main/initprog.pas | 7 + 2 files changed, 111 insertions(+), 106 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 5f5abe50c4..d07292491e 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1,8 +1,8 @@ (* - * Keyman is copyright (C) SIL Global. MIT License. - * - * Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md - *) + * Keyman is copyright (C) SIL Global. MIT License. + * + * Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md +*) unit Keyman.System.UpdateStateMachine; interface @@ -76,8 +76,6 @@ type procedure HandleMSIInstallComplete; function SetRegistryState(Update: TUpdateState): Boolean; - //function SetIncRegistryCount: Boolean; - //function ClearRegistryCount: Boolean; function GetAutomaticUpdates: Boolean; function SetApplyNow(Value: Boolean): Boolean; function GetApplyNow: Boolean; @@ -144,7 +142,7 @@ end; type -// Derived classes for each state + // Derived classes for each state IdleState = class(TState) public procedure Enter; override; @@ -254,7 +252,8 @@ var lpState: TUpdateState; begin if (FErrorMessage <> '') and FShowErrors then - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, '"+FErrorMessage+"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + '"+FErrorMessage+"'); for lpState := Low(TUpdateState) to High(TUpdateState) do begin @@ -281,7 +280,8 @@ begin if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Failed to open registry key: "'+SRegKey_KeymanEngine_CU+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Failed to open registry key: "' + SRegKey_KeymanEngine_CU + '"'); Exit; end; @@ -292,7 +292,8 @@ begin except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to write install state machine state'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to write install state machine state'); end; end; @@ -316,21 +317,23 @@ begin try Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and - Registry.ValueExists(SRegValue_Update_State) then + Registry.ValueExists(SRegValue_Update_State) then begin try StateValue := Registry.ReadString(SRegValue_Update_State); EnumValue := GetEnumValue(TypeInfo(TUpdateState), StateValue); // Bounds Check EnumValue against TUpdateState - if (EnumValue >= Ord(Low(TUpdateState))) and (EnumValue <= Ord(High(TUpdateState))) then + if (EnumValue >= Ord(Low(TUpdateState))) and + (EnumValue <= Ord(High(TUpdateState))) then UpdateState := TUpdateState(EnumValue) else UpdateState := usIdle; // Default if out of bounds except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to read install state machine state'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to read install state machine state'); UpdateState := usIdle; end; end; @@ -355,13 +358,14 @@ begin Result := not Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) or not Registry.ValueExists(SRegValue_AutomaticUpdates) or Registry.ReadBool(SRegValue_AutomaticUpdates); - except - on E: ERegistryException do - begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to read automatic updates'); - Result := False; - end; + except + on E: ERegistryException do + begin + TKeymanSentryClient.ReportHandledException(E, + 'Failed to read automatic updates'); + Result := False; end; + end; finally Registry.Free; end; @@ -386,7 +390,8 @@ begin except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to write apply now'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to write apply now'); end; end; finally @@ -407,7 +412,7 @@ begin Registry.ValueExists(SRegValue_ApplyNow) and Registry.ReadBool(SRegValue_ApplyNow); except - on E: ERegistryException do + on E: ERegistryException do begin KL.Log('Failed to read registry: ' + E.Message); Result := False; @@ -424,7 +429,8 @@ begin Result := TStateClass(CurrentState.ClassType) else begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Error CurrentState was uninitiallised'); Result := nil; end; end; @@ -454,7 +460,8 @@ begin CurrentState := FStateInstance[enumState]; end; -function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass) : TUpdateState; +function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass) + : TUpdateState; begin if StateClass = IdleState then Result := usIdle @@ -469,7 +476,8 @@ begin else begin Result := usIdle; - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Unknown State Machine class'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Unknown State Machine class'); end; end; @@ -479,25 +487,26 @@ begin Result := True else begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Error CurrentState was uninitiallised'); Result := False; end; end; - procedure TUpdateStateMachine.HandleMSIInstallComplete; -var SavePath: string; - FileName: String; - FileNames: TStringDynArray; +var + SavePath: string; + FileName: String; + FileNames: TStringDynArray; begin - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, FileNames); - for FileName in FileNames do - begin - System.SysUtils.DeleteFile(FileName); - end; - CurrentState.ChangeState(IdleState); + GetFileNamesInDirectory(SavePath, FileNames); + for FileName in FileNames do + begin + System.SysUtils.DeleteFile(FileName); + end; + CurrentState.ChangeState(IdleState); end; procedure TUpdateStateMachine.HandleCheck; @@ -545,7 +554,6 @@ begin CurrentState.HandleFirstRun; end; - function TUpdateStateMachine.CurrentStateName: string; begin if not IsCurrentStateAssigned then @@ -564,7 +572,8 @@ procedure TState.HandleFirstRun; begin // If Handle First run hits base implementation // something is wrong. - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Handle first run called in state:"'+Self.ClassName+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Handle first run called in state:"' + Self.ClassName + '"'); bucStateContext.HandleMSIInstallComplete; end; @@ -584,11 +593,11 @@ end; procedure IdleState.HandleCheck; var CheckForUpdates: TRemoteUpdateCheck; - Result : TRemoteUpdateCheckResult; + Result: TRemoteUpdateCheckResult; begin { ##### For Testing only just advancing to downloading #### } - //ChangeState(UpdateAvailableState); + // ChangeState(UpdateAvailableState); // will keep here as there are more PR's #12621 { #### End of Testing ### }; @@ -596,13 +605,12 @@ begin if it needs to be broken into a seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - // Handle_check event force check CheckForUpdates := TRemoteUpdateCheck.Create(True); try - Result:= CheckForUpdates.Run; + Result := CheckForUpdates.Run; finally - CheckForUpdates.Free; + CheckForUpdates.Free; end; { Response OK and Update is available } @@ -660,10 +668,12 @@ var begin // call seperate process RootPath := ExtractFilePath(ParamStr(0)); - FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); + FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), + IncludeTrailingPathDelimiter(RootPath), '-bd'); if not FResult then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to download updated Failed'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Executing kmshell process to download updated Failed'); ChangeState(IdleState); end; end; @@ -735,7 +745,8 @@ begin if InstallNow = True then begin bucStateContext.SetApplyNow(True); - ChangeState(InstallingState); // TODO: Aeroplane bug find this should start download first? "StartDownloadProcess;" + ChangeState(DownloadingState); + // TODO: Aeroplane bug find this should start download first? "StartDownloadProcess;" end; end; @@ -858,7 +869,7 @@ end; function WaitingRestartState.HandleKmShell; var SavedPath: String; - Filenames: TStringDynArray; + FileNames: TStringDynArray; frmStartInstall: TfrmStartInstall; begin // Still can't go if keyman has run @@ -873,8 +884,8 @@ begin // Check downloaded cache if available then SavedPath := IncludeTrailingPathDelimiter (TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, Filenames); - if Length(Filenames) = 0 then + GetFileNamesInDirectory(SavedPath, FileNames); + if Length(FileNames) = 0 then begin // Return to Idle state and check for Updates state ChangeState(IdleState); @@ -936,35 +947,29 @@ begin end; end; - - // Installing packages needs to be elevated procedure InstallingState.CheckInstallPackageElevation; var - SavePath: String; - fileExt: String; - fileName: String; - Filenames: TStringDynArray; - executeResult: Boolean; - ucr: TUpdateCheckResponse; - hasPackages, hasKeymanInstall, requiresAdmin : Boolean; + executeResult: Cardinal; begin if not kmcom.SystemInfo.IsAdministrator then begin if CanElevate then begin - executeResult := WaitForElevatedConfiguration(0, '-ou') <> 0; - if executeResult then + executeResult := WaitForElevatedConfiguration(0, '-ikp'); + if (executeResult <> 0) then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install keyboard packages failed:"'+IntToStr(Ord(executeResult))+'"'); - // even though package install failed still install Keyman + TKeymanSentryClient.Client.MessageEvent + (Sentry.Client.SENTRY_LEVEL_ERROR, + 'Executing kmshell process to install keyboard packages failed:"' + + IntToStr(Ord(executeResult)) + '"'); DoInstallKeyman; end; end else begin - // TODO: How do we alert the user that package requires a user with admin rights - //ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); + // TODO: How do we alert the user that package requires a user with admin rights + // ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); end; end else @@ -973,27 +978,22 @@ begin end; end; - - -/////////////////////////////////////////////////////////////////////////////// - function InstallingState.DoInstallKeyman: Boolean; var FResult: Boolean; SavePath: String; fileExt: String; - fileName: String; - Filenames: TStringDynArray; - ucr: TUpdateCheckResponse; - found : Boolean; + FileName: String; + FileNames: TStringDynArray; + found: Boolean; begin SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, Filenames); + GetFileNamesInDirectory(SavePath, FileNames); found := False; - for fileName in Filenames do + for FileName in FileNames do begin - fileExt := LowerCase(ExtractFileExt(fileName)); + fileExt := LowerCase(ExtractFileExt(FileName)); if fileExt = '.exe' then begin found := True; @@ -1005,7 +1005,8 @@ begin // We will need to add the pop up that says install update now yes/no // This will run the setup executable which will ask for elevated permissions if found then - FResult := TUtilExecute.Shell(0, SavePath + ExtractFileName(fileName), '', '-au') + FResult := TUtilExecute.Shell(0, SavePath + ExtractFileName(FileName), + '', '-au') else FResult := False; @@ -1014,21 +1015,24 @@ begin bucStateContext.HandleMSIInstallComplete; KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall fail'); ChangeState(IdleState); - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"'+IntToStr(Ord(FResult))+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Executing kmshell process to install failed:"' + + IntToStr(Ord(FResult)) + '"'); end; Result := FResult; end; - function InstallingState.DoInstallPackage(PackageFileName: String): Boolean; var FPackage: IKeymanPackageFile2; begin Result := True; - FPackage := kmcom.Packages.GetPackageFromFile(PackageFileName) as IKeymanPackageFile2; - FPackage.Install2(True); // Force overwrites existing package and leaves most settings for it intact + FPackage := kmcom.Packages.GetPackageFromFile(PackageFileName) + as IKeymanPackageFile2; + FPackage.Install2(True); + // Force overwrites existing package and leaves most settings for it intact FPackage := nil; kmcom.Refresh; @@ -1037,9 +1041,10 @@ begin end; -function InstallingState.DoInstallPackages(Params: TUpdateCheckResponse): Boolean; +function InstallingState.DoInstallPackages + (Params: TUpdateCheckResponse): Boolean; var - i : Integer; + i: Integer; SavePath: String; PackageFullPath: String; begin @@ -1050,29 +1055,25 @@ begin if not DoInstallPackage(PackageFullPath) then // I2742 begin // Package did install log or error - KL.Log('Installing Package failed'+ PackageFullPath); + KL.Log('Installing Package failed' + PackageFullPath); end; end; Result := True; end; - - - - procedure InstallingState.Enter; var SavePath: String; - fileExt: String; - fileName: String; - Filenames: TStringDynArray; + FileNames: TStringDynArray; ucr: TUpdateCheckResponse; - hasPackages, hasKeymanInstall : Boolean; + hasPackages, hasKeymanInstall: Boolean; begin + hasPackages := False; + hasKeymanInstall := False; bucStateContext.SetRegistryState(usInstalling); SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, Filenames); + GetFileNamesInDirectory(SavePath, FileNames); // TODO: epic-update-windows // Check if there are also packages to install if so if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then @@ -1130,12 +1131,7 @@ end; procedure InstallingState.HandleInstallPackages; var - SavePath: String; - fileExt: String; - fileName: String; - Filenames: TStringDynArray; ucr: TUpdateCheckResponse; - hasPackages, hasKeymanInstall : Boolean; begin // This event should only be reached in elevated process if not then // move on to just installing Keyman packages. @@ -1152,7 +1148,6 @@ begin DoInstallKeyman; end; - procedure InstallingState.HandleFirstRun; begin @@ -1170,21 +1165,24 @@ end; // Private Functions: function ConfigCheckContinue: Boolean; var - registry: TRegistryErrorControlled; + Registry: TRegistryErrorControlled; begin -{ Verify that it has been at least CheckPeriod days since last update check } + { Verify that it has been at least CheckPeriod days since last update check } Result := False; try - registry := TRegistryErrorControlled.Create; // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try - if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + if Registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then begin - if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then + if Registry.ValueExists(SRegValue_CheckForUpdates) and + not Registry.ReadBool(SRegValue_CheckForUpdates) then begin Result := False; Exit; end; - if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + if Registry.ValueExists(SRegValue_LastUpdateCheckTime) and + (Now - Registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > + CheckPeriod) then begin Result := True; end @@ -1195,11 +1193,11 @@ begin Exit; end; finally - registry.Free; + Registry.Free; end; except { we will not run the check if an error occurs reading the settings } - on E:Exception do + on E: Exception do begin Result := False; LogMessage(E.Message); diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index b5a84792ce..55f0f422f2 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -82,6 +82,7 @@ type fmMigrate, fmSplash, fmStart, fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, + fmInstallKeyboardPackageAdmin, fmBackgroundUpdateCheck, fmBackgroundDownload, fmApplyInstallNow, @@ -246,6 +247,7 @@ begin else if s = '-ukl' then FMode := fmUninstallKeyboardLanguage // I3624 else if s = '-up' then FMode := fmUninstallPackage { I1201 - Fix crash uninstalling admin-installed keyboards and packages } else if s = '-ou' then FMode := fmOnlineUpdateAdmin { I1730 - Check update of keyboards (admin elevation) } + else if s = '-ikp' then FMode := fmInstallKeyboardPackageAdmin else if s = '-a' then FMode := fmAbout else if s = '-ra' then FMode := fmRegistryAdd else if s = '-rr' then FMode := fmRegistryRemove @@ -456,6 +458,11 @@ begin BUpdateSM.HandleInstallNow; Exit; end + else if (FMode = fmInstallKeyboardPackageAdmin) then + begin + BUpdateSM.HandleInstallPackages; + Exit; + end else begin if BUpdateSM.HandleKmShell = 1 then From b79ac5899d3b6d3678512c9259dfb16e37b87b25 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:21:24 +1000 Subject: [PATCH 078/124] feat(windows): format file source --- .../main/Keyman.System.UpdateStateMachine.pas | 149 ++++++++++-------- 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 83959f0377..dd6fda4037 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1,8 +1,8 @@ (* - * Keyman is copyright (C) SIL Global. MIT License. - * - * Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md - *) + * Keyman is copyright (C) SIL Global. MIT License. + * + * Notes: For the state diagram in mermaid ../BackgroundUpdateStateDiagram.md +*) unit Keyman.System.UpdateStateMachine; interface @@ -138,13 +138,13 @@ end; type -// Derived classes for each state + // Derived classes for each state IdleState = class(TState) public procedure Enter; override; procedure Exit; override; procedure HandleCheck; override; - function HandleKmShell: Integer; override; + function HandleKmShell: Integer; override; procedure HandleDownload; override; procedure HandleAbort; override; procedure HandleInstallNow; override; @@ -198,7 +198,7 @@ type * @returns True if the installation is successful, False otherwise. *) - function DoInstallKeyman(SavePath: string): Boolean; overload; + function DoInstallKeyman(SavePath: string): Boolean; overload; public procedure Enter; override; @@ -236,7 +236,8 @@ var lpState: TUpdateState; begin if (FErrorMessage <> '') and FShowErrors then - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, '"+FErrorMessage+"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + '"+FErrorMessage+"'); for lpState := Low(TUpdateState) to High(TUpdateState) do begin @@ -263,7 +264,8 @@ begin if not Registry.OpenKey(SRegKey_KeymanEngine_CU, True) then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Failed to open registry key: "'+SRegKey_KeymanEngine_CU+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Failed to open registry key: "' + SRegKey_KeymanEngine_CU + '"'); Exit; end; @@ -274,7 +276,8 @@ begin except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to write install state machine state'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to write install state machine state'); end; end; @@ -298,21 +301,23 @@ begin try Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and - Registry.ValueExists(SRegValue_Update_State) then + Registry.ValueExists(SRegValue_Update_State) then begin try StateValue := Registry.ReadString(SRegValue_Update_State); EnumValue := GetEnumValue(TypeInfo(TUpdateState), StateValue); // Bounds Check EnumValue against TUpdateState - if (EnumValue >= Ord(Low(TUpdateState))) and (EnumValue <= Ord(High(TUpdateState))) then + if (EnumValue >= Ord(Low(TUpdateState))) and + (EnumValue <= Ord(High(TUpdateState))) then UpdateState := TUpdateState(EnumValue) else UpdateState := usIdle; // Default if out of bounds except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to read install state machine state'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to read install state machine state'); UpdateState := usIdle; end; end; @@ -337,13 +342,14 @@ begin Result := not Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) or not Registry.ValueExists(SRegValue_AutomaticUpdates) or Registry.ReadBool(SRegValue_AutomaticUpdates); - except - on E: ERegistryException do - begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to read automatic updates'); - Result := False; - end; + except + on E: ERegistryException do + begin + TKeymanSentryClient.ReportHandledException(E, + 'Failed to read automatic updates'); + Result := False; end; + end; finally Registry.Free; end; @@ -368,7 +374,8 @@ begin except on E: ERegistryException do begin - TKeymanSentryClient.ReportHandledException(E, 'Failed to write apply now'); + TKeymanSentryClient.ReportHandledException(E, + 'Failed to write apply now'); end; end; finally @@ -389,7 +396,7 @@ begin Registry.ValueExists(SRegValue_ApplyNow) and Registry.ReadBool(SRegValue_ApplyNow); except - on E: ERegistryException do + on E: ERegistryException do begin KL.Log('Failed to read registry: ' + E.Message); Result := False; @@ -406,7 +413,8 @@ begin Result := TStateClass(CurrentState.ClassType) else begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Error CurrentState was uninitiallised'); Result := nil; end; end; @@ -436,7 +444,8 @@ begin CurrentState := FStateInstance[enumState]; end; -function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass) : TUpdateState; +function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass) + : TUpdateState; begin if StateClass = IdleState then Result := usIdle @@ -451,7 +460,8 @@ begin else begin Result := usIdle; - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Unknown State Machine class'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Unknown State Machine class'); end; end; @@ -461,25 +471,26 @@ begin Result := True else begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Error CurrentState was uninitiallised'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Error CurrentState was uninitiallised'); Result := False; end; end; - procedure TUpdateStateMachine.HandleMSIInstallComplete; -var SavePath: string; - FileName: String; - FileNames: TStringDynArray; +var + SavePath: string; + FileName: String; + FileNames: TStringDynArray; begin - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); + SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, FileNames); - for FileName in FileNames do - begin - System.SysUtils.DeleteFile(FileName); - end; - CurrentState.ChangeState(IdleState); + GetFileNamesInDirectory(SavePath, FileNames); + for FileName in FileNames do + begin + System.SysUtils.DeleteFile(FileName); + end; + CurrentState.ChangeState(IdleState); end; procedure TUpdateStateMachine.HandleCheck; @@ -534,7 +545,8 @@ procedure TState.HandleFirstRun; begin // If Handle First run hits base implementation // something is wrong. - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Handle first run called in state:"'+Self.ClassName+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Handle first run called in state:"' + Self.ClassName + '"'); bucStateContext.HandleMSIInstallComplete; end; @@ -554,11 +566,11 @@ end; procedure IdleState.HandleCheck; var CheckForUpdates: TRemoteUpdateCheck; - Result : TRemoteUpdateCheckResult; + Result: TRemoteUpdateCheckResult; begin { ##### For Testing only just advancing to downloading #### } - //ChangeState(UpdateAvailableState); + // ChangeState(UpdateAvailableState); // will keep here as there are more PR's #12621 { #### End of Testing ### }; @@ -566,13 +578,12 @@ begin if it needs to be broken into a seperate state of WaitngCheck RESP } { if Response not OK stay in the idle state and return } - // Handle_check event force check CheckForUpdates := TRemoteUpdateCheck.Create(True); try - Result:= CheckForUpdates.Run; + Result := CheckForUpdates.Run; finally - CheckForUpdates.Free; + CheckForUpdates.Free; end; { Response OK and Update is available } @@ -630,10 +641,12 @@ var begin // call seperate process RootPath := ExtractFilePath(ParamStr(0)); - FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); + FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), + IncludeTrailingPathDelimiter(RootPath), '-bd'); if not FResult then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to download updated Failed'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Executing kmshell process to download updated Failed'); ChangeState(IdleState); end; end; @@ -705,7 +718,7 @@ begin if InstallNow = True then begin bucStateContext.SetApplyNow(True); - ChangeState(InstallingState); // TODO: Aeroplane bug find this should start download first? "StartDownloadProcess;" + ChangeState(DownloadingState); end; end; @@ -721,7 +734,7 @@ begin bucStateContext.SetRegistryState(usDownloading); { ## for testing log that we would download } KL.Log('DownloadingState.HandleKmshell test code continue'); - //DownloadResult := True; + // DownloadResult := True; { End testing } RetryCount := 0; DownloadResult := False; @@ -828,7 +841,7 @@ end; function WaitingRestartState.HandleKmShell; var SavedPath: String; - Filenames: TStringDynArray; + FileNames: TStringDynArray; frmStartInstall: TfrmStartInstall; begin // Still can't go if keyman has run @@ -843,8 +856,8 @@ begin // Check downloaded cache if available then SavedPath := IncludeTrailingPathDelimiter (TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, Filenames); - if Length(Filenames) = 0 then + GetFileNamesInDirectory(SavedPath, FileNames); + if Length(FileNames) = 0 then begin // Return to Idle state and check for Updates state ChangeState(IdleState); @@ -927,7 +940,9 @@ begin if not FResult then begin - TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"'+IntToStr(Ord(FResult))+'"'); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Executing kmshell process to install failed:"' + + IntToStr(Ord(FResult)) + '"'); end; Result := FResult; @@ -937,24 +952,24 @@ procedure InstallingState.Enter; var SavePath: String; fileExt: String; - fileName: String; - Filenames: TStringDynArray; + FileName: String; + FileNames: TStringDynArray; begin bucStateContext.SetRegistryState(usInstalling); SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, Filenames); + GetFileNamesInDirectory(SavePath, FileNames); // for now we only want the exe although excute install can // handle msi - for fileName in Filenames do + for FileName in FileNames do begin - fileExt := LowerCase(ExtractFileExt(fileName)); + fileExt := LowerCase(ExtractFileExt(FileName)); if fileExt = '.exe' then break; end; - if DoInstallKeyman(SavePath + ExtractFileName(fileName)) then + if DoInstallKeyman(SavePath + ExtractFileName(FileName)) then begin KL.Log('TUpdateStateMachine.InstallingState.Enter: DoInstall OK'); end @@ -1003,28 +1018,30 @@ end; procedure InstallingState.HandleFirstRun; begin bucStateContext.HandleMSIInstallComplete; - //Result := kmShellContinue; + // Result := kmShellContinue; end; - // Private Functions: function ConfigCheckContinue: Boolean; var - registry: TRegistryErrorControlled; + Registry: TRegistryErrorControlled; begin -{ Verify that it has been at least CheckPeriod days since last update check } + { Verify that it has been at least CheckPeriod days since last update check } Result := False; try - registry := TRegistryErrorControlled.Create; // I2890 + Registry := TRegistryErrorControlled.Create; // I2890 try - if registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then + if Registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then begin - if registry.ValueExists(SRegValue_CheckForUpdates) and not registry.ReadBool(SRegValue_CheckForUpdates) then + if Registry.ValueExists(SRegValue_CheckForUpdates) and + not Registry.ReadBool(SRegValue_CheckForUpdates) then begin Result := False; Exit; end; - if registry.ValueExists(SRegValue_LastUpdateCheckTime) and (Now - registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > CheckPeriod) then + if Registry.ValueExists(SRegValue_LastUpdateCheckTime) and + (Now - Registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > + CheckPeriod) then begin Result := True; end @@ -1035,11 +1052,11 @@ begin Exit; end; finally - registry.Free; + Registry.Free; end; except { we will not run the check if an error occurs reading the settings } - on E:Exception do + on E: Exception do begin Result := False; LogMessage(E.Message); From c449a8e662a07cfd7d18eb6205c63cb5a981b458 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:14:04 +1000 Subject: [PATCH 079/124] feat(windows): fix keyman updates xsl --- windows/src/desktop/kmshell/kmshell.res | Bin 7036 -> 0 bytes .../main/Keyman.System.UpdateCheckStorage.pas | 7 ++--- .../main/Keyman.System.UpdateStateMachine.pas | 24 +++++++++++++----- .../src/desktop/kmshell/xml/keyman_update.xsl | 8 +----- 4 files changed, 23 insertions(+), 16 deletions(-) delete mode 100644 windows/src/desktop/kmshell/kmshell.res diff --git a/windows/src/desktop/kmshell/kmshell.res b/windows/src/desktop/kmshell/kmshell.res deleted file mode 100644 index 80494425e9cab438c239cd54b9fc76a6f09be330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7036 zcmeI1O=uj+6~~`s1$k^NbP8*qI-STYftKc5qh&^3VeiJH_(Np%aST1vJ<_0=?#}c` zvr-7?duj_CG}I-$&KHnP18_(G8&z@qlu4zXR1QI{GkPl`2#uixx3jkxFD@ zWQz)9g9!^JOmRdtfh&#}IHE9Jgm1vl!>2+nylr@LiO?vR;3D`iKLg4pgH|YyS-ZWu zeeTdFO`k=0>QqK#x5W5)j8n$=Chj>X6^y83WRAl||1$brT7q4|izw9*?=AGZ@Zjfm z5J@ZUZa?{)opIk2k(eTmZl0s-!;^7DL3Uc%B>I(jCuX7Q$drrN^p5#M1OFWO6ggKg zj^|xyu8HB{)|&Vf+AuC=`owrdhiC_dwN}|6!av&BkQMG|(&6DDY8H55a^=Yicez%% zQ+A~Ns zfZqYX4SpSb4*WFuD0mON2i^nkf%m|B;Q4ie*E75x;pO5rBlrgRGw^lrN8k^@^XNOE zw?VIi&Vil=9mV+P;E%!Yg5Lt~f?ohX2R;iv13m(N1pHO-7-UIJ}_rN2d69n`OZ`UKdT`u0PnHi!6_%rZz@JHYe!0&+Xv3%$p{4w}l z@LS+r@C)F3EFX+pr+x67;FrPQ20sG6SAy!^IrwAnyWqFLBh%gj`1dEM?lr)lfv;iyLa#C_3PL4>eVZH`SK;bc=3XsK7C3%J3I8` z$rIY%-lhi+9?<&wI$4%Qxm=Fs=jUm9dYUFDCn*|@()H`t>DskxG&D4X713EnWg+^x zDCC=shU}0wHBdx{u!Si**p?>lAYccoaKY}yz9^!`+@g)jZ#?IkEm3dMbM6o!qQaVf zE;`tWO<@ZcX-Zp;0oB*xLJYU;=f1fKAxQRz)&Ig*uOdd_{pEoBL??aKmy4ViaNr^YeBhV0vjVJ{@+mO5hC3$b^g*JuwLFl_9@ z@J9oCudw033T(MS6ByWZ+K(0rdSN?5r~L|V*o?!bH>dron1Dxt%(lWah(g$}`&9w3 z4O!R$09n{Rz_PR@z_PUE6fFBT148y~0Kz-a5rdQiT_xcS-G7^ZGSy#k#8428Jl73~ zxO#sM~c|#sh0*HqQBzd0c z(E0Sy33T`ehm*Du+I1;+j?ktDatF}TTXH+l)Z67YAkaJI7U1jMaublcl>IXnpR}5J z5W4Rz^ta*;T_%e4VDK(o0MG+jp5a>lVg!Ce#NmwxY`hhgoS%DHZOW{3V4{~*Ul#y# z>?d^8zKru6&U#GB7rhm2sq+~Sx&Y)%Vg}~%53%t1P#iGPFAB*Dcb-a|A6dr;O~G&Y zvh;rs&^&(%|Nj93Iq>fSnsQ5TkD(>E=`Em5Zr1~#UGC5&&>?r~40Os}I)LDy&B;R% zdH*TjCJ|g1m*)-9U7Et4d=J-6@vCGIcew3*FHiNu!^7fq&%bW<;r!3{i^RXt^pm1U zjsK@`0hZMGyu`Mw-6yb-ARhniaQbTNyPn8deq4%G*ZKTFj_( zvtMc{RdK7jW#}c_GBfI$>8dv-uUty&u4|Tar8NZuSZ+pLsaT)6`6aWgyIQ%Juh_0# zsA_q;{F&~SM^_`NQr4|v!E~$h9kU5{W>#b0J4j5$%vUZcO4=|T(=rh0+9b@jj_Q;j zm+XfU1eyh2EmqeA&py>P2M<&$TCu3w?6mYUI`0M#FuG8*47=uz-n7eQ+tO~@R<&Z6 z+?GQ#cgfK(zgmj3k}DPSzctrp?PU|mO(rH1xnw*NkLUA+h*4;fCEZ$F(HAlCuUnl? z#m<|qYgeW!`K4mj%vV<`f*A83c`Qgm$1|s!-N=o|RkgC>REZ?7l4xAV6 zq;lm_D6KmV-c5{STcRVB{)MK^E}2U9mXb3|c1^)bVZd}1u!_6lICiC~6zq!PFCt~W zh>wz@X^1KmO8bOq-2Y0+8X5JEW;7g$#1rFMBoR+)@mM~g>4}1=g~KVsG|iY1D}?`~ zj(za-k4AdW9VsNollk$amI|8@Egp?cX!wZc zv{cLp7m`NU(8qtw+>vnasZ(aSFs?_UTCR|bA>n39L;4e1JXJ6fVLg)2Cq%+Sp%AaW zv99Hp9_v`9yv)afFFF|57l_j0d;b!Cx4na%W)^=_xsShr-9>8(^>6Uks9F34OP2OM Ifp>}j0>X0-NdN!< diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas index e5e66f061f..376721c6bb 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -21,6 +21,7 @@ implementation uses System.SysUtils, + System.RegularExpressions, KeymanPaths, KeymanVersion; @@ -55,15 +56,15 @@ class function TUpdateCheckStorage.HasKeyboardPackages(const data: TUpdateCheckR var i : Integer; fileName : string; - f: TSearchRec; + KeyboardRegex: TRegEx; begin Result := False; + KeyboardRegex := TRegEx.Create('\.k..$'); for i := 0 to High(data.Packages) do begin fileName := data.Packages[i].FileName; - if FindFirst(fileName + '*.k??', 0, f) = 0 then + if KeyboardRegex.IsMatch(fileName) then Result := True; - System.SysUtils.FindClose(f); end; end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index d07292491e..b9d12c16bc 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -500,7 +500,7 @@ var FileNames: TStringDynArray; begin SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - + KL.Log('ITUpdateStateMachine.HandleMSIInstallComplete'); GetFileNamesInDirectory(SavePath, FileNames); for FileName in FileNames do begin @@ -954,8 +954,10 @@ var begin if not kmcom.SystemInfo.IsAdministrator then begin + KL.Log('InstallingState.CheckInstallPackageElevation not IsAdmin'); if CanElevate then begin + KL.Log('InstallingState.CheckInstallPackageElevation CanElevate'); executeResult := WaitForElevatedConfiguration(0, '-ikp'); if (executeResult <> 0) then begin @@ -963,17 +965,20 @@ begin (Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install keyboard packages failed:"' + IntToStr(Ord(executeResult)) + '"'); - DoInstallKeyman; + KL.Log('InstallingState.CheckInstallPackageElevation Error elevating'); + ChangeState(IdleState); end; end else begin + KL.Log('InstallingState.CheckInstallPackageElevation require user with admin'); // TODO: How do we alert the user that package requires a user with admin rights // ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); end; end else begin + KL.Log('InstallingState.CheckInstallPackageElevation HandlePackages straight away'); HandleInstallPackages; // we can install packages straight away end; end; @@ -1028,7 +1033,7 @@ var FPackage: IKeymanPackageFile2; begin Result := True; - + KL.Log('InstallingState.DoInstallPackage Entry' + PackageFileName); FPackage := kmcom.Packages.GetPackageFromFile(PackageFileName) as IKeymanPackageFile2; FPackage.Install2(True); @@ -1037,6 +1042,7 @@ begin kmcom.Refresh; kmcom.Apply; + KL.Log('InstallingState.DoInstallPackage about to delete'); System.SysUtils.DeleteFile(PackageFileName); end; @@ -1081,13 +1087,14 @@ begin hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); end; - + KL.Log('InstallingState.Enter before hasPackages'); if hasPackages then begin + KL.Log('InstallingState.Enter hasPackages'); CheckInstallPackageElevation; Exit; end; - // only reach here if + // only reach here if no has packages otherwise it will if hasKeymanInstall then begin DoInstallKeyman; @@ -1133,17 +1140,22 @@ procedure InstallingState.HandleInstallPackages; var ucr: TUpdateCheckResponse; begin + KL.Log('InstallingState.HandleInstallPackages'); // This event should only be reached in elevated process if not then - // move on to just installing Keyman packages. + // move on to just installing Keyman packages if not kmcom.SystemInfo.IsAdministrator then begin + KL.Log('InstallingState.HandleInstallPackages Not Admin'); DoInstallKeyman; Exit; end; // TODO: epic-update-windows // Check if there are also packages to install if so if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then + begin + KL.Log('InstallingState.HandleInstallPackages about to call do install packages'); DoInstallPackages(ucr); + end; DoInstallKeyman; end; diff --git a/windows/src/desktop/kmshell/xml/keyman_update.xsl b/windows/src/desktop/kmshell/xml/keyman_update.xsl index 9acfd28d3b..074e78d4ed 100644 --- a/windows/src/desktop/kmshell/xml/keyman_update.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_update.xsl @@ -3,12 +3,6 @@ - -
    @@ -122,4 +116,4 @@ - \ No newline at end of file + From c720fce0f63aed93ac64103bd1d80caaeb3f1a4b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:27:04 +1000 Subject: [PATCH 080/124] feat(windows): remove more dead comments --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 5 ----- 1 file changed, 5 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index dd6fda4037..71fbdfb822 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -75,8 +75,6 @@ type procedure HandleMSIInstallComplete; function SetRegistryState(Update: TUpdateState): Boolean; - //function SetIncRegistryCount: Boolean; - //function ClearRegistryCount: Boolean; function GetAutomaticUpdates: Boolean; function SetApplyNow(Value: Boolean): Boolean; function GetApplyNow: Boolean; @@ -751,9 +749,6 @@ begin // Failed three times in this process return to the // IdleState to wait 7 days before trying again ChangeState(IdleState); - // TODO: Future could go to a RetryState which serialized the a retry count - // to disk. Then it could try launch the download again on the next - // kmshell start event. end else begin From 12d912cbd45c916f263e95a878b6be32a7af4736 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:11:14 +1000 Subject: [PATCH 081/124] feat(windows): Improve install dialog forms --- ...yman.Configuration.UI.UfrmStartInstall.dfm | 23 ++++++----- ...n.Configuration.UI.UfrmStartInstallNow.dfm | 40 +++++++------------ ...n.Configuration.UI.UfrmStartInstallNow.pas | 1 - 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index 9084afe1b6..b7766d09bc 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -1,9 +1,11 @@ object frmStartInstall: TfrmStartInstall Left = 0 Top = 0 + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog Caption = 'Keyman Update' - ClientHeight = 225 - ClientWidth = 425 + ClientHeight = 142 + ClientWidth = 322 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -11,14 +13,15 @@ object frmStartInstall: TfrmStartInstall Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False + Position = poScreenCenter PixelsPerInch = 96 TextHeight = 13 object lblInstallUpdate: TLabel - Left = 128 - Top = 96 - Width = 175 + Left = 72 + Top = 48 + Width = 180 Height = 19 - Caption = 'Keyman update available' + Caption = 'Keyman update available.' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -16 @@ -27,8 +30,8 @@ object frmStartInstall: TfrmStartInstall ParentFont = False end object cmdInstall: TButton - Left = 228 - Top = 184 + Left = 140 + Top = 104 Width = 75 Height = 25 Caption = 'Install' @@ -36,8 +39,8 @@ object frmStartInstall: TfrmStartInstall TabOrder = 0 end object cmdLater: TButton - Left = 336 - Top = 184 + Left = 234 + Top = 104 Width = 75 Height = 25 Caption = 'Close' diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm index eae5493040..8c0e467033 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.dfm @@ -1,9 +1,11 @@ object frmStartInstallNow: TfrmStartInstallNow Left = 0 Top = 0 + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog Caption = 'Keyman Update' - ClientHeight = 225 - ClientWidth = 425 + ClientHeight = 164 + ClientWidth = 354 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -11,14 +13,15 @@ object frmStartInstallNow: TfrmStartInstallNow Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False + Position = poScreenCenter PixelsPerInch = 96 TextHeight = 13 object lblUpdateMessage: TLabel - Left = 56 - Top = 88 - Width = 274 - Height = 19 - Caption = 'Keyman and Windows will be restarted' + Left = 32 + Top = 48 + Width = 290 + Height = 41 + Caption = 'Your computer will be restarted if you update now.' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -16 @@ -27,31 +30,18 @@ object frmStartInstallNow: TfrmStartInstallNow ParentFont = False WordWrap = True end - object lblUpdateNow: TLabel - Left = 56 - Top = 40 - Width = 115 - Height = 25 - Caption = 'Update Now' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -21 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - end object cmdInstall: TButton - Left = 228 - Top = 184 + Left = 147 + Top = 120 Width = 75 Height = 25 - Caption = 'Update' + Caption = 'Update now' ModalResult = 1 TabOrder = 0 end object cmdLater: TButton - Left = 336 - Top = 184 + Left = 247 + Top = 120 Width = 75 Height = 25 Caption = 'Close' diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas index 5f9c9caefc..778974e363 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas @@ -26,7 +26,6 @@ type cmdInstall: TButton; cmdLater: TButton; lblUpdateMessage: TLabel; - lblUpdateNow: TLabel; private public end; From 0f95fed5889a2b957fe5f4d1fa35622ab7150952 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:55:43 +1000 Subject: [PATCH 082/124] feat(windows): rename checkinstallpackageelevation --- .../main/Keyman.System.UpdateStateMachine.pas | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index b9d12c16bc..e754e55471 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -213,7 +213,7 @@ type function DoInstallPackages(Params: TUpdateCheckResponse): Boolean; function DoInstallPackage(PackageFileName: String): Boolean; - procedure CheckInstallPackageElevation; + procedure LaunchInstallPackageProcess; public procedure Enter; override; @@ -728,7 +728,7 @@ begin InstallNow := True; if HasKeymanRun then begin - // TODO: UI and non-UI units should be split + // TODO: epic-update-windows UI and non-UI units should be split // if the unit launches UI then it should be a .UI. unit // https://github.com/keymanapp/keyman/pull/12375/files#r1751041747 frmStartInstallNow := TfrmStartInstallNow.Create(nil); @@ -746,7 +746,6 @@ begin begin bucStateContext.SetApplyNow(True); ChangeState(DownloadingState); - // TODO: Aeroplane bug find this should start download first? "StartDownloadProcess;" end; end; @@ -948,16 +947,16 @@ begin end; // Installing packages needs to be elevated -procedure InstallingState.CheckInstallPackageElevation; +procedure InstallingState.LaunchInstallPackageProcess; var executeResult: Cardinal; begin if not kmcom.SystemInfo.IsAdministrator then begin - KL.Log('InstallingState.CheckInstallPackageElevation not IsAdmin'); + KL.Log('InstallingState.LaunchInstallPackageProcess not IsAdmin'); if CanElevate then begin - KL.Log('InstallingState.CheckInstallPackageElevation CanElevate'); + KL.Log('InstallingState.LaunchInstallPackageProcess CanElevate'); executeResult := WaitForElevatedConfiguration(0, '-ikp'); if (executeResult <> 0) then begin @@ -965,20 +964,20 @@ begin (Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install keyboard packages failed:"' + IntToStr(Ord(executeResult)) + '"'); - KL.Log('InstallingState.CheckInstallPackageElevation Error elevating'); + KL.Log('InstallingState.LaunchInstallPackageProcess Error elevating'); ChangeState(IdleState); end; end else begin - KL.Log('InstallingState.CheckInstallPackageElevation require user with admin'); - // TODO: How do we alert the user that package requires a user with admin rights + KL.Log('InstallingState.LaunchInstallPackageProcess require user with admin'); + // TODO: epic-windows-updates How do we alert the user that package requires a user with admin rights // ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); end; end else begin - KL.Log('InstallingState.CheckInstallPackageElevation HandlePackages straight away'); + KL.Log('InstallingState.LaunchInstallPackageProcess HandlePackages straight away'); HandleInstallPackages; // we can install packages straight away end; end; @@ -1091,7 +1090,7 @@ begin if hasPackages then begin KL.Log('InstallingState.Enter hasPackages'); - CheckInstallPackageElevation; + LaunchInstallPackageProcess; Exit; end; // only reach here if no has packages otherwise it will @@ -1149,8 +1148,6 @@ begin DoInstallKeyman; Exit; end; - // TODO: epic-update-windows - // Check if there are also packages to install if so if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin KL.Log('InstallingState.HandleInstallPackages about to call do install packages'); @@ -1162,16 +1159,7 @@ end; procedure InstallingState.HandleFirstRun; begin - bucStateContext.HandleMSIInstallComplete; - // TODO: epic-windows-updates - // clean up MSI install files only, don't change state to idle - // if packages to install and keyman hasn't started - // goto packages ready to install - // then kmShellContinue - - - //Result := kmShellContinue; end; // Private Functions: From d9d160a1e760beb99d2b2761760c6a37a926448b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 17 Dec 2024 08:02:40 +1000 Subject: [PATCH 083/124] feat(windows): todo add not to remove logging comments --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index e754e55471..07bda0aa7f 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -260,7 +260,7 @@ begin FreeAndNil(FStateInstance[lpState]); end; - // TODO: #10210 remove debugging comments + // TODO: #10210 TODO: epic-windows-update remove debugging comments throughout this Unit. // KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); // KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); From c1e2ca84a46586af73b82ed1248afe9eb0b35a5e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:12:49 +1000 Subject: [PATCH 084/124] feat(windows): Fix update table with css grid --- windows/src/desktop/kmshell/xml/config.css | 41 ++++++++++ .../src/desktop/kmshell/xml/keyman_update.xsl | 77 ++++++++---------- .../desktop/kmshell/xml/menuframe_update.png | Bin 0 -> 1573 bytes 3 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 windows/src/desktop/kmshell/xml/menuframe_update.png diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index 4a433082a1..c07087dbb1 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -499,6 +499,14 @@ table tr padding: 1px 0 1px 10px; } +.grid_container_update { + display: grid; + position: relative; + grid-template-columns: 50px 1fr 1fr 1fr; + padding: 10px 50px 10px 64px; + border: grey 1px; +} + .grid_container.grid_disabled { opacity: 0.5; @@ -1093,9 +1101,42 @@ th height: 16px; } +.update_title +{ + background: url('keyman-title.png') 96px 13px no-repeat; + height: 60px; +} + +.update_title img +{ + margin: 13px 0 0 10px; +} + +.update_edition +{ + float: left; + font-size: 16px; + margin-left: 64px; +} + +#update_status +{ + font-size: 13px; + margin-left: 64px; + margin-top: 30px; +} + #update_content { height: 100%; overflow: hidden; + display: flex; + flex-direction: column; + padding: 5px 10px 5px 10px; +} + +.update_controls +{ + margin-left: 64px; } /* QRCodes */ diff --git a/windows/src/desktop/kmshell/xml/keyman_update.xsl b/windows/src/desktop/kmshell/xml/keyman_update.xsl index 074e78d4ed..ad84e8e6ad 100644 --- a/windows/src/desktop/kmshell/xml/keyman_update.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_update.xsl @@ -16,26 +16,22 @@
    -  
    +  
    -
    Updates are available which will be applied when Windows is next restarted:
    +
    Updates are available which will be applied when Windows is next restarted:
    -
    +
    +
    Select
    +
    +
    +
    - - - - - - - -
    -
    +
    keyman:update_applynow @@ -56,49 +52,49 @@ - - - - javascript:updateTick(""); - Update_ - checked - Update_ - - - Update__RequiresAdmin - - + +
    + + javascript:updateTick(""); + Update_ + checked + Update_ + + + Update__RequiresAdmin + +
    - +
    - - +
    +
    - - +
    +
    - +
    - +
    - - +
    +
    - - +
    +
    - +
    - +
    @@ -107,13 +103,12 @@
    - - +
    +
    - +
    - diff --git a/windows/src/desktop/kmshell/xml/menuframe_update.png b/windows/src/desktop/kmshell/xml/menuframe_update.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7bc9d47bcca69d98ce3c2c0c4dd87179db3490 GIT binary patch literal 1573 zcmV+=2HN?FP)EX>4Tx04R~2kiAO8Koo_)tl}Rlh>C?+q_Ex6tt|Y*V6m`fBN(l+NfsBA7?LcD zZ{VBg!)zf4K7+M_wP&J*g=rkPoR7=B=gbVW>?BOf`2$F^s&L#x|I)v*7vE^J4y>^p zgk|0voO(2#&sQv?FCF96Joo2Wh~hGo&Qw{L7Zq6h;_kG{4XfC^_1t6egV>JJED}G7 zccU~iY`ktfn^8uGU5yuMO~@)*m@B;1k8)8t|P_j~Q*yxz37W_eHqb!$%58jaxn z5mtTJc!rhhhODLEZ)=$8)-(Tn;}_T|P+W=`(Yycv00Lr5M??VshmXv^00009a7bBm z000XU000XU0RWnu7ytkO2XskIMF;2%5&$4BKyhd{0000PbVXQnLvL+uWo~o;Lvm$d zbY)~9cWHEJAV*0}P*;Ht7XSbRV@X6oR9M5+*;|htM->I&udDlXpL0B8I}Y)H?ZG4& z6M_j=2-hf59uQHG$S>eG!V?lA+(eY{fB_MKh2ubC8?4x7Jf7RR^`$Bg)3L;R`r%Z+ zlv?W2UbWZWt7>)Tb01AAB$-Yo=;S&<610MV#t5CfpGQFyB$+Jx{39xcSh7|0K<1`e z1$N!DXK7}tbirjCSQ=ZHpi$`rLyQ6TEv2M2QY(?vZunRy$>oyh*vQYflcUk{L73QzL#u7yglGO}Y!B}nG} z=C-A%R{Fk|dUDswL27c4OrBKX%wOypYvejxzUo}xhH}v(KkFOM*fp=5d^u3#83ZI9 z-gD10GL`dcxs@G_g^AO0?3wwMnPlWAE{e*5=voZX&7#~t?K&-U=!$J)nP2#?M>@fm zy{2%^k(o*-dfQJ;MRAbs8irYP=7y=xO5vV~2HXB8q3%_a8*)j~TKUaT!3>khgqxx$ zs6|gpN(Z`LP;wu7;Eb~lB`bx5hd!}246%mUyvBz{A{t30)41i9S}oT)vhP`&zUwKa zDCivcU?8#%1ajZfNMng;j>0*+f{~@hMUP$bqUX%bbPAo!?IF70P#}}LDxF4+r2`9@ z3pRC*6*9YCP-086@XQXiZS~m)}|_%mulXk+FT#^`UELYNqkn*j%A9 zQ~B6!W54!ezw;kQqSDHyg zkd&qaiO3*2dD*c{rID=UmVJ$8YJYVk${lD;<-HL?Wy55BKt@|Ogs-eaphk@ut zUzBTAc9br-=8ms>&VAo@(RP2Xy`-}zN-E!Ub}$JU=&Duvj!ZC9Dpihr!*x+6dO_`3 zodZi#l}u*St0%s|`hbks_KFtod&!^O_l}u;&wJ0V-&=U3bzf-{Q*X*OgVA*$x2{u& zuDkBoNFi8C#-iH9zOxo`Ia*V1*y)9&HChk*Bgqt^;AMq(WjY7W3XV;T9NXwElBjdu z4_%@+mh<(=x#$O;5yU5SN(k!#a+xa0u~ug8v88=`_B=3GXeFnVzT~_!a!=V*7|TrL1J&e! z=5nVKG%^Rhm6dEd7ELt@69F57m2rPX%h!djO)9Wb@x(FL|Jyow``MxV|7U$b14Q&G Xyh7$Uo4Mqu00000NkvXXu0mjfFxv2e literal 0 HcmV?d00001 From 89b17273f71c5747d990ccab8eab5a9ac0684687 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:48:14 +1000 Subject: [PATCH 085/124] feat(windows): delete old update icon --- .../src/desktop/kmshell/xml/menuframe_update.jpg | Bin 1404 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 windows/src/desktop/kmshell/xml/menuframe_update.jpg diff --git a/windows/src/desktop/kmshell/xml/menuframe_update.jpg b/windows/src/desktop/kmshell/xml/menuframe_update.jpg deleted file mode 100644 index 6eeb76bfd6f64f987667906903678cbd227dc21b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1404 zcmbVKX;4#F6g~;rNJ1nK5GY98NOfS^h%7}xFbV{rVAuqlfsjQhB!MIdIA9Gb&=$LZ zwjctfRG~~Og#v|EWDx-!s-8V=2B;X z26jM!9k4?z(If|j`i8-(u{Z!o383Ez*IJeK{{@OvrjejhADT)@vRVs3*om-fnl>57 zXaXlOOe8=!4`HrUp_C$AjqoO^LW*PjTah9u71zN7-wjiy3=c(Fgr+U{fe?Zq1h^0n zsgM9kpamzCm1t!TxPlAHexL?1WPk=FC?|mot)(H377in%K}vqG8=)|S&~8@ovsv*A zK`!w~BLQjQ4k}O}P23Og^F|K#y6) zi-y?$^|lJfb%pbWJ!1D!r$$5~K2VU*Ak)ZX5{XQwQYkbhoyla-84MGanW+hj&0;W2 zElt_x919Bz=9;xumK-ZHjs*v62L=tDf<1x22CNJ0(GMb{ONrmhe}h0o!W1gan9e|i zMpGaXjEsmRBistHvrrvKY_i#Uz7NHGznHoqg|kifMFnl+?z#>O(QU&f=XiCtG2L>l zm9@=g2j1ITwr+QEb#s5uL$GJBub;m#AT%sIA`+dvL@JXXI-Kxy9-#(4LPLAObQ0N$bEAgx-rlPXJ?x5avgGJN4=X=ayx;aySMQ| z=kfckK&^7o>2I+{(s|>;rn&Ix&e`XYiLXj7y4;qX@;O*=d{XydorfwUqZ$LLiXEhy zRP6H~4f@_XnvrzoW5T%KX$Knn)U z881h}$Y^;@LR!jjA1UqgZaLRWU;TBhppY9n^VWHybGcq+l3o?vT!z8HHaE9~wM7^t z7LV@rt{JqZ>Mk=`xn5@Zizk9)fe9|~f(q!*6Ic6siIXny@${hh{`42#Ma!KJp!nfC*J z-1dxDR^`y}(%8W0_9&~a)w?$;w)UD|7Hn5J>OMVisIT)Ok5_&nD|Vuk)pOOu=~D5< Yp{(p1BS(7H*d8rVtA$gA18vyQKR^b-kN^Mx From 477bc9fd5a633295e51d831e41728323066c7e81 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:43:31 +1000 Subject: [PATCH 086/124] feat(windows): WIP apply now button needs more checking --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 4 ---- windows/src/desktop/kmshell/main/UfrmMain.pas | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 07bda0aa7f..9f8ad62906 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -656,7 +656,6 @@ end; procedure IdleState.HandleInstallNow; begin bucStateContext.CurrentState.HandleCheck; - // TODO: How do we notify the command line no update available end; { UpdateAvailableState } @@ -778,9 +777,6 @@ begin // Failed three times in this process return to the // IdleState to wait 7 days before trying again ChangeState(IdleState); - // TODO: Future could go to a RetryState which serialized the a retry count - // to disk. Then it could try launch the download again on the next - // kmshell start event. end else begin diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index cb206e0291..bd341e70dc 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -825,6 +825,9 @@ begin end; procedure TfrmMain.Update_CheckNow; +// TODO: epic-windows-update +// Get an instance to the state machine and call handle check so the state can change to update +// available. var UpdateCheck : TRemoteUpdateCheck; begin UpdateCheck := TRemoteUpdateCheck.Create(True); From 6b938e1beffcd1e3f2f8fcfdf5fca0911032fbae Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:47:22 +1000 Subject: [PATCH 087/124] feat(windows): disable apply updated now if no updates Disable the apply update now if no updates are available. Fixes: #12840 --- .../src/desktop/kmshell/xml/keyman_update.xsl | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/windows/src/desktop/kmshell/xml/keyman_update.xsl b/windows/src/desktop/kmshell/xml/keyman_update.xsl index ad84e8e6ad..0daa6ace3b 100644 --- a/windows/src/desktop/kmshell/xml/keyman_update.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_update.xsl @@ -2,6 +2,8 @@ + +
    @@ -19,7 +21,14 @@  
    -
    Updates are available which will be applied when Windows is next restarted:
    +
    + + Updates are available which will be applied when Windows is next restarted: + + + No updates are available. + +
    Select
    @@ -32,11 +41,24 @@
    - - - keyman:update_applynow - 220px - + + + + + + keyman:update_applynow + 220px + + + + + + + 220px + 1 + + + From 20f1ea0d7d6f5cc7e0b18ef060e2e9b687a483aa Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:00:53 +1000 Subject: [PATCH 088/124] feat(windows): add internal borders version grid --- windows/src/desktop/kmshell/xml/config.css | 37 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index c07087dbb1..8a8ebf7ed2 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -503,8 +503,14 @@ table tr display: grid; position: relative; grid-template-columns: 50px 1fr 1fr 1fr; - padding: 10px 50px 10px 64px; - border: grey 1px; + margin: 10px 50px 10px 64px; + /* From https://geary.co/internal-borders-css-grid/ */ + overflow: hidden; + gap: var(--gap); + --gap: 2em; + --line-offset: calc(var(--gap) / 2); + --line-thickness: 1px; + --line-color: black; } .grid_container.grid_disabled @@ -516,11 +522,36 @@ table tr { position: relative; font-size: 12px; - margin: 0px; padding: 1px; margin: 0 0 0 5px; } +.grid_item::before, +.grid_item::after +{ + content: ''; + position: absolute; + background-color: var(--line-color); + z-index: 1; +} + +/* row borders */ +.grid_item::after +{ + inline-size: 100vw; + block-size: var(--line-thickness); + inset-inline-start: 0; + inset-block-start: calc(var(--line-offset) * -1); +} + +/* column borders */ +.grid_item::before +{ + inline-size: var(--line-thickness); + block-size: 100vh; + inset-inline-start: calc(var(--line-offset) * -1); +} + .grid_item_title{ font-weight: bold; } From fc40aa7c8eb5bb18fdb2dae184feaa76b1b38ce5 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:32:02 +1000 Subject: [PATCH 089/124] feat(windows): address review comments --- .../main/Keyman.System.RemoteUpdateCheck.pas | 17 +++--- .../main/Keyman.System.UpdateStateMachine.pas | 52 ++----------------- 2 files changed, 11 insertions(+), 58 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 9e4b0b007e..c78060bbfd 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -56,8 +56,6 @@ type property ShowErrors: Boolean read FShowErrors write FShowErrors; end; -procedure LogMessage(LogMessage: string); - (** * This function checks if a week or CheckPeriod time has passed since the last * update check. @@ -73,11 +71,13 @@ uses System.Win.Registry, Winapi.Windows, Winapi.WinINet, + Sentry.Client, GlobalProxySettings, KLog, keymanapi_TLB, KeymanVersion, + Keyman.System.KeymanSentryClient, Keyman.System.UpdateCheckStorage, kmint, ErrorControlledRegistry, @@ -103,7 +103,8 @@ end; destructor TRemoteUpdateCheck.Destroy; begin if (FErrorMessage <> '') and FShowErrors then - LogMessage(FErrorMessage); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + '"+FErrorMessage+"'); KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = ' + FErrorMessage); KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = ' + @@ -223,13 +224,6 @@ begin end; 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; - function ConfigCheckContinue: Boolean; var Registry: TRegistryErrorControlled; @@ -260,7 +254,8 @@ begin on E: ERegistryException do begin Result := False; - LogMessage(E.Message); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + E.Message); end; end; end; diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 71fbdfb822..8a5642091a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -637,7 +637,7 @@ var FResult: Boolean; RootPath: string; begin - // call seperate process + // call separate process RootPath := ExtractFilePath(ParamStr(0)); FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0), IncludeTrailingPathDelimiter(RootPath), '-bd'); @@ -746,8 +746,10 @@ begin if (not DownloadResult) then begin - // Failed three times in this process return to the - // IdleState to wait 7 days before trying again + // Failed three times in this process; return to the + // IdleState to wait 'CheckPeriod' before trying again + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Error Updates not downloaded after 3 attempts'); ChangeState(IdleState); end else @@ -1016,48 +1018,4 @@ begin // Result := kmShellContinue; end; -// Private Functions: -function ConfigCheckContinue: Boolean; -var - Registry: TRegistryErrorControlled; -begin - { Verify that it has been at least CheckPeriod days since last update check } - Result := False; - try - Registry := TRegistryErrorControlled.Create; // I2890 - try - if Registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then - begin - if Registry.ValueExists(SRegValue_CheckForUpdates) and - not Registry.ReadBool(SRegValue_CheckForUpdates) then - begin - Result := False; - Exit; - end; - if Registry.ValueExists(SRegValue_LastUpdateCheckTime) and - (Now - Registry.ReadDateTime(SRegValue_LastUpdateCheckTime) > - CheckPeriod) then - begin - Result := True; - end - else - begin - Result := False; - end; - Exit; - end; - finally - Registry.Free; - end; - except - { we will not run the check if an error occurs reading the settings } - on E: Exception do - begin - Result := False; - LogMessage(E.Message); - Exit; - end; - end; -end; - end. From 26bbdf31c3d5ff6c540faa364a59ad485e0935a7 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:38:09 +1000 Subject: [PATCH 090/124] feat(windows): add review suggestion Co-authored-by: Eberhard Beilharz --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 8a5642091a..db1749403c 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -373,7 +373,7 @@ begin on E: ERegistryException do begin TKeymanSentryClient.ReportHandledException(E, - 'Failed to write apply now'); + 'Failed to write "apply now"'); end; end; finally From 7fd16dc93f6294c283d4b45cd761051fa6d8769c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 Jan 2025 12:34:53 +0100 Subject: [PATCH 091/124] chore(linux): improve help for build.sh --- resources/docker-images/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 6e81d8a27e..01af657bcd 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -20,8 +20,8 @@ builder_describe \ ":web" \ "--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ "--no-cache Force rebuild of docker images" \ - "build" \ - "test" + "build Build docker images" \ + "test Test the docker images by running configure,build,test for all or the specified platforms" builder_parse "$@" From 93caf9b782a5e4b7fdd91ae73e828abc906d69da Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 Jan 2025 12:42:05 +0100 Subject: [PATCH 092/124] chore(linux): change from SIL International to SIL Global --- resources/docker-images/android/Dockerfile | 4 ++-- resources/docker-images/base/Dockerfile | 4 ++-- resources/docker-images/core/Dockerfile | 4 ++-- resources/docker-images/linux/Dockerfile | 4 ++-- resources/docker-images/web/Dockerfile | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index 6ebafb496a..98c76d332b 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -1,8 +1,8 @@ -# Copyright (c) 2024 SIL International. All rights reserved. +# Copyright (c) 2024 SIL Global. All rights reserved. ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Android Build Image" diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index 14bc0b36cc..fb3bf1dfc9 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -1,9 +1,9 @@ -# Copyright (c) 2024 SIL International. All rights reserved. +# Copyright (c) 2024 SIL Global. All rights reserved. ARG UBUNTU_VERSION=latest FROM ubuntu:${UBUNTU_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Build Base Image" diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 990143f6e2..a3bdd93f9b 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2024 SIL International. All rights reserved. +# Copyright (c) 2024 SIL Global. All rights reserved. # ARGS used in this file: # - ARG BASE_VERSION=default # - ARG REQUIRED_NODE_VERSION=18 @@ -6,7 +6,7 @@ ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Core Build Image" diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile index 3fe18c94b9..8518a64cc2 100644 --- a/resources/docker-images/linux/Dockerfile +++ b/resources/docker-images/linux/Dockerfile @@ -1,8 +1,8 @@ -# Copyright (c) 2024 SIL International. All rights reserved. +# Copyright (c) 2024 SIL Global. All rights reserved. ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Linux Build Image" diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 986212fb11..93b50cb417 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2024 SIL International. All rights reserved. +# Copyright (c) 2024 SIL Global. All rights reserved. # ARGS used in this file: # - ARG BASE_VERSION=default # - ARG REQUIRED_NODE_VERSION=18 @@ -6,7 +6,7 @@ ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman for Web Build Image" From 9c8ad2e7832916701d4ff6e9c7ca02b9dc30557f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 Jan 2025 17:08:57 +0100 Subject: [PATCH 093/124] fix(core): fix compile error Compiling the tests failed in the container. --- core/tests/unit/km_core_keyboard_api.tests.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/tests/unit/km_core_keyboard_api.tests.cpp b/core/tests/unit/km_core_keyboard_api.tests.cpp index 2898f9e7cd..06e604c023 100644 --- a/core/tests/unit/km_core_keyboard_api.tests.cpp +++ b/core/tests/unit/km_core_keyboard_api.tests.cpp @@ -56,10 +56,8 @@ TEST_F(KmCoreKeyboardApiTests, LoadFromBlobNull) { // Setup km::core::path kmxfile = ""; - std::unique_ptr data(new uint8_t[0]); - // Execute - auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.get(), 0, &this->keyboard); + auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), nullptr, 0, &this->keyboard); // Verify EXPECT_EQ(status, KM_CORE_STATUS_INVALID_ARGUMENT); From d425f2a1a4dccbbec8ad3beaf78095900c8ef9b4 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 Jan 2025 18:40:08 +0100 Subject: [PATCH 094/124] chore(linux): use standard header --- resources/docker-images/android/Dockerfile | 2 +- resources/docker-images/base/Dockerfile | 2 +- resources/docker-images/core/Dockerfile | 3 ++- resources/docker-images/linux/Dockerfile | 2 +- resources/docker-images/web/Dockerfile | 3 ++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index 98c76d332b..e86d496ab8 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2024 SIL Global. All rights reserved. +# Keyman is copyright (C) SIL Global. MIT License. ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index fb3bf1dfc9..4c24e89bb0 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2024 SIL Global. All rights reserved. +# Keyman is copyright (C) SIL Global. MIT License. ARG UBUNTU_VERSION=latest FROM ubuntu:${UBUNTU_VERSION} diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index a3bdd93f9b..cefeb6a2ca 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -1,4 +1,5 @@ -# Copyright (c) 2024 SIL Global. All rights reserved. +# Keyman is copyright (C) SIL Global. MIT License. +# # ARGS used in this file: # - ARG BASE_VERSION=default # - ARG REQUIRED_NODE_VERSION=18 diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile index 8518a64cc2..3f680efe3e 100644 --- a/resources/docker-images/linux/Dockerfile +++ b/resources/docker-images/linux/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2024 SIL Global. All rights reserved. +# Keyman is copyright (C) SIL Global. MIT License. ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 93b50cb417..8c708e6a47 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -1,4 +1,5 @@ -# Copyright (c) 2024 SIL Global. All rights reserved. +# Keyman is copyright (C) SIL Global. MIT License. +# # ARGS used in this file: # - ARG BASE_VERSION=default # - ARG REQUIRED_NODE_VERSION=18 From 3251168e6b7a5accf6321a2df32fe9ffb6256933 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 8 Jan 2025 18:40:22 +0100 Subject: [PATCH 095/124] chore(linux): address code review comments --- resources/docker-images/core/Dockerfile | 6 +++--- resources/docker-images/web/Dockerfile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index cefeb6a2ca..924e8f1e39 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -1,8 +1,8 @@ # Keyman is copyright (C) SIL Global. MIT License. # # ARGS used in this file: -# - ARG BASE_VERSION=default -# - ARG REQUIRED_NODE_VERSION=18 +# - ARG BASE_VERSION +# - ARG REQUIRED_EMSCRIPTEN_VERSION ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} @@ -17,7 +17,7 @@ RUN apt-get install -qy git jq llvm meson pkgconf \ # Pre-install emscripten USER build -ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 +ARG REQUIRED_EMSCRIPTEN_VERSION=unset RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \ export EMSDK_KEEP_DOWNLOADS=1 && \ cd /home/build/ && \ diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 8c708e6a47..5a27c9724d 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -2,7 +2,7 @@ # # ARGS used in this file: # - ARG BASE_VERSION=default -# - ARG REQUIRED_NODE_VERSION=18 +# - ARG REQUIRED_EMSCRIPTEN_VERSION=unset ARG BASE_VERSION=default FROM keymanapp/keyman-base-ci:${BASE_VERSION} @@ -23,7 +23,7 @@ COPY run-tests.sh /usr/bin/run-tests.sh # Pre-install emscripten USER build -ARG REQUIRED_EMSCRIPTEN_VERSION=1.0 +ARG REQUIRED_EMSCRIPTEN_VERSION=unset RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \ export EMSDK_KEEP_DOWNLOADS=1 && \ cd /home/build/ && \ From 47c5ae0f4b77fbf43ec389e5d47d12e9d04d9448 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:35:59 +1000 Subject: [PATCH 096/124] feat(windows): re-align with state transistion matrix Made sure that since adding install packages it aligns with the state transistion matrix. Also rename HandleMSIComplete to removed cached files and removed the call in the function for ChangeState(IdleState); Now it can be used in multiple places when cleaning up the cached files. --- .../main/Keyman.System.UpdateStateMachine.pas | 95 +++++++++++++------ 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 6f9b0cb097..c31b3f3ebb 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -73,7 +73,7 @@ type procedure SetStateOnly(const enumState: TUpdateState); function ConvertStateToEnum(const StateClass: TStateClass): TUpdateState; function IsCurrentStateAssigned: Boolean; - procedure HandleMSIInstallComplete; + procedure RemoveCachedFiles; function SetRegistryState(Update: TUpdateState): Boolean; function GetAutomaticUpdates: Boolean; @@ -493,20 +493,19 @@ begin end; end; -procedure TUpdateStateMachine.HandleMSIInstallComplete; +procedure TUpdateStateMachine.RemoveCachedFiles; var SavePath: string; FileName: String; FileNames: TStringDynArray; begin SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - KL.Log('TUpdateStateMachine.HandleMSIInstallComplete'); + KL.Log('TUpdateStateMachine.RemoveCachedFiles'); GetFileNamesInDirectory(SavePath, FileNames); for FileName in FileNames do begin System.SysUtils.DeleteFile(FileName); end; - CurrentState.ChangeState(IdleState); end; procedure TUpdateStateMachine.HandleCheck; @@ -574,7 +573,8 @@ begin // something is wrong. TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Handle first run called in state:"' + Self.ClassName + '"'); - bucStateContext.HandleMSIInstallComplete; + bucStateContext.RemoveCachedFiles; + ChangeState(IdleState); end; { IdleState } @@ -650,13 +650,12 @@ end; procedure IdleState.HandleAbort; begin - + // Do Nothing end; procedure IdleState.HandleInstallNow; begin - bucStateContext.CurrentState.HandleCheck; - // TODO: How do we notify the command line no update available + // Do Nothing end; { UpdateAvailableState } @@ -694,8 +693,22 @@ begin end; procedure UpdateAvailableState.HandleCheck; +var + CheckForUpdates: TRemoteUpdateCheck; + Result: TRemoteUpdateCheckResult; begin - + // Check if new updates while in this state + CheckForUpdates := TRemoteUpdateCheck.Create(True); + try + Result := CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; + if Result <> wucSuccess then + begin + KL.Log('UpdateAvailableState.HandleCheck not successful: '+ + GetEnumName(TypeInfo(TUpdateState), Ord(Result))); + end; end; function UpdateAvailableState.HandleKmShell; @@ -824,6 +837,11 @@ end; procedure DownloadingState.HandleAbort; begin + // TODO epic-windows-updates + // another process is likely downloading the files + // To clean do this would be to set a registry marker + // or file in the cached directory then when download is finished + // we an check for abort. end; procedure DownloadingState.HandleInstallNow; @@ -860,15 +878,29 @@ begin end; procedure WaitingRestartState.HandleCheck; +var + CheckForUpdates: TRemoteUpdateCheck; + Result: TRemoteUpdateCheckResult; begin - + // Check if new updates while in this state + CheckForUpdates := TRemoteUpdateCheck.Create(True); + try + Result := CheckForUpdates.Run; + finally + CheckForUpdates.Free; + end; + { Response OK and go back to update available so files can be downloaded } + if Result = wucSuccess then + begin + ChangeState(UpdateAvailableState); + end; end; function WaitingRestartState.HandleKmShell; var - SavedPath: String; - FileNames: TStringDynArray; frmStartInstall: TfrmStartInstall; + ucr: TUpdateCheckResponse; + hasPackages, hasKeymanInstall: Boolean; begin // Still can't go if keyman has run if HasKeymanRun then @@ -879,17 +911,22 @@ begin end else begin - // Check downloaded cache if available then - SavedPath := IncludeTrailingPathDelimiter - (TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavedPath, FileNames); - if Length(FileNames) = 0 then + // Checking the files are available could be seen us redundant here as the + // Install state will check anyway, but since we still ask the user if they + // want to install lets not bug them if the files are no longer cached. + hasPackages := False; + hasKeymanInstall := False; + if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then + begin + hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); + hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); + end; + if not (hasPackages Or hasKeymanInstall) then begin // Return to Idle state and check for Updates state ChangeState(IdleState); bucStateContext.CurrentState.HandleCheck; // TODO no event here Result := kmShellExit; - // Exit; // again exit was not working end else begin @@ -916,7 +953,7 @@ end; procedure WaitingRestartState.HandleAbort; begin - + ChangeState(UpdateAvailableState); end; procedure WaitingRestartState.HandleInstallNow; @@ -1015,8 +1052,7 @@ begin if not FResult then begin - bucStateContext.HandleMSIInstallComplete; - + bucStateContext.RemoveCachedFiles; TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"' + IntToStr(Ord(FResult)) + '"'); @@ -1068,8 +1104,6 @@ end; procedure InstallingState.Enter; var - SavePath: String; - FileNames: TStringDynArray; ucr: TUpdateCheckResponse; hasPackages, hasKeymanInstall: Boolean; begin @@ -1077,10 +1111,7 @@ begin hasPackages := False; hasKeymanInstall := False; bucStateContext.SetRegistryState(usInstalling); - SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - GetFileNamesInDirectory(SavePath, FileNames); - // TODO: epic-update-windows - // Check if there are also packages to install if so + if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); @@ -1099,7 +1130,9 @@ begin DoInstallKeyman; Exit; end; - + // unexpected: should have had either packages or a keyman file + bucStateContext.RemoveCachedFiles; + ChangeState(IdleState); end; procedure InstallingState.Exit; @@ -1127,7 +1160,7 @@ end; procedure InstallingState.HandleAbort; begin - ChangeState(IdleState); + // To late as MSI is installing end; procedure InstallingState.HandleInstallNow; @@ -1159,8 +1192,8 @@ end; procedure InstallingState.HandleFirstRun; begin - bucStateContext.HandleMSIInstallComplete; - // Result := kmShellContinue; + bucStateContext.RemoveCachedFiles; + ChangeState(IdleState); end; end. From 069ee3c54c1ada75ac877562acf61fb2574a59c8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:45:17 +1000 Subject: [PATCH 097/124] feat(windows): revert debug flow for downloades --- .../main/Keyman.System.UpdateStateMachine.pas | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index c31b3f3ebb..cbed69dc2c 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -203,17 +203,17 @@ type function DoInstallKeyman: Boolean; overload; - (** - * Installs the Keyman Keyboard files using separate shell. - * - * @params SavePath The path to the downloaded files. - * - * @returns True if the installation is successful, False otherwise. - *) + (** + * Installs the Keyman Keyboard files using separate shell. + * + * @params SavePath The path to the downloaded files. + * + * @returns True if the installation is successful, False otherwise. + *) - function DoInstallPackages(Params: TUpdateCheckResponse): Boolean; - function DoInstallPackage(PackageFileName: String): Boolean; - procedure LaunchInstallPackageProcess; + function DoInstallPackages(Params: TUpdateCheckResponse): Boolean; + function DoInstallPackage(PackageFileName: String): Boolean; + procedure LaunchInstallPackageProcess; public procedure Enter; override; @@ -450,7 +450,8 @@ begin end else begin - // TODO: #10210 Error log for Unable to set state for Value + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Set CurrentState was failed'); end; end; @@ -500,7 +501,9 @@ var FileNames: TStringDynArray; begin SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - KL.Log('TUpdateStateMachine.RemoveCachedFiles'); + // TODO: epic-windows-updates + // remove debug log + // KL.Log('TUpdateStateMachine.RemoveCachedFiles'); GetFileNamesInDirectory(SavePath, FileNames); for FileName in FileNames do begin @@ -600,11 +603,6 @@ begin // ChangeState(UpdateAvailableState); // will keep here as there are more PR's #12621 { #### End of Testing ### }; - - { // // TODO-WINDOWS-UPDATES Check how long a check takes then determine - if it needs to be broken into a seperate state of WaitngCheck RESP } - { if Response not OK stay in the idle state and return } - // Handle_check event force check CheckForUpdates := TRemoteUpdateCheck.Create(True); try @@ -626,7 +624,7 @@ var CheckForUpdates: TRemoteUpdateCheck; UpdateCheckResult: TRemoteUpdateCheckResult; begin - // Remote manages the last check time therfore + // Remote manages the last check time therefore // we will allow it to return early if it hasn't reached // the configured time between checks. CheckForUpdates := TRemoteUpdateCheck.Create(False); @@ -772,12 +770,16 @@ var begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); + + // TODO: epic-windows-updates + // Remove this test code { ## for testing log that we would download } - KL.Log('DownloadingState.Enter test code continue'); - DownloadResult := True; + //KL.Log('DownloadingState.Enter test code continue'); + //DownloadResult := True; { End testing } + RetryCount := 0; - //DownloadResult := False; + DownloadResult := False; while (not DownloadResult) and (RetryCount < 3) do begin @@ -837,11 +839,7 @@ end; procedure DownloadingState.HandleAbort; begin - // TODO epic-windows-updates - // another process is likely downloading the files - // To clean do this would be to set a registry marker - // or file in the cached directory then when download is finished - // we an check for abort. + // To abort during the downloading end; procedure DownloadingState.HandleInstallNow; @@ -1056,7 +1054,6 @@ begin TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install failed:"' + IntToStr(Ord(FResult)) + '"'); - // TODO: epic-windows-updates Check this is correct to return to idle ChangeState(IdleState); end; From 92e2d533beb74101919f47d9f5adc8cd2271c4d6 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 9 Jan 2025 14:45:51 +0700 Subject: [PATCH 098/124] chore(web): adds engine/predictive-text script for web/ build target --- web/build.sh | 5 +- web/src/engine/predictive-text/build.sh | 64 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100755 web/src/engine/predictive-text/build.sh diff --git a/web/build.sh b/web/build.sh index d01d493517..0fb51021f6 100755 --- a/web/build.sh +++ b/web/build.sh @@ -166,7 +166,10 @@ builder_run_child_actions build:engine/attachment # Uses engine/interfaces (due to resource-path config interface) builder_run_child_actions build:engine/keyboard-storage -# Uses engine/interfaces, engine/keyboard-storage, & engine/osk +# Builds the predictive-text components +builder_run_child_actions build:engine/predictive-text + +# Uses engine/interfaces, engine/keyboard-storage, engine/predictive-text, & engine/osk builder_run_child_actions build:engine/main # Uses all but engine/element-wrappers and engine/attachment diff --git a/web/src/engine/predictive-text/build.sh b/web/src/engine/predictive-text/build.sh new file mode 100755 index 0000000000..387d9ed183 --- /dev/null +++ b/web/src/engine/predictive-text/build.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Compile keymanweb predictive-text components. + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +# ################################ Main script ################################ + +builder_describe "Builds predictive-text components used within Keyman Engine for Web (KMW)." \ + "clean" \ + "configure" \ + "build" \ + "test" \ + ":templates Builds the model templates utlilized by compiled lexical models" \ + ":wordbreakers Builds the wordbreakers provided for lexical model use" \ + ":worker-main Builds the predictive-text worker interface module" \ + ":worker-thread Builds the predictive-text worker" \ + ":_all (Meta build target used when targets are not specified)" \ + "--ci+ Set to utilize CI-based test configurations & reporting." + +# Possible TODO? +# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ + +builder_parse "$@" + +config=release +if builder_is_debug_build; then + config=debug +fi + +builder_describe_outputs \ + configure "/node_modules" \ + build:templates "/web/src/engine/predictive-text/build/obj/index.js" \ + build:wordbreakers "/web/src/engine/wordbreakers/build/main/obj/index.js" \ + build:worker-main "/web/src/engine/worker-main/build/obj/lmlayer.js" \ + build:worker-thread "/web/src/engine/worker-thread/build/obj/worker-main.wrapped.js" + +BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mjs" + +#### Build action definitions #### + +# We can run all clean & configure actions at once without much issue. + +builder_run_child_actions clean + +## Clean actions + +builder_run_child_actions configure + +## Build actions + +builder_run_child_actions build:wordbreakers + +builder_run_child_actions build:templates +builder_run_child_actions build:worker-thread +builder_run_child_actions build:worker-main + +builder_run_child_actions test \ No newline at end of file From f71da43be56caff5b8944a5086c9e5e4ac318784 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 9 Jan 2025 14:46:06 +0700 Subject: [PATCH 099/124] chore(web): reconnects web headless tests --- web/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/build.sh b/web/build.sh index 0fb51021f6..e6d9a5feb8 100755 --- a/web/build.sh +++ b/web/build.sh @@ -193,7 +193,7 @@ builder_run_child_actions build:test-pages builder_run_action build:_all build_action # Run tests -# builder_run_child_actions test +builder_run_child_actions test builder_run_action test:_all test_action function do_test_help() { From 4fdd16a64b10e305cf40913dbb43f62b77f58ee6 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 9 Jan 2025 14:54:27 +0700 Subject: [PATCH 100/124] change(web): CI vs local testing control flow for predictive-text --- web/src/engine/predictive-text/build.sh | 6 +++++- web/src/engine/predictive-text/worker-main/build.sh | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/web/src/engine/predictive-text/build.sh b/web/src/engine/predictive-text/build.sh index 387d9ed183..63f947718e 100755 --- a/web/src/engine/predictive-text/build.sh +++ b/web/src/engine/predictive-text/build.sh @@ -61,4 +61,8 @@ builder_run_child_actions build:templates builder_run_child_actions build:worker-thread builder_run_child_actions build:worker-main -builder_run_child_actions test \ No newline at end of file +# If doing CI testing, the predictive-text child actions have their own build configuration. +# For local testing, though, we can allow them to proceed. +if ! builder_has_option --ci; then + builder_run_child_actions test +fi \ No newline at end of file diff --git a/web/src/engine/predictive-text/worker-main/build.sh b/web/src/engine/predictive-text/worker-main/build.sh index f7b788b6ef..f15462d71d 100755 --- a/web/src/engine/predictive-text/worker-main/build.sh +++ b/web/src/engine/predictive-text/worker-main/build.sh @@ -57,12 +57,13 @@ function do_build() { function do_test() { local TEST_OPTIONS= if builder_has_option --ci; then - TEST_OPTIONS=--ci + # We'll test the included libraries here for now. At some point, we may wish + # to establish a ci.sh script for predictive-text to handle this instead. + ./unit_tests/test.sh test:libraries test:headless test:browser --ci + else + # If we're not in --ci mode, then this doesn't need to trigger the sibling projects' tests. + ./unit_tests/test.sh test:headless test:browser fi - - # We'll test the included libraries here for now. At some point, we may wish - # to establish a ci.sh script for predictive-text to handle this instead. - ./unit_tests/test.sh test:libraries test:headless test:browser $TEST_OPTIONS } builder_run_action configure do_configure From 5176dd8a0ee5e30f03cf531cebb63c78bf9ba67c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 9 Jan 2025 12:07:10 +0100 Subject: [PATCH 101/124] refactor(linux): address code review comments Also some cleanup. --- resources/docker-images/android/Dockerfile | 3 +++ resources/docker-images/base/Dockerfile | 1 + resources/docker-images/build.sh | 12 ++++++++---- resources/docker-images/linux/run-tests.sh | 5 +++++ resources/docker-images/run.sh | 22 +++++++++++----------- resources/docker-images/web/run-tests.sh | 5 +++++ 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/resources/docker-images/android/Dockerfile b/resources/docker-images/android/Dockerfile index e86d496ab8..91cd095a69 100644 --- a/resources/docker-images/android/Dockerfile +++ b/resources/docker-images/android/Dockerfile @@ -47,6 +47,9 @@ VOLUME /home/build/build WORKDIR /home/build/build # Pre-install gradle. This will put files in ~/.gradle which will speed up builds. +# Note it would be safer to copy these files directly from our repo rather than +# getting it over the Internet, but Docker doesn't allow us to copy files +# from outside the current directory when building the image. RUN mkdir -p $HOME/tmp/gradle/wrapper && \ # KMEA uses gradle-7.6.4-bin curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \ diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index 4c24e89bb0..87516b3f96 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -32,6 +32,7 @@ RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers RUN < /usr/bin/bashwrapper #!/bin/bash export KEYMAN_USE_NVM=1 +export DOCKER_RUNNING=true EOF # Install NVM diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 01af657bcd..8100459d47 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -49,7 +49,9 @@ _add_build_args() { _convert_parameters_to_build_args() { build_args=() build_version= - local required_node_version="$(_print_expected_node_version)" + local required_node_version + # shellcheck disable=SC2034 + required_node_version="$(_print_expected_node_version)" _add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER "" _add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java @@ -82,8 +84,9 @@ build_action() { OPTION_NO_CACHE="--no-cache" fi + # shellcheck disable=SC2164 cd "${platform}" - # shellcheck disable=SC2248 + # shellcheck disable=SC2248,SC2086 docker build ${OPTION_NO_CACHE:-} --platform amd64 -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . # If the user didn't specify particular versions we will additionaly create an image # with the tag 'default'. @@ -91,7 +94,8 @@ build_action() { builder_echo debug "Setting default tag for ${platform}" docker build --platform amd64 -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" . fi - cd - || true + # shellcheck disable=SC2164,SC2103 + cd - builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" } @@ -99,7 +103,7 @@ test_action() { local platform=$1 builder_echo debug "Testing image for ${platform}" - ./run.sh ${platform} -- ./build.sh configure,build,test:${platform} + ./run.sh "${platform}" -- ./build.sh configure,build,test:"${platform}" } if builder_has_action build; then diff --git a/resources/docker-images/linux/run-tests.sh b/resources/docker-images/linux/run-tests.sh index 962418467e..fbc5fb3b67 100755 --- a/resources/docker-images/linux/run-tests.sh +++ b/resources/docker-images/linux/run-tests.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash set -e +if [[ -z "${DOCKER_RUNNING:-}" ]]; then + echo "This script is intended to be run inside a docker container." + exit 0 +fi + # Start system dbus sudo dbus-daemon --system --fork diff --git a/resources/docker-images/run.sh b/resources/docker-images/run.sh index c85c15e873..c42d6c2a02 100755 --- a/resources/docker-images/run.sh +++ b/resources/docker-images/run.sh @@ -21,37 +21,37 @@ builder_describe \ builder_parse "$@" run_android() { - docker run -it --rm -v ${KEYMAN_ROOT}:/home/build/build \ - -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ + docker run -it --rm -v "${KEYMAN_ROOT}":/home/build/build \ + -v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \ keymanapp/keyman-android-ci:default \ "${builder_extra_params[@]}" } run_core() { - docker run -it --rm -v ${KEYMAN_ROOT}:/home/build/build \ - -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ + docker run -it --rm -v "${KEYMAN_ROOT}":/home/build/build \ + -v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \ keymanapp/keyman-core-ci:default \ "${builder_extra_params[@]}" } run_linux() { - mkdir -p ${KEYMAN_ROOT}/linux/build/docker-linux - docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ - -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ - -v ${KEYMAN_ROOT}/linux/build/docker-linux:/home/build/build/linux/build \ + mkdir -p "${KEYMAN_ROOT}/linux/build/docker-linux" + docker run -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ + -v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \ + -v "${KEYMAN_ROOT}/linux/build/docker-linux":/home/build/build/linux/build \ -e DESTDIR=/tmp \ keymanapp/keyman-linux-ci:default \ "${builder_extra_params[@]}" } run_web() { - docker run -it --privileged --rm -v ${KEYMAN_ROOT}:/home/build/build \ - -v ${KEYMAN_ROOT}/core/build/docker-core:/home/build/build/core/build \ + docker run -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ + -v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \ keymanapp/keyman-web-ci:default \ "${builder_extra_params[@]}" } -mkdir -p ${KEYMAN_ROOT}/core/build/docker-core +mkdir -p "${KEYMAN_ROOT}/core/build/docker-core" builder_run_action android run_android builder_run_action core run_core diff --git a/resources/docker-images/web/run-tests.sh b/resources/docker-images/web/run-tests.sh index 1a3100159f..84871d8b19 100755 --- a/resources/docker-images/web/run-tests.sh +++ b/resources/docker-images/web/run-tests.sh @@ -1,4 +1,9 @@ #!/usr/bin/env bash +if [[ -z "${DOCKER_RUNNING:-}" ]]; then + echo "This script is intended to be run inside a docker container." + exit 0 +fi + set -e echo "Starting Xvfb..." Xvfb -screen 0 1024x768x24 :33 &> /dev/null & From 3f35478c6b1debe4c159745f49ba984a2e396e1a Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 10 Jan 2025 08:17:44 +0700 Subject: [PATCH 102/124] chore(web): Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- web/src/engine/predictive-text/build.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/src/engine/predictive-text/build.sh b/web/src/engine/predictive-text/build.sh index 63f947718e..677d8b2269 100755 --- a/web/src/engine/predictive-text/build.sh +++ b/web/src/engine/predictive-text/build.sh @@ -17,7 +17,7 @@ builder_describe "Builds predictive-text components used within Keyman Engine fo "configure" \ "build" \ "test" \ - ":templates Builds the model templates utlilized by compiled lexical models" \ + ":templates Builds the model templates utilized by compiled lexical models" \ ":wordbreakers Builds the wordbreakers provided for lexical model use" \ ":worker-main Builds the predictive-text worker interface module" \ ":worker-thread Builds the predictive-text worker" \ @@ -48,9 +48,6 @@ BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mj # We can run all clean & configure actions at once without much issue. builder_run_child_actions clean - -## Clean actions - builder_run_child_actions configure ## Build actions From 7eb283c7d3591ddf0c72deacaecf24a01ba5106e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:07:34 +1000 Subject: [PATCH 103/124] feat(windows): access violation due to syntax error In rendering the xml for the update configuration, there was a lookup of the Keyboard from the json file to kmcom object, it wasn't found but the if begin statement was incorrect so it attempte render the string anyway. --- windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas index 32fd7b2cf6..c2f7a2bfc2 100644 --- a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -65,8 +65,9 @@ begin begin n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); if n >= 0 then - pkg := kmcom.Packages[n]; begin + pkg := kmcom.Packages[n]; + xml := xml + ''+ ''+IntToStr(i+1)+''+ From 3380dfc4154e4b50f9db03574ddfe9d9a7383afe Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:28:31 +1000 Subject: [PATCH 104/124] feat(windows): commit review suggestions Co-authored-by: Eberhard Beilharz --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index cbed69dc2c..150ae8ceaa 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -201,7 +201,7 @@ type * @returns True if the installation is successful, False otherwise. *) - function DoInstallKeyman: Boolean; overload; + function DoInstallKeyman: Boolean; overload; (** * Installs the Keyman Keyboard files using separate shell. @@ -705,7 +705,7 @@ begin if Result <> wucSuccess then begin KL.Log('UpdateAvailableState.HandleCheck not successful: '+ - GetEnumName(TypeInfo(TUpdateState), Ord(Result))); + GetEnumName(TypeInfo(TUpdateState), Ord(Result))); end; end; @@ -909,7 +909,7 @@ begin end else begin - // Checking the files are available could be seen us redundant here as the + // Checking the files are available could be seen as redundant here as the // Install state will check anyway, but since we still ask the user if they // want to install lets not bug them if the files are no longer cached. hasPackages := False; @@ -1171,7 +1171,7 @@ var begin KL.Log('InstallingState.HandleInstallPackages'); // This event should only be reached in elevated process if not then - // move on to just installing Keyman packages + // move on to just installing Keyman if not kmcom.SystemInfo.IsAdministrator then begin KL.Log('InstallingState.HandleInstallPackages Not Admin'); From ec8a501dfab28198dbd109f944100244c0c767b8 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:53:36 +1000 Subject: [PATCH 105/124] feat(windows): address review comments --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 150ae8ceaa..ec53d55ca7 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1115,13 +1115,19 @@ begin hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); end; KL.Log('InstallingState.Enter before hasPackages'); + { Notes: The reason packages (keyboards) is installed first is + because we are trying to reduce the number of times the user has + to be asked to elevate to admin or restart. Keyboard installation always + needs elevation, when we do that and execute kmshell as an elevated process + we can then launch the Keyman installer and it will not need + to ask for elevation. } if hasPackages then begin KL.Log('InstallingState.Enter hasPackages'); LaunchInstallPackageProcess; Exit; end; - // only reach here if no has packages otherwise it will + // If no packages then install Keyman now if hasKeymanInstall then begin DoInstallKeyman; From 102af4f30459cecbe0fa2e0aa3caa6f1b0924028 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 10 Jan 2025 14:59:38 +0700 Subject: [PATCH 106/124] fix(android): processing of hardware keystrokes when OSK is hidden Fixes: #12366 --- .../app/src/main/java/com/keyman/engine/KMKeyboard.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 7d731f2b63..de01e0bcc5 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; @@ -70,6 +71,10 @@ final class KMKeyboard extends WebView { protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED; protected ArrayList javascriptAfterLoad = new ArrayList<>(); + // .getMainLooper() returns the looper associated with the main UI thread. + // https://stackoverflow.com/questions/13974661/runonuithread-vs-looper-getmainlooper-post-in-android + private Handler jsQueuer = new Handler(Looper.getMainLooper()); + private static String currentKeyboard = null; /** @@ -368,7 +373,7 @@ final class KMKeyboard extends WebView { if(this.javascriptAfterLoad.size() > 0) { // Don't call this WebView method on just ANY thread - run it on the main UI thread. // https://stackoverflow.com/a/22611010 - this.postDelayed(new Runnable() { + jsQueuer.postDelayed(new Runnable() { @Override public void run() { StringBuilder allCalls = new StringBuilder(); From dbaa7198a6cf981308a18fb88c9bad02f3ab3629 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:59:04 +1000 Subject: [PATCH 107/124] feat(windows): check correct member of data structure This change check the correct member of the data structure for the keyman bundle install file. It also uses a more simple string match for the file extension. --- .../main/Keyman.System.UpdateCheckStorage.pas | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas index 376721c6bb..a7a822df7c 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -70,18 +70,13 @@ end; class function TUpdateCheckStorage.HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean; var - i : Integer; - fileName : string; - f: TSearchRec; + fileExtension: string; begin - Result := False; - for i := 0 to High(data.Packages) do - begin - fileName := data.Packages[i].FileName; - if FindFirst(fileName + '*.exe', 0, f) = 0 then - Result := True; - System.SysUtils.FindClose(f); - end; + fileExtension := LowerCase(ExtractFileExt(data.FileName)); + if fileExtension = '.exe' then + Result := True + else + Result := False end; end. From c4ea97c889fc309ea89c66479efde14960837913 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:44:10 +1000 Subject: [PATCH 108/124] feat(windows): onlineupdatecheck references removed All but one references and calls to OnlineUpdateCheck where removed this is the first step towards removing these units completley. HttpServer.App.OnlineUpdate.pas is still calling this so the code will remain untill removed. --- .../main/Keyman.System.DownloadUpdate.pas | 3 +- .../main/Keyman.System.RemoteUpdateCheck.pas | 3 +- windows/src/desktop/kmshell/main/UfrmMain.pas | 31 +------------------ windows/src/desktop/kmshell/main/initprog.pas | 20 ++---------- .../desktop/kmshell/startup/UfrmSplash.pas | 4 +-- windows/src/engine/keyman/UfrmKeyman7Main.dfm | 4 +-- windows/src/engine/keyman/UfrmKeyman7Main.pas | 8 ++--- .../kmcomapi/com/system/keymancontrol.pas | 2 +- 8 files changed, 13 insertions(+), 62 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index f91c8f6ea4..f41f4a39d7 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -9,8 +9,7 @@ uses System.SysUtils, httpuploader, Keyman.System.UpdateCheckResponse, - KeymanPaths, - OnlineUpdateCheck; + KeymanPaths; type TDownloadUpdateParams = record diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index c78060bbfd..df892929f0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -13,8 +13,7 @@ uses System.SysUtils, KeymanPaths, httpuploader, - Keyman.System.UpdateCheckResponse, - OnlineUpdateCheck; + Keyman.System.UpdateCheckResponse; const CheckPeriod: Integer = 7; // Days between checking for updates diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index bd341e70dc..e436abed5e 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -141,7 +141,6 @@ type procedure Support_Diagnostics; procedure Support_Online; - procedure Support_UpdateCheck; procedure Support_ProxyConfig; procedure Support_ContactSupport(params: TStringList); // I4390 @@ -187,7 +186,6 @@ uses MessageIdentifierConsts, MessageIdentifiers, Keyman.System.RemoteUpdateCheck, - OnlineUpdateCheck, OptionsXMLRenderer, Keyman.Configuration.System.UmodWebHttpServer, Keyman.Configuration.System.HttpServer.App.ConfigMain, @@ -348,7 +346,6 @@ begin else if command = 'support_diagnostics' then Support_Diagnostics else if command = 'support_online' then Support_Online - else if command = 'support_updatecheck' then Support_UpdateCheck else if command = 'support_proxyconfig' then Support_ProxyConfig else if command = 'update_checknow' then Update_CheckNow @@ -797,32 +794,6 @@ begin Free; end; end; -// TODO-WINDOWS-UPDATES: #10210 Remove Update -procedure TfrmMain.Support_UpdateCheck; -begin - with TOnlineUpdateCheck.Create(Self, True, False) do - try - case Run of - oucShutDown: - begin - try - if kmcom.Control.IsKeymanRunning then - try - kmcom.Control.StopKeyman; - except - on E:Exception do KL.Log(E.Message); - end; - except - on E:Exception do KL.Log(E.Message); - end; - end; - oucSuccess: - DoRefresh; - end - finally - Free; - end; -end; procedure TfrmMain.Update_CheckNow; // TODO: epic-windows-update @@ -841,7 +812,7 @@ end; procedure TfrmMain.Update_ApplyNow; var - ShellPath, s: string; + ShellPath : string; FResult: Boolean; begin ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 55f0f422f2..b3abdbe694 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -80,8 +80,8 @@ type fmUninstallPackage, fmRegistryAdd, fmRegistryRemove, fmMain, fmHelp, fmHelpKMShell, fmMigrate, fmSplash, fmStart, - fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 - fmOnlineUpdateAdmin, fmTextEditor, + fmUpgradeKeyboards, // I2548 + fmTextEditor, fmInstallKeyboardPackageAdmin, fmBackgroundUpdateCheck, fmBackgroundDownload, @@ -123,7 +123,6 @@ uses kmint, KMShellHints, KeymanMutex, - OnlineUpdateCheck, Keyman.System.RemoteUpdateCheck, RegistryKeys, UfrmBaseKeyboard, @@ -246,7 +245,6 @@ begin else if s = '-uk' then FMode := fmUninstallKeyboard { I1201 - Fix crash uninstalling admin-installed keyboards and packages } else if s = '-ukl' then FMode := fmUninstallKeyboardLanguage // I3624 else if s = '-up' then FMode := fmUninstallPackage { I1201 - Fix crash uninstalling admin-installed keyboards and packages } - else if s = '-ou' then FMode := fmOnlineUpdateAdmin { I1730 - Check update of keyboards (admin elevation) } else if s = '-ikp' then FMode := fmInstallKeyboardPackageAdmin else if s = '-a' then FMode := fmAbout else if s = '-ra' then FMode := fmRegistryAdd @@ -255,9 +253,6 @@ begin else if s = '-?' then FMode := fmHelpKMShell else if s = '-h' then FMode := fmHelp else if s = '-t' then FMode := fmTextEditor - //TODO-WINDOWS-UPDATES: will remove -ouc not used - // -buc uses the Statemachine can be used for external scripts to force a check - else if s = '-ouc' then FMode := fmOnlineUpdateCheck else if s = '-buc' then FMode := fmBackgroundUpdateCheck else if s = '-bd' then FMode := fmBackgroundDownload else if s = '-an' then FMode := fmApplyInstallNow @@ -514,17 +509,6 @@ begin then ExitCode := 0 else ExitCode := 2; - fmOnlineUpdateAdmin: - OnlineUpdateAdmin(nil, FirstKeyboardFileName); - - fmOnlineUpdateCheck: - with TOnlineUpdateCheck.Create(nil, FForce, FSilent) do - try - Run; - finally - Free; - end; - fmUpgradeKeyboards:// I2548 begin if FQuery='13,backup' then diff --git a/windows/src/desktop/kmshell/startup/UfrmSplash.pas b/windows/src/desktop/kmshell/startup/UfrmSplash.pas index 1be49479f1..5a6cccd529 100644 --- a/windows/src/desktop/kmshell/startup/UfrmSplash.pas +++ b/windows/src/desktop/kmshell/startup/UfrmSplash.pas @@ -90,7 +90,6 @@ uses MessageIdentifierConsts, MessageIdentifiers, KeymanMutex, - OnlineUpdateCheck, PngImage, ErrorControlledRegistry, RegistryKeys, @@ -268,8 +267,7 @@ begin if kmcom.Options[KeymanOptionName(TUtilKeymanOption.koCheckForUpdates)].Value then begin - if not kmcom.Control.IsOnlineUpdateCheckOpen then - RunConfiguration(0, '-ouc -s'); + RunConfiguration(0, '-buc -s'); end; end; end; diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.dfm b/windows/src/engine/keyman/UfrmKeyman7Main.dfm index d04a7f649c..ab0d25d3e5 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.dfm +++ b/windows/src/engine/keyman/UfrmKeyman7Main.dfm @@ -27,10 +27,10 @@ object frmKeyman7Main: TfrmKeyman7Main Left = 28 Top = 40 end - object tmrOnlineUpdateCheck: TTimer + object tmrBackgroundUpdateCheck: TTimer Enabled = False Interval = 300000 - OnTimer = tmrOnlineUpdateCheckTimer + OnTimer = tmrBackgroundUpdateCheckTimer Left = 280 Top = 104 end diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index d8cb3e30e7..c0f6e9d62f 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -198,13 +198,13 @@ type TfrmKeyman7Main = class(TForm) mnu: TPopupMenu; tmrTestKeymanFunctioning: TTimer; - tmrOnlineUpdateCheck: TTimer; + tmrBackgroundUpdateCheck: TTimer; tmrCheckInputPane: TTimer; tmrRefresh: TTimer; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure tmrTestKeymanFunctioningTimer(Sender: TObject); - procedure tmrOnlineUpdateCheckTimer(Sender: TObject); + procedure tmrBackgroundUpdateCheckTimer(Sender: TObject); procedure tmrCheckInputPaneTimer(Sender: TObject); procedure tmrRefreshTimer(Sender: TObject); private @@ -1838,7 +1838,7 @@ begin end; end; -procedure TfrmKeyman7Main.tmrOnlineUpdateCheckTimer(Sender: TObject); +procedure TfrmKeyman7Main.tmrBackgroundUpdateCheckTimer(Sender: TObject); begin with TRegistryErrorControlled.Create do // I2890 try @@ -1846,7 +1846,7 @@ begin begin if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) then Exit; if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) then Exit; - TKeymanDesktopShell.RunKeymanConfiguration('-ouc'); + TKeymanDesktopShell.RunKeymanConfiguration('-buc'); end; finally Free; diff --git a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas index b3f8958596..6b429b985e 100644 --- a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas +++ b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas @@ -429,7 +429,7 @@ end; procedure TKeymanControl.OpenUpdateCheck; begin - RunKeymanConfiguration('-ouc'); + RunKeymanConfiguration('-buc'); end; procedure TKeymanControl.StartKeyman; From 986b11f690f7b8077d839b59c83fd112bf989d52 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:02:50 +1000 Subject: [PATCH 109/124] feat(windows): Remove Update tasktray icon old dialog Removes the systemtray icon and the old update dialog. --- windows/src/desktop/kmshell/kmshell.dpr | 2 - windows/src/desktop/kmshell/kmshell.dproj | 16 ++-- .../kmshell/main/OnlineUpdateCheck.pas | 91 +------------------ 3 files changed, 10 insertions(+), 99 deletions(-) diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 87f9fbf6bf..19e22a946c 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -43,7 +43,6 @@ uses InterfaceHotkeys in '..\..\global\delphi\general\InterfaceHotkeys.pas', utilsystem in '..\..\..\..\common\windows\delphi\general\utilsystem.pas', Upload_Settings in '..\..\..\..\common\windows\delphi\general\Upload_Settings.pas', - UfrmOnlineUpdateNewVersion in 'main\UfrmOnlineUpdateNewVersion.pas' {frmOnlineUpdateNewVersion}, OnlineUpdateCheck in 'main\OnlineUpdateCheck.pas', utilxml in '..\..\..\..\common\windows\delphi\general\utilxml.pas', UfrmInstallKeyboardFromWeb in 'install\UfrmInstallKeyboardFromWeb.pas' {frmInstallKeyboardFromWeb}, @@ -77,7 +76,6 @@ uses UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas', UILanguages in 'util\UILanguages.pas', UfrmKeyboardOptions in 'main\UfrmKeyboardOptions.pas' {frmKeyboardOptions}, - UfrmOnlineUpdateIcon in 'main\UfrmOnlineUpdateIcon.pas' {frmOnlineUpdateIcon}, KeymanTrayIcon in '..\..\engine\keyman\KeymanTrayIcon.pas', UImportOlderVersionKeyboards10 in 'main\UImportOlderVersionKeyboards10.pas', VisualKeyboard in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 5be4729400..2bb3c24512 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -171,9 +171,6 @@ - -
    frmOnlineUpdateNewVersion
    -
    @@ -224,9 +221,6 @@
    frmKeyboardOptions
    - -
    frmOnlineUpdateIcon
    -
    @@ -430,6 +424,12 @@ + + kmshell.rsm + true + + + kmshell.exe true @@ -441,9 +441,9 @@ true - + - kmshell.exe + .\ true diff --git a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas index c202742813..f48335df3f 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -103,7 +103,6 @@ type function DownloadUpdates: Boolean; procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean); function DoRun: TOnlineUpdateCheckResult; - procedure ShowUpdateForm; procedure ShutDown; procedure DownloadUpdatesHTTPStatus(Sender: THTTPUploader; const Message: string; Position, Total: Int64); // I2855 @@ -157,8 +156,6 @@ uses utildir, utilexecute, OnlineUpdateCheckMessages, - UfrmOnlineUpdateIcon, - UfrmOnlineUpdateNewVersion, utilkmshell, utilsystem, utiluac, @@ -393,86 +390,6 @@ begin end; end; -procedure TOnlineUpdateCheck.ShowUpdateForm; -var - i: Integer; - FRequiresAdmin: Boolean; - FOwnerHandle: THandle; -begin - if Assigned(FOwner) - then FOwnerHandle := FOwner.Handle - else FOwnerHandle := Application.Handle; - - { We have an update available } - with OnlineUpdateNewVersion(FOwner) do - try - Params := Self.FParams; - if ShowModal <> mrYes then - begin - Self.FParams.Result := oucUnknown; - Self.FErrorMessage := ''; - Exit; - end; - - Self.FParams := Params; - finally - Free; - end; - - if not DownloadUpdates then - begin - Self.FParams.Result := oucUnknown; // I2742 - Exit; - end - else - begin - if not kmcom.SystemInfo.IsAdministrator then - begin - FRequiresAdmin := FParams.Keyman.Install; - for i := 0 to High(FParams.Packages) do - if FParams.Packages[i].Install then - begin - FRequiresAdmin := True; - Break; - end; - end - else - FRequiresAdmin := False; - - if FRequiresAdmin then - begin - if CanElevate then - begin - SavePackageUpgradesToDownloadTempPath; - if WaitForElevatedConfiguration(FOwnerHandle, '-ou "'+DownloadTempPath+'"', not FParams.Keyman.Install) <> 0 then // I2513 - FParams.Result := oucFailure - else if FParams.Keyman.Install then - FParams.Result := oucShutDown - else - FParams.Result := oucSuccess; - end - else - begin - ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); - FParams.Result := oucFailure; - end; - end - else - begin - FParams.Result := oucSuccess; - for i := 0 to High(FParams.Packages) do - if FParams.Packages[i].Install then - if not DoInstallPackage(FParams.Packages[i]) then FParams.Result := oucFailure; - - if FParams.Keyman.Install then - begin - DoInstallKeyman; - FParams.Result := oucShutDown; - end; - end; - end; -end; - procedure TOnlineUpdateCheck.ShutDown; begin if Assigned(Application) then @@ -581,12 +498,8 @@ begin end else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then begin - if not FSilent then - ShowUpdateForm - else - begin - ShowUpdateIcon; - end; + // No longer showing user notification through the online update + // forms or icon after background windows update see #10038 Result := FParams.Result; end; end From ffd9150e38edb9782a10ed4b1bcd5964d57f66eb Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:47:40 +1000 Subject: [PATCH 110/124] feat(windows): Address todo epic issues --- .../main/Keyman.System.DownloadUpdate.pas | 23 ++++++------- .../main/Keyman.System.UpdateStateMachine.pas | 33 +++---------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index f41f4a39d7..40cc86b3b0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -7,7 +7,9 @@ interface uses System.Classes, System.SysUtils, + Sentry.Client, httpuploader, + Keyman.System.KeymanSentryClient, Keyman.System.UpdateCheckResponse, KeymanPaths; @@ -39,7 +41,7 @@ type function DownloadUpdates : Boolean; - // TODO-WINDOWS-UPDATES: verify filesizes match the ucr metadata so we know we don't have partial downloades. + // TODO: #12888 verify filesizes match the ucr metadata so we know we don't have partial downloads. //function VerifyAllFilesDownloaded : Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; @@ -63,12 +65,12 @@ uses Upload_Settings, utilkmshell; - // TODO-WINDOWS-UPDATES: temp wrapper for converting showmessage to logs don't know where - // if not using klog - procedure LogMessage(LogMessage: string); - begin - KL.Log(LogMessage); - end; +procedure ErrorLogMessage(ErrorLogMessage: string); +begin + KL.Log(ErrorLogMessage); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + ErrorLogMessage); +end; constructor TDownloadUpdate.Create; begin @@ -124,8 +126,8 @@ var 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])); + then ErrorLogMessage(S_OnlineUpdate_UnableToContact) + else ErrorLogMessage(WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message])); Result := False; end; end; @@ -156,8 +158,7 @@ begin Inc(FDownload.TotalSize, Params.InstallSize); if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 begin - // TODO-WINDOWS-UPDATES: #10210 convert to error log. - LogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); + ErrorLogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); end else begin diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index ec53d55ca7..c9f8f5dd9a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -261,8 +261,6 @@ begin end; // TODO: #10210 TODO: epic-windows-update remove debugging comments throughout this Unit. - // KL.Log('TUpdateStateMachine.Destroy: FErrorMessage = '+FErrorMessage); - // KL.Log('TUpdateStateMachine.Destroy: FParams.Result = '+IntToStr(Ord(FParams.Result))); inherited Destroy; end; @@ -414,7 +412,8 @@ begin except on E: ERegistryException do begin - KL.Log('Failed to read registry: ' + E.Message); + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'Failed to read registry: ' + E.Message); Result := False; end; end; @@ -501,9 +500,6 @@ var FileNames: TStringDynArray; begin SavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath); - // TODO: epic-windows-updates - // remove debug log - // KL.Log('TUpdateStateMachine.RemoveCachedFiles'); GetFileNamesInDirectory(SavePath, FileNames); for FileName in FileNames do begin @@ -704,7 +700,7 @@ begin end; if Result <> wucSuccess then begin - KL.Log('UpdateAvailableState.HandleCheck not successful: '+ + KL.Log('UpdateAvailableState.HandleCheck CheckForUpdates not successful: '+ GetEnumName(TypeInfo(TUpdateState), Ord(Result))); end; end; @@ -771,13 +767,6 @@ begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); - // TODO: epic-windows-updates - // Remove this test code - { ## for testing log that we would download } - //KL.Log('DownloadingState.Enter test code continue'); - //DownloadResult := True; - { End testing } - RetryCount := 0; DownloadResult := False; @@ -987,10 +976,8 @@ var begin if not kmcom.SystemInfo.IsAdministrator then begin - KL.Log('InstallingState.LaunchInstallPackageProcess not IsAdmin'); if CanElevate then begin - KL.Log('InstallingState.LaunchInstallPackageProcess CanElevate'); executeResult := WaitForElevatedConfiguration(0, '-ikp'); if (executeResult <> 0) then begin @@ -998,21 +985,18 @@ begin (Sentry.Client.SENTRY_LEVEL_ERROR, 'Executing kmshell process to install keyboard packages failed:"' + IntToStr(Ord(executeResult)) + '"'); - KL.Log('InstallingState.LaunchInstallPackageProcess Error elevating'); ChangeState(IdleState); end; end else begin - KL.Log('InstallingState.LaunchInstallPackageProcess require user with admin'); // TODO: epic-windows-updates How do we alert the user that package requires a user with admin rights // ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.'); end; end else begin - KL.Log('InstallingState.LaunchInstallPackageProcess HandlePackages straight away'); - HandleInstallPackages; // we can install packages straight away + HandleInstallPackages; // can install packages straight away end; end; @@ -1065,7 +1049,6 @@ var FPackage: IKeymanPackageFile2; begin Result := True; - KL.Log('InstallingState.DoInstallPackage Entry' + PackageFileName); FPackage := kmcom.Packages.GetPackageFromFile(PackageFileName) as IKeymanPackageFile2; FPackage.Install2(True); @@ -1074,7 +1057,7 @@ begin kmcom.Refresh; kmcom.Apply; - KL.Log('InstallingState.DoInstallPackage about to delete'); + System.SysUtils.DeleteFile(PackageFileName); end; @@ -1092,7 +1075,6 @@ begin PackageFullPath := SavePath + Params.Packages[i].FileName; if not DoInstallPackage(PackageFullPath) then // I2742 begin - // Package did install log or error KL.Log('Installing Package failed' + PackageFullPath); end; end; @@ -1114,7 +1096,6 @@ begin hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); end; - KL.Log('InstallingState.Enter before hasPackages'); { Notes: The reason packages (keyboards) is installed first is because we are trying to reduce the number of times the user has to be asked to elevate to admin or restart. Keyboard installation always @@ -1123,7 +1104,6 @@ begin to ask for elevation. } if hasPackages then begin - KL.Log('InstallingState.Enter hasPackages'); LaunchInstallPackageProcess; Exit; end; @@ -1175,18 +1155,15 @@ procedure InstallingState.HandleInstallPackages; var ucr: TUpdateCheckResponse; begin - KL.Log('InstallingState.HandleInstallPackages'); // This event should only be reached in elevated process if not then // move on to just installing Keyman if not kmcom.SystemInfo.IsAdministrator then begin - KL.Log('InstallingState.HandleInstallPackages Not Admin'); DoInstallKeyman; Exit; end; if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin - KL.Log('InstallingState.HandleInstallPackages about to call do install packages'); DoInstallPackages(ucr); end; From b68eb251ede93fcdebf59df9b4a6cd94fc914917 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:03:47 +1000 Subject: [PATCH 111/124] feat(windows): localise todo --- .../kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas | 4 ++-- .../main/Keyman.Configuration.UI.UfrmStartInstallNow.pas | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas index 8571c0b515..5d3b11767a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.pas @@ -1,7 +1,7 @@ { Keyman is copyright (C) SIL Global. MIT License. - - // TODO-WINDOWS-UPDATES: Localise all the labels and captions. + + // TODO: #12887 Localise all the labels and captions. } unit Keyman.Configuration.UI.UfrmStartInstall; interface diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas index 778974e363..ce3fd29da0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstallNow.pas @@ -1,7 +1,7 @@ { Keyman is copyright (C) SIL Global. MIT License. - // TODO-WINDOWS-UPDATES: Localise all the labels and captions. + // TODO: #12887 Localise all the labels and captions. } unit Keyman.Configuration.UI.UfrmStartInstallNow; interface From 42b7fea4093e15f23732a48fc3dd3dce1cf53544 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:06:58 +1000 Subject: [PATCH 112/124] feat(windows): remove test comments --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 5 ----- 1 file changed, 5 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index c9f8f5dd9a..8ca0346707 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -595,11 +595,6 @@ var Result: TRemoteUpdateCheckResult; begin - { ##### For Testing only just advancing to downloading #### } - // ChangeState(UpdateAvailableState); - // will keep here as there are more PR's #12621 - { #### End of Testing ### }; - // Handle_check event force check CheckForUpdates := TRemoteUpdateCheck.Create(True); try Result := CheckForUpdates.Run; From 9d66b2981c841cb7fd7466169e6a6222e2356550 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:33:59 +1000 Subject: [PATCH 113/124] feat(windows): address review comments Co-authored-by: Eberhard Beilharz --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 8ca0346707..380d2f2675 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -413,7 +413,7 @@ begin on E: ERegistryException do begin TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, - 'Failed to read registry: ' + E.Message); + 'Failed to read registry: ' + E.Message); Result := False; end; end; From 71f7b002558ec758600d61ade79cea78776d6fd5 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:41:31 +1000 Subject: [PATCH 114/124] feat(windows): remove deprecated conditional check With the refactoring of displaying the updates available the check only if conditional was no longer needed. --- .../desktop/kmshell/main/OnlineUpdateCheck.pas | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas index f48335df3f..ba5d835a17 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -489,19 +489,8 @@ 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 - // No longer showing user notification through the online update - // forms or icon after background windows update see #10038 - Result := FParams.Result; - end; + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := FParams.Result; end else begin From 74f4392d752613d45f627c42a1c4eb85ad2f62f6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 15 Jan 2025 09:59:50 +0700 Subject: [PATCH 115/124] fix(developer): detect invalid key ids in touch layout files Fixes: #12870 --- .../src/kmw-compiler/validate-layout-file.ts | 10 + ...out_invalid_identifier.keyman-touch-layout | 550 ++++++++++++++++++ .../error_touch_layout_invalid_identifier.kmn | 10 + .../kmc-kmn/test/kmw/kmw-compiler.tests.ts | 17 +- 4 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn diff --git a/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts b/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts index a5ee9da293..9bed88f5f8 100644 --- a/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts +++ b/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts @@ -46,6 +46,16 @@ function GetKeyIdUnicodeType(value: string): TKeyIdType { function KeyIdType(FId: string): TKeyIdType { // I4142 FId = FId.toUpperCase(); + + // Validate key id format: + // K_xxxx -- predefined virtual key - restricted character set + // T_xxxx -- custom 'touch' virtual key - touch key id + // U_ABCD_1234 -- Unicode key id (1+ chars) + // x00 -- "ISO" key identifier (not currently supported in touch layout files) + if(!/^((K_[A-Z0-9_?]+)|(T_\S+)|(U_[0-9A-F_]+))$/.test(FId)) { + // note: |[A-Z][0-9][0-9] -- ISO key identifiers not currently supported + return TKeyIdType.Key_Invalid; + } switch(FId.charAt(0)) { case 'T': return TKeyIdType.Key_Touch; diff --git a/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout b/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout new file mode 100644 index 0000000000..e08e33eedc --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout @@ -0,0 +1,550 @@ +{ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "U_1E6B[_0307]", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Arial" + } +} \ No newline at end of file diff --git a/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn b/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn new file mode 100644 index 0000000000..9dfa805bb5 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn @@ -0,0 +1,10 @@ +store(&VERSION) '15.0' +store(&NAME) "error_touch_layout_invalid_identifier" +store(©RIGHT) '© 2015-2024 SIL Global' +store(&TARGETS) 'any' +store(&LAYOUTFILE) 'error_touch_layout_invalid_identifier.keyman-touch-layout' +store(&KEYBOARDVERSION) '1.3' + +begin Unicode > use(main) + +group(main) using keys \ No newline at end of file diff --git a/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts b/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts index 37a58384b2..d90b33c312 100644 --- a/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts +++ b/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts @@ -36,7 +36,9 @@ describe('KeymanWeb Compiler', function() { }); this.afterEach(function() { - callbacks.printMessages(); + if(this.currentTest?.isFailed() || debug) { + callbacks.printMessages(); + } callbacks.clear(); }); @@ -203,6 +205,19 @@ describe('KeymanWeb Compiler', function() { assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel)); }); + it('should give error ERROR_TouchLayoutInvalidIdentifier if a virtual key is badly formatted e.g. U_1234[_5678]', async function() { + // #12870 + const filenames = generateTestFilenames('error_touch_layout_invalid_identifier'); + + let result = await kmnCompiler.run(filenames.source, null); + assert.isNull(result); + assert.isFalse(callbacks.hasMessage(KmnCompilerMessages.INFO_MinimumCoreEngineVersion)); + assert.isFalse(callbacks.hasMessage(KmwCompilerMessages.INFO_MinimumWebEngineVersion)); + assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_TouchLayoutInvalidIdentifier)); + assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_InvalidTouchLayoutFile)); + assert.lengthOf(callbacks.messages, 2); + }); + }); async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string): From 83dbfcc04679722e19c99330614bb0b309e08bb7 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:53:14 +1000 Subject: [PATCH 116/124] feat(windows): UI removed from state machine The UI checks have been moved outside the state machine. This was clean for the install now case. For the waiting restart case not so. To help a new method ready to install was added to the state machine. --- .../main/Keyman.System.UpdateStateMachine.pas | 101 +++++------------- windows/src/desktop/kmshell/main/UfrmMain.pas | 34 +++++- windows/src/desktop/kmshell/main/initprog.pas | 24 ++++- 3 files changed, 79 insertions(+), 80 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 380d2f2675..6b81dfe5ce 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -9,16 +9,11 @@ interface uses System.SysUtils, - System.UITypes, - System.IOUtils, System.Types, System.TypInfo, Sentry.Client, - httpuploader, KeymanPaths, - Keyman.Configuration.UI.UfrmStartInstall, - Keyman.Configuration.UI.UfrmStartInstallNow, Keyman.System.ExecutionHistory, Keyman.System.UpdateCheckResponse, utilkmshell; @@ -95,6 +90,15 @@ type procedure HandleInstallPackages; procedure HandleFirstRun; function CurrentStateName: string; + (** + * Checks if Keyman is the WaitingRestartState and that + * Keyman has not run in this Windows session. + * The sole purpose is for the calling code then produce + * a UI to confirm the user wants to continue install. + * + * @returns True if the Keyman is ready to install. + *) + function ReadyToInstall: Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; function CheckRegistryState: TUpdateState; @@ -559,6 +563,18 @@ begin Result := CurrentState.ClassName; end; +function TUpdateStateMachine.ReadyToInstall: Boolean; +var + ucr: TUpdateCheckResponse; +begin + if not IsCurrentStateAssigned then + Exit(False); + if (CurrentState.ClassName = 'WaitingRestartState') and not HasKeymanRun then + Result := True + else + Result := False; +end; + // base implmentation to be overiden procedure TState.HandleInstallPackages; @@ -722,34 +738,9 @@ begin end; procedure UpdateAvailableState.HandleInstallNow; -var - frmStartInstallNow: TfrmStartInstallNow; - InstallNow: Boolean; begin - - InstallNow := True; - if HasKeymanRun then - begin - // TODO: epic-update-windows UI and non-UI units should be split - // if the unit launches UI then it should be a .UI. unit - // https://github.com/keymanapp/keyman/pull/12375/files#r1751041747 - frmStartInstallNow := TfrmStartInstallNow.Create(nil); - try - if frmStartInstallNow.ShowModal = mrOk then - InstallNow := True - else - InstallNow := False; - finally - frmStartInstallNow.Free; - end; - end; - // If user decides NOT to install now stay in UpdateAvailable State - if InstallNow = True then - begin - bucStateContext.SetApplyNow(True); - ChangeState(DownloadingState); - end; - + bucStateContext.SetApplyNow(True); + ChangeState(DownloadingState); end; { DownloadingState } @@ -880,7 +871,6 @@ end; function WaitingRestartState.HandleKmShell; var - frmStartInstall: TfrmStartInstall; ucr: TUpdateCheckResponse; hasPackages, hasKeymanInstall: Boolean; begin @@ -888,14 +878,9 @@ begin if HasKeymanRun then begin Result := kmShellContinue; - // Exit; // Exit is not wokring for some reason. - // this else is only here because the exit is not working. end else begin - // Checking the files are available could be seen as redundant here as the - // Install state will check anyway, but since we still ask the user if they - // want to install lets not bug them if the files are no longer cached. hasPackages := False; hasKeymanInstall := False; if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then @@ -907,23 +892,13 @@ begin begin // Return to Idle state and check for Updates state ChangeState(IdleState); - bucStateContext.CurrentState.HandleCheck; // TODO no event here + bucStateContext.CurrentState.HandleCheck; Result := kmShellExit; end else begin - frmStartInstall := TfrmStartInstall.Create(nil); - try - if frmStartInstall.ShowModal = mrOk then - begin - ChangeState(InstallingState); - Result := kmShellExit; - end - else - Result := kmShellContinue; - finally - frmStartInstall.Free; - end; + ChangeState(InstallingState); + Result := kmShellExit; end; end; end; @@ -939,29 +914,9 @@ begin end; procedure WaitingRestartState.HandleInstallNow; -// If user decides not to install now stay in WaitingRestart State -var - frmStartInstallNow: TfrmStartInstallNow; - InstallNow: Boolean; begin - InstallNow := True; - if HasKeymanRun then - begin - frmStartInstallNow := TfrmStartInstallNow.Create(nil); - try - if frmStartInstallNow.ShowModal = mrOk then - InstallNow := True - else - InstallNow := False; - finally - frmStartInstallNow.Free; - end; - end; - if InstallNow = True then - begin - bucStateContext.SetApplyNow(True); - ChangeState(InstallingState); - end; + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState); end; // Installing packages needs to be elevated diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index e436abed5e..86df47ec70 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -85,6 +85,7 @@ uses Winapi.Windows, keymanapi_TLB, + Sentry.Client, XMLRenderer, KeyboardListXMLRenderer, UfrmKeymanBase, @@ -185,12 +186,15 @@ uses LanguagesXMLRenderer, MessageIdentifierConsts, MessageIdentifiers, + Keyman.System.ExecutionHistory, + Keyman.System.KeymanSentryClient, Keyman.System.RemoteUpdateCheck, OptionsXMLRenderer, Keyman.Configuration.System.UmodWebHttpServer, Keyman.Configuration.System.HttpServer.App.ConfigMain, Keyman.Configuration.UI.InstallFile, Keyman.Configuration.UI.UfrmSettingsManager, + Keyman.Configuration.UI.UfrmStartInstallNow, RegistryKeys, SupportXMLRenderer, UfrmChangeHotkey, @@ -813,12 +817,32 @@ end; procedure TfrmMain.Update_ApplyNow; var ShellPath : string; - FResult: Boolean; + FResult, InstallNow: Boolean; + frmStartInstallNow: TfrmStartInstallNow; begin - ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); - FResult := TUtilExecute.Shell(0, ShellPath, '', '-an'); - if not FResult then - KL.Log('TrmfMain: Executing Update_ApplyNow Failed'); // TODO: Make error log + InstallNow := True; + // Confirm User is ok that this will require a reset + if HasKeymanRun then + begin + frmStartInstallNow := TfrmStartInstallNow.Create(nil); + try + if frmStartInstallNow.ShowModal = mrOk then + InstallNow := True + else + InstallNow := False; + finally + frmStartInstallNow.Free; + end; + end; + + if InstallNow = True then + begin + ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell); + FResult := TUtilExecute.Shell(0, ShellPath, '', '-an'); + if not FResult then + TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, + 'TrmfMain: Shell Execute Update_ApplyNow Failed'); + end; end; procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index b3abdbe694..651387bc47 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -117,6 +117,7 @@ uses Keyman.Configuration.System.TIPMaintenance, Keyman.Configuration.System.UImportOlderVersionKeyboards11To13, Keyman.Configuration.UI.UfrmSettingsManager, + Keyman.Configuration.UI.UfrmStartInstall, Keyman.System.KeymanStartTask, KeymanPaths, KLog, @@ -389,6 +390,8 @@ var FIcon: string; FMutex: TKeymanMutex; // I2720 BUpdateSM : TUpdateStateMachine; + frmStartInstall: TfrmStartInstall; + UserCanceled : Boolean; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -460,9 +463,26 @@ begin end else begin - if BUpdateSM.HandleKmShell = 1 then + // The following logic around the WaitingRestartState should be + // encapsulated in the state machine however as we want separation of + // UI elements from the state machine we have bring some of logic here. + UserCanceled := False; + if BUpdateSM.ReadyToInstall and + (not FSilent and (FMode in [fmStart, fmSplash, fmMain, fmAbout])) then + begin + frmStartInstall := TfrmStartInstall.Create(nil); + try + if frmStartInstall.ShowModal = mrOk then + UserCanceled := False + else + UserCanceled := True + finally + frmStartInstall.Free; + end; + end; + if not UserCanceled and (BUpdateSM.HandleKmShell = 1) then Exit; - end; + end; finally BUpdateSM.Free; end; From 1e20d7adf1c9f6f1f5a4b179c92b666c600ac072 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:29:16 +1000 Subject: [PATCH 117/124] feat(windows): make sure there is a keyman install Need to make sure there is a keyman install package before calling do install keyman. The handleinstallpackages is launched on a different process hence why we need it to call do install keyman. --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 6b81dfe5ce..747914b6b1 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1104,20 +1104,26 @@ end; procedure InstallingState.HandleInstallPackages; var ucr: TUpdateCheckResponse; + hasKeymanInstall : Boolean; begin + TUpdateCheckStorage.LoadUpdateCacheData(ucr); + hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); // This event should only be reached in elevated process if not then // move on to just installing Keyman if not kmcom.SystemInfo.IsAdministrator then begin - DoInstallKeyman; + if hasKeymanInstall then + DoInstallKeyman; Exit; end; + if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin DoInstallPackages(ucr); end; - DoInstallKeyman; + if hasKeymanInstall then + DoInstallKeyman; end; procedure InstallingState.HandleFirstRun; From c6e92a58d458cc72c918450f24a21a449efaae27 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:51:50 +1000 Subject: [PATCH 118/124] feat(windows): remove unused variable --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 747914b6b1..80fbf08634 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -564,8 +564,6 @@ begin end; function TUpdateStateMachine.ReadyToInstall: Boolean; -var - ucr: TUpdateCheckResponse; begin if not IsCurrentStateAssigned then Exit(False); From 4bc3b78072e896bf9f1c51429302dc50d3a45290 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 16 Jan 2025 09:14:21 +0700 Subject: [PATCH 119/124] chore: use GitHub PR titles when writing HISTORY.md --- HISTORY.md | 2 +- resources/build/increment-version.sh | 6 +++--- resources/build/version/src/fixupHistory.ts | 4 ++-- resources/build/version/src/index.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 5d9206857f..85c52044f9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -213,7 +213,7 @@ ## 18.0.134 alpha 2024-11-04 -* (#12606) +* fix(developer): ldml don't allow a uset as right-hand-side variable (#12606) ## 18.0.133 alpha 2024-11-01 diff --git a/resources/build/increment-version.sh b/resources/build/increment-version.sh index 1a577b480f..da459776eb 100755 --- a/resources/build/increment-version.sh +++ b/resources/build/increment-version.sh @@ -104,9 +104,9 @@ echo "increment-version.sh: running resources/build/version" pushd "$KEYMAN_ROOT" ABORT=0 if [[ -z "$fromversion" ]]; then - node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE || ABORT=$? + node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --github-pr || ABORT=$? else - node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --from "$fromversion" --to "$toversion" || ABORT=$? + node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --github-pr --from "$fromversion" --to "$toversion" || ABORT=$? fi if [[ $ABORT = 50 ]]; then @@ -179,7 +179,7 @@ if [ "$action" == "commit" ]; then # In order to avoid potential git conflicts, we run the history collater # again on the master HISTORY.md. Note that the script always exits 1 to # indicate it hasn't updated VERSION.md. We could tweak that in the future. - node resources/build/version/lib/index.js history --no-write-github-comment -t "$GITHUB_TOKEN" -b "$base" || true + node resources/build/version/lib/index.js history --no-write-github-comment --github-pr -t "$GITHUB_TOKEN" -b "$base" || true # If HISTORY.md has been updated, then we want to create a branch and push # it for review diff --git a/resources/build/version/src/fixupHistory.ts b/resources/build/version/src/fixupHistory.ts index 2183b11f70..a8ceda3c50 100644 --- a/resources/build/version/src/fixupHistory.ts +++ b/resources/build/version/src/fixupHistory.ts @@ -184,7 +184,7 @@ export const sendCommentToPullRequestAndRelatedIssues = async ( */ export const fixupHistory = async ( - octokit: GitHub, base: string, force: boolean, writeGithubComment: boolean, from?: string, to?: string + octokit: GitHub, base: string, force: boolean, writeGithubComment: boolean, useGitHubPRInfo: boolean, from?: string, to?: string ): Promise => { // @@ -194,7 +194,7 @@ export const fixupHistory = async ( let pulls: PRInformation[] = []; try { - pulls = await reportHistory(octokit, base, force, false, from, to); + pulls = await reportHistory(octokit, base, force, useGitHubPRInfo, from, to); } catch(e) { logWarning(String(e)); return -1; diff --git a/resources/build/version/src/index.ts b/resources/build/version/src/index.ts index 16f1583e39..ea0b12e7e7 100644 --- a/resources/build/version/src/index.ts +++ b/resources/build/version/src/index.ts @@ -108,7 +108,7 @@ const main = async (): Promise => { if(argv._.includes('history')) { logInfo(`# Validating history for ${version}`); - changeCount = await fixupHistory(octokit, argv.base, argv.force, argv['write-github-comment'], argv.from, argv.to); + changeCount = await fixupHistory(octokit, argv.base, argv.force, argv['write-github-comment'], argv['github-pr'], argv.from, argv.to); logInfo(`# ${changeCount} change(s) found for ${version}\n`); } From 67a1622c422ae42f1d016630bc3ad1c704736b5e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 16 Jan 2025 11:04:22 +0700 Subject: [PATCH 120/124] fix(developer): filter incorrect fonts out of .keyboard_info The font collection code was somewhat wrong in kmc-keyboard-info. It collected all fonts referenced in the package, even for multi-keyboard packages, which meant that the .keyboard_info file listed all fonts for all languages. Furthermore, if a font was referenced in multiple language entries in the .kps, then it would be repeated for each language in the .keyboard_info. This patch addresses both of these bugs. The good news is that this makes some of the .keyboard_info files smaller. In particular, fv_all.keyboard_info goes from 4675 lines down to 695 lines! Fixes: #12852 --- .../src/keyboard-info-compiler.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts index 74c06d5d12..ee784429b9 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts @@ -499,9 +499,6 @@ export class KeyboardInfoCompiler implements KeymanCompiler { keyboard_info.languages[language] = {}; } - const fontSource = [].concat(...kmpJsonData.keyboards.map(e => e.displayFont ? [e.displayFont] : []), ...kmpJsonData.keyboards.map(e => e.webDisplayFonts ?? [])); - const oskFontSource = [].concat(...kmpJsonData.keyboards.map(e => e.oskFont ? [e.oskFont] : []), ...kmpJsonData.keyboards.map(e => e.webOskFonts ?? [])); - let commonScript = null; for(const bcp47 of Object.keys(keyboard_info.languages)) { @@ -528,6 +525,20 @@ export class KeyboardInfoCompiler implements KeymanCompiler { // do it right now. // + // The code below: + // 1. Only includes fonts associated with keyboards which support the current bcp47 (filter) + // 2. Joins the displayFont and webDisplayFonts data, and removes duplicates (...new Set()) + + const supportedKeyboards = kmpJsonData.keyboards.filter(k => k.languages.find(lang => lang.id == bcp47)); + const fontSource = [...new Set([].concat( + ...supportedKeyboards.map(e => e.displayFont ? [e.displayFont] : []), + ...supportedKeyboards.map(e => e.webDisplayFonts ?? []) + ))]; + const oskFontSource = [...new Set([].concat( + ...supportedKeyboards.map(e => e.oskFont ? [e.oskFont] : []), + ...supportedKeyboards.map(e => e.webOskFonts ?? []) + ))]; + if(fontSource.length) { language.font = await this.fontSourceToKeyboardInfoFont(kpsFilename, kmpJsonData, fontSource); if(language.font == null) { From 488cab0dd4cecedb39ebf605f60c9f297620d5a4 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Jan 2025 11:40:48 +0700 Subject: [PATCH 121/124] chore(ios): Update crowdin strings for Khmer --- .../KeymanEngine/km.lproj/Localizable.strings | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ios/engine/KMEI/KeymanEngine/km.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/km.lproj/Localizable.strings index 47b548eea2..d6592e3e98 100644 --- a/ios/engine/KMEI/KeymanEngine/km.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/km.lproj/Localizable.strings @@ -226,6 +226,24 @@ /* Text showing name of spacebar caption - language + keyboard */ "menu-settings-spacebar-item-languageKeyboard" = "ភាសា និងក្ដារចុច"; +/* Label for the "Adjust Keyboard Height" item on the main settings screen */ +"menu-settings-adjust-keyboard-height" = "កែកម្ពស់ក្ដារចុច"; + +/* Title for the "Adjust Keyboard Height" settings child screen */ +"adjust-keyboard-height-title" = "កែក្ដារចុច"; + +/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */ +"button-label-reset-default-keyboard-height" = "ប្ដូរ​កម្ពស់ក្ដារចុចទៅលំនាំដើម"; + +/* Instruction text to drag keyboard to resize */ +"keyboard-drag-instructions" = "រំកិលព្រួញដើម្បីកែកម្ពស់ក្ដារចុច"; + +/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */ +"portrait-keyboard-rotate-instructions" = "បង្វិលឧបករណ៍ដើម្បីប្តូរទៅជាផ្ដេក"; + +/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */ +"landscape-keyboard-rotate-instructions" = "បង្វិលឧបករណ៍ដើម្បីប្តូរទៅជាបញ្ឈរ"; + /* Short text for notification: download failure for keyboard */ "notification-download-failure-keyboard" = "មិន​អាច​ទាញ​យក​ក្ដារចុច​បាន​ទេ"; From af10f1de68b09d9b0ca01bbf6e1d197d366d9bf0 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 16 Jan 2025 13:02:52 -0500 Subject: [PATCH 122/124] auto: increment master version to 18.0.169 --- HISTORY.md | 13 +++++++++++++ VERSION.md | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 85c52044f9..0e676f9040 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,18 @@ # Keyman Version History +## 18.0.168 alpha 2025-01-16 + +* chore: update macOS environment-variable shell script (#12878) +* fix(android): use main looper to dispatch key events when OSK is hidden (#12871) +* chore(web): integrates predictive-text builds to top level script, reconnects headless tests (#12866) +* chore(ios): Update crowdin strings for Khmer (#12910) +* chore(windows): merge master epic windows updates (#12904) +* fix(developer): detect invalid key ids in touch layout files (#12895) +* chore: use GitHub PR titles when writing HISTORY.md (#12907) +* fix(developer): filter incorrect fonts out of .keyboard_info (#12909) +* change(web): make 'keep' transform pattern match standard suggestion pattern by including the prefix string (#12906) +* chore: Add docker images for building the different platforms (#11397) + ## 18.0.167 alpha 2025-01-15 * chore: changes to use https and removes anchor in docs (#12838) diff --git a/VERSION.md b/VERSION.md index 492b0286be..b7db0fe155 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.168 \ No newline at end of file +18.0.169 \ No newline at end of file From 1d342ad0d7007ab41fdde3fe6d0e9bead18bf9b9 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:23:32 +1000 Subject: [PATCH 123/124] chore(windows): remove postinstall state from diagram The postinstall state no longer exists due to the way interaction with the microsoft installer works. This PR just updateds the diagram to match. --- .../src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md index 160d54b2c1..34383de72e 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md +++ b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md @@ -6,6 +6,5 @@ stateDiagram Downloading --> Installing Downloading --> WaitingRestart WaitingRestart --> Installing - Installing --> PostInstall - PostInstall --> Idle + Installing --> Idle ``` From ea8a9cdbb9e56cf8d7135f9e4651a6895c648110 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 16 Jan 2025 19:31:47 -0500 Subject: [PATCH 124/124] auto: increment master version to 18.0.170 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 0e676f9040..78352f95e5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 18.0.169 alpha 2025-01-17 + +* chore(windows): remove `postinstall` state from mermaid diagram (#12923) + ## 18.0.168 alpha 2025-01-16 * chore: update macOS environment-variable shell script (#12878) diff --git a/VERSION.md b/VERSION.md index b7db0fe155..df7920cb67 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.169 \ No newline at end of file +18.0.170 \ No newline at end of file