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 01/43] 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 62599851cd2..7404a2af4fc 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -162,6 +162,7 @@ interface 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 @@ interface 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 @@ interface 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 00000000000..a8d4e72cab3 --- /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 00000000000..ccdcffa2bd3 --- /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 fc40f584cda..fb2dcc3ddf4 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 @@ interface Keyman.System.UpdateCheckResponse, OnlineUpdateCheck; +const + CheckPeriod: Integer = 7; // Days between checking for updates + type ERemoteUpdateCheck = class(Exception); @@ -61,6 +63,7 @@ TRemoteUpdateCheck = class end; procedure LogMessage(LogMessage: string); +function CheckForUpdates: Boolean; implementation @@ -122,111 +125,6 @@ function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 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 02/43] 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 71e434af915..c3a2312f6b2 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 16f49e757d8..d09d24cdc19 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 ccdcffa2bd3..f68e422337f 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 @@ interface CheckPeriod: Integer = 7; // Days between checking for updates type - TRemoteUpdateCheckDownloadParams = record + TDownloadUpdateParams = record TotalSize: Integer; TotalDownloads: Integer; StartPosition: Integer; @@ -41,7 +41,7 @@ TDownloadUpdate = class 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 03/43] feat(windows): WIP merge sm --- .../desktop/kmshell/main/BackgroundUpdate.pas | 3 +- .../main/Keyman.System.DownloadUpdate.pas | 161 ++++++++++-------- .../main/Keyman.System.RemoteUpdateCheck.pas | 2 +- 3 files changed, 95 insertions(+), 71 deletions(-) diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas index a8d4e72cab3..cd545045c4e 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -352,7 +352,8 @@ implementation 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 f68e422337f..b5f570579b5 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 @@ TDownloadUpdate = class 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; - - Request.SetURL(url); - Upload; - if Response.StatusCode = 200 then + http := THttpUploader.Create(nil); + try + http.Proxy.Server := GetProxySettings.Server; + http.Proxy.Port := GetProxySettings.Port; + http.Proxy.Username := GetProxySettings.Username; + http.Proxy.Password := GetProxySettings.Password; + http.Request.Agent := API_UserAgent; + + http.Request.SetURL(url); + http.Upload; + if http.Response.StatusCode = 200 then + begin + fs := TFileStream.Create(savepath, fmCreate); + try + fs.Write(http.Response.PMessageBody^, http.Response.MessageBodyLength); + finally + fs.Free; + end; + Result := True; + end + else // I2742 + // If it fails we set to false but will try the other files + Result := False; + Exit; + finally + http.Free; + end; + except + on E:EHTTPUploader do begin - 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); + Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize); + Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName; + end; - // Keyboard Packages - FDownload.StartPosition := 0; - for i := 0 to High(Params.Packages) do - begin - if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742 - begin - Params.Packages[i].Install := False; // Download failed but install other files - end - else - Inc(downloadCount); - FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; - end; + // Add the Keyman installer + Inc(FDownload.TotalDownloads); + Inc(FDownload.TotalSize, Params.InstallSize); - // Keyamn Installer - if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742 + // Keyboard Packages + FDownload.StartPosition := 0; + for i := 0 to High(Params.Packages) do begin - // TODO record fail? and log // Download failed but user wants to install other files - end - else - begin - Inc(downloadCount) + if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742 + begin + Params.Packages[i].Install := False; // Download failed but install other files + end + else + Inc(downloadCount); + FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize; end; - // There needs to be at least one file successfully downloaded to return - // TRUE that files 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; + // 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 @@ function TDownloadUpdate.DownloadUpdates(Params: TUpdateCheckResponse): Boolean; 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 b6680ca40ec..63510b946e5 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 04/43] 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 | 162 ++++--------- .../main/Keyman.System.DownloadUpdate.pas | 57 ++++- .../main/Keyman.System.RemoteUpdateCheck.pas | 219 +++--------------- 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, 233 insertions(+), 331 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 00000000000..558defbdec9 --- /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 c3a2312f6b2..5eb0ee9013c 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 d09d24cdc19..159a3fe88d2 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 cd545045c4e..4809d9587ca 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -32,6 +32,7 @@ interface httpuploader, Keyman.System.UpdateCheckResponse, + Keyman.System.ExecuteHistory, UfrmDownloadProgress; type @@ -132,47 +133,8 @@ UpdateAvailableState = class(TState) 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 @@ TBackgroundUpdate = class tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - function IsKeymanRunning: Boolean; + //function IsKeymanRunning: Boolean; function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; @@ -353,6 +315,7 @@ implementation utilsystem, utiluac, versioninfo, + Keyman.System.RemoteUpdateCheck, Keyman.System.DownloadUpdate; const @@ -501,18 +464,18 @@ function TBackgroundUpdate.CheckRegistryState : TUpdateState; // I2329 -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 @@ procedure IdleState.Exit; 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 @@ procedure DownloadingState.Enter; DownloadResult := DownloadUpdatesBackground; if DownloadResult then begin - if bucStateContext.IsKeymanRunning then + if HasKeymanRun then ChangeState(WaitingRestartState) else ChangeState(InstallingState); @@ -860,70 +833,31 @@ function DownloadingState.StateName; 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 } - - 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); + 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 Result in [ wucSuccess] then +// begin +// kmcom.Keyboards.Refresh; +// kmcom.Keyboards.Apply; +// kmcom.Packages.Refresh; +// end; - 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 @@ procedure WaitingPostInstallState.HandleMSIInstallComplete; 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 b5f570579b5..d216f2f4634 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 @@ TDownloadUpdateParams = record 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 @@ implementation 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe 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 63510b946e5..b1738bad936 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 @@ TRemoteUpdateCheck = class private FForce: Boolean; FRemoteResult: TRemoteUpdateCheckResult; - FErrorMessage: string; - FShowErrors: Boolean; - FDownload: TRemoteUpdateCheckDownloadParams; - FCheckOnly: Boolean; - - function DownloadUpdates(Params: TUpdateCheckResponse) : Boolean; - procedure DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse; 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. + } 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 @@ implementation { TRemoteUpdateCheck } -constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); +constructor TRemoteUpdateCheck.Create(AForce: Boolean); begin inherited Create; @@ -83,7 +89,6 @@ constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); FRemoteResult := wucUnknown; FForce := AForce; - FCheckOnly := ACheckOnly; KL.Log('TRemoteUpdateCheck.Create'); end; @@ -102,135 +107,18 @@ destructor TRemoteUpdateCheck.Destroy; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function CheckForUpdates: Boolean; 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 4076ab21015..7a2e05200d9 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -433,7 +433,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - 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 74857532839..76eb4929dd1 100644 --- a/windows/src/desktop/kmshell/util/utilkmshell.pas +++ b/windows/src/desktop/kmshell/util/utilkmshell.pas @@ -33,7 +33,7 @@ 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 @@ function GetLongFile(APath:String):String; 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 9f8a3fba8e9..d995ca24c90 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 44959854d5a..71cdb756d38 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 2c019150d7c..223c1385582 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -45,7 +45,8 @@ implementation 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 @@ procedure RunProgram; 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 05/43] 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 558defbdec9..97e9b2bfb1b 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -18,14 +18,18 @@ function RecordKeymanStarted : Boolean; 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 @@ function HasKeymanRun : Boolean; 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 4809d9587ca..fe2eb22a6c4 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -213,8 +213,6 @@ WaitingPostInstallState = class(TState) { 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 @@ TBackgroundUpdate = class 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 @@ TBackgroundUpdate = class 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 @@ implementation { 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 @@ constructor TBackgroundUpdate.Create(AOwner: TCustomForm; AForce, ASilent: Boole 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 @@ procedure TBackgroundUpdate.SetState(const Value: TStateClass); 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 @@ procedure TBackgroundUpdate.SetState(const Value: TStateClass); 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 @@ procedure IdleState.HandleCheck; 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 @@ procedure WaitingRestartState.HandleDownload; 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 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 @@ procedure InstallingState.HandleDownload; 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 @@ procedure 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 := '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 d216f2f4634..bee733e4b7d 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 @@ TDownloadUpdate = class } function DownloadUpdates : Boolean; + function CheckAllFilesDownloaded : Boolean; property ShowErrors: Boolean read FShowErrors write FShowErrors; end; @@ -82,7 +83,10 @@ implementation 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 @@ function TDownloadUpdate.DownloadUpdates: Boolean; 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 7a2e05200d9..94e47a32680 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -143,6 +143,7 @@ implementation UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, + BackgroundUpdate, KeyboardTIPCheck, @@ -384,7 +385,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 - RemoteUpdateCheck: TRemoteUpdateCheck; + BUpdateSM : TBackgroundUpdate; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -433,15 +434,20 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - 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 bdf602ae056..5e61ff1f4d9 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 @@ function ReportFailure(hInstall: MSIHANDLE; const func: string; code: UINT): UIN 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 @@ function EnginePostInstall(hInstall: MSIHANDLE): UINT; 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 223c1385582..9f1dbc9b3f0 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -46,6 +46,7 @@ implementation RegistryKeys, UfrmKeyman7Main, UserMessages, + Klog, Keyman.System.ExecuteHistory; function ValidateParameters(var FCommand: Integer): Boolean; forward; @@ -77,10 +78,11 @@ procedure RunProgram; 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 06/43] 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 a779fdcdd2b..5df7c2aa6a7 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -149,6 +149,7 @@ TfrmMain = class(TfrmWebContainer) procedure DoApply; procedure DoRefresh; procedure Update_CheckNow; + procedure Update_ApplyNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -185,6 +186,7 @@ implementation LanguagesXMLRenderer, MessageIdentifierConsts, MessageIdentifiers, + Keyman.System.RemoteUpdateCheck, OnlineUpdateCheck, OptionsXMLRenderer, Keyman.Configuration.System.UmodWebHttpServer, @@ -209,7 +211,8 @@ implementation utilkmshell, utilhttp, utiluac, - utilxml; + utilxml, + KeymanPaths; type PHKL = ^HKL; @@ -349,6 +352,7 @@ procedure TfrmMain.FireCommand(const command: WideString; params: TStringList); 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 @@ procedure TfrmMain.Support_ProxyConfig; Free; end; end; - +// TODO: #10210 Remove Update procedure TfrmMain.Support_UpdateCheck; begin with TOnlineUpdateCheck.Create(Self, True, False) do @@ -821,16 +825,30 @@ procedure TfrmMain.Support_UpdateCheck; 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 94e47a32680..d4d43874cc3 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -83,6 +83,7 @@ procedure Main(Owner: TComponent = nil); fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, fmBackgroundUpdateCheck, + fmApplyInstallNow, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 @@ -253,6 +254,7 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF 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 07/43] 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 7404a2af4fc..6de89094233 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -180,6 +180,7 @@ interface 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 fe2eb22a6c4..a816987822d 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -98,6 +98,7 @@ TState = class abstract 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 @@ IdleState = class(TState) 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 @@ UpdateAvailableState = class(TState) 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 @@ DownloadingState = class(TState) procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -156,6 +160,7 @@ WaitingRestartState = class(TState) procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -181,6 +186,7 @@ InstallingState = class(TState) procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -194,6 +200,7 @@ RetryState = class(TState) procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -207,6 +214,7 @@ WaitingPostInstallState = class(TState) procedure HandleInstall; override; procedure HandleMSIInstallComplete; override; procedure HandleAbort; override; + procedure HandleInstallNow; override; function StateName: string; override; end; @@ -254,6 +262,7 @@ TBackgroundUpdate = class 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 @@ TBackgroundUpdate = class 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 @@ function TBackgroundUpdate.CheckRegistryState : TUpdateState; // I2329 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 @@ procedure TBackgroundUpdate.HandleAbort; CurrentState.HandleAbort; end; +procedure TBackgroundUpdate.HandleInstallNow; +begin + CurrentState.HandleInstallNow; +end; + function TBackgroundUpdate.CurrentStateName: string; begin // Implement your logic here @@ -699,6 +766,12 @@ procedure IdleState.HandleAbort; // 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 @@ procedure UpdateAvailableState.Enter; bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAuto then begin - bucStateContext.CurrentState.HandleDownload ; + bucStateContext.CurrentState.HandleDownload; end; end; @@ -756,6 +829,12 @@ procedure UpdateAvailableState.HandleAbort; // 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 @@ procedure DownloadingState.HandleAbort; // 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 @@ procedure WaitingRestartState.HandleAbort; // 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 @@ procedure InstallingState.HandleAbort; 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 @@ procedure RetryState.HandleAbort; // 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 @@ procedure WaitingPostInstallState.HandleAbort; // 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 08/43] 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 fe2eb22a6c4..881ce6a7ebd 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -614,7 +614,6 @@ procedure TBackgroundUpdate.HandleAbort; function TBackgroundUpdate.CurrentStateName: string; begin - // Implement your logic here Result := CurrentState.StateName; end; @@ -675,33 +674,33 @@ procedure IdleState.HandleCheck; 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 @@ procedure UpdateAvailableState.Exit; procedure UpdateAvailableState.HandleCheck; begin - // Implement your logic here + end; procedure UpdateAvailableState.HandleDownload; @@ -743,22 +742,22 @@ function UpdateAvailableState.HandleKmShell; 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 @@ function DownloadingState.HandleKmShell; 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 @@ procedure WaitingRestartState.Exit; 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 @@ function WaitingRestartState.HandleKmShell; 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 @@ procedure InstallingState.Exit; 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 @@ function InstallingState.HandleKmShell; 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 @@ procedure InstallingState.HandleAbort; function InstallingState.StateName; begin - // Implement your logic here + Result := 'InstallingState'; end; @@ -1101,12 +1097,12 @@ procedure RetryState.Exit; 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 @@ function RetryState.HandleKmShell; 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 @@ procedure WaitingPostInstallState.HandleAbort; 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 09/43] 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 97e9b2bfb1b..2ed824ec0c5 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -18,28 +18,18 @@ function RecordKeymanStarted : Boolean; 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 881ce6a7ebd..2e96b6528d0 100644 --- a/windows/src/desktop/kmshell/main/BackgroundUpdate.pas +++ b/windows/src/desktop/kmshell/main/BackgroundUpdate.pas @@ -94,7 +94,7 @@ TState = class abstract 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 @@ IdleState = class(TState) 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 @@ UpdateAvailableState = class(TState) 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 @@ DownloadingState = class(TState) 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 @@ WaitingRestartState = class(TState) 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 @@ InstallingState = class(TState) 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 @@ RetryState = class(TState) 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 @@ WaitingPostInstallState = class(TState) 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 @@ TBackgroundUpdate = class tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - //function IsKeymanRunning: Boolean; function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; @@ -460,20 +459,6 @@ function TBackgroundUpdate.CheckRegistryState : TUpdateState; // I2329 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 @@ procedure TBackgroundUpdate.SetState(const Value: TStateClass); 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 @@ function TBackgroundUpdate.ConvertEnumState(const TEnumState: TUpdateState) : TS 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 @@ procedure TState.ChangeState(NewState: TStateClass); 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 @@ procedure IdleState.HandleCheck; 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 @@ procedure DownloadingState.Exit; procedure DownloadingState.HandleCheck; begin - // For now just pretend updated found + end; procedure DownloadingState.HandleDownload; @@ -805,13 +791,23 @@ function DownloadingState.HandleKmShell; // 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 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; 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 @@ procedure InstallingState.DoInstallKeyman; 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 @@ procedure InstallingState.Enter; 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 @@ procedure InstallingState.Enter; 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 @@ procedure RetryState.HandleDownload; function RetryState.HandleKmShell; begin - // TODO Implement retry + // #TODO: #10210 Implement retry Result := kmShellContinue end; @@ -1157,8 +1148,8 @@ procedure WaitingPostInstallState.HandleDownload; 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 @@ procedure WaitingPostInstallState.HandleMSIInstallComplete; 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 bee733e4b7d..f095341c822 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 @@ implementation 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe // 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 @@ function TDownloadUpdate.DownloadUpdates: Boolean; 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 @@ function TDownloadUpdate.CheckAllFilesDownloaded: Boolean; 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 @@ function TDownloadUpdate.CheckAllFilesDownloaded: Boolean; 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 10/43] 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 00000000000..4414a07c101 --- /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 00000000000..dce20b6ccef --- /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 2fa2307a339..c4cd8d6f222 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 11/43] 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 5eb0ee9013c..eb4cbaa4c2e 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 159a3fe88d2..64a6b83be17 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 12/43] feat(windows): rename files to new delphi pattern --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 12 ++++++------ .../kmshell/main/BackgroundUpdateStateDiagram.md | 11 +++++++++++ ...BackgroundUpdate.pas => Keyman.System.Update.pas} | 2 +- windows/src/desktop/kmshell/main/initprog.pas | 2 +- 5 files changed, 20 insertions(+), 9 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 5d19e25509b..1f1f618ec6c 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 d1e73fb3bd8..83a0827efb3 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,7 +357,7 @@ - + @@ -425,21 +425,21 @@ False - + kmshell.exe true - + - kmshell.exe + kmshell.rsm true - + - kmshell.rsm + kmshell.exe true diff --git a/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md b/windows/src/desktop/kmshell/main/BackgroundUpdateStateDiagram.md new file mode 100644 index 00000000000..59c735eeb2a --- /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 eafa8194ffe..fb878347830 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 d4d43874cc3..763cf11f990 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -144,7 +144,7 @@ implementation UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, - BackgroundUpdate, + Keyman.System.Update, KeyboardTIPCheck, 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 13/43] feat(windows): small refactor of sm wip --- windows/src/desktop/kmshell/kmshell.dpr | 2 +- windows/src/desktop/kmshell/kmshell.dproj | 12 +- .../main/Keyman.System.UpdateStateMachine.pas | 1302 +++++++++++++++++ windows/src/desktop/kmshell/main/initprog.pas | 6 +- 4 files changed, 1312 insertions(+), 10 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 1f1f618ec6c..805a79aa58a 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 83a0827efb3..c999a12d422 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -357,7 +357,7 @@ - + @@ -425,19 +425,19 @@ False - + - kmshell.exe + kmshell.rsm true - + - kmshell.rsm + kmshell.exe true - + kmshell.exe true 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 00000000000..9ae3df31956 --- /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 763cf11f990..284695bfd48 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -144,7 +144,7 @@ implementation UpgradeMnemonicLayout, utilfocusappwnd, utilkmshell, - Keyman.System.Update, + Keyman.System.UpdateStateMachine, KeyboardTIPCheck, @@ -387,7 +387,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 - BUpdateSM : TBackgroundUpdate; + BUpdateSM : TUpdateStateMachine; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -436,7 +436,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF end; // TODO: #10038 Will add this as part of the background update state machine // for now just verifing the download happens via -buc switch. - 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 14/43] 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 9ae3df31956..6d6eb9b4265 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 @@ TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) constructor Create(AParams: TUpdateStateMachineParams); function Params: TUpdateStateMachineParams; end; - + // Private Utility functions + function ConfigCheckContinue: Boolean; implementation uses @@ -335,7 +336,7 @@ implementation { TUpdateStateMachine } constructor TUpdateStateMachine.Create(AForce : Boolean); -var TSerailsedState : TUpdateState; +// var TSerailsedState : TUpdateState; // TODO: Remove begin inherited Create; @@ -448,7 +449,6 @@ function TUpdateStateMachine.CheckRegistryState : TUpdateState; // I2329 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 @@ procedure IdleState.HandleCheck; 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 @@ procedure IdleState.HandleDownload; 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 @@ procedure UpdateAvailableState.Enter; bucStateContext.SetRegistryState(usUpdateAvailable); if bucStateContext.FAuto then begin - bucStateContext.CurrentState.HandleDownload; + ChangeState(DownloadingState); end; end; @@ -787,14 +806,14 @@ procedure UpdateAvailableState.HandleCheck; 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 @@ procedure UpdateAvailableState.HandleInstallNow; 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 @@ function DownloadingState.StateName; function DownloadingState.DownloadUpdatesBackground: Boolean; var - i: Integer; DownloadBackGroundSavePath : String; DownloadResult : Boolean; DownloadUpdate: TDownloadUpdate; @@ -988,7 +999,7 @@ function WaitingRestartState.HandleKmShell; 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 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 @@ function PostInstallState.StateName; 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 5e61ff1f4d9..77fe071786d 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 @@ function UpdateState: Boolean; 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 15/43] 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 | 9 +++++---- 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, 36 insertions(+), 19 deletions(-) diff --git a/VERSION.md b/VERSION.md index 70957c1c68a..a9a1646b488 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 2787f8b3472..2756c63893d 100644 --- a/common/windows/delphi/general/klog.pas +++ b/common/windows/delphi/general/klog.pas @@ -23,7 +23,7 @@ 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 e40c30ad462..711bae6f806 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 ee141cc12d3..4b0bb489e52 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 ab0e47ff7db..905e4ac8eb6 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 45913bd470b..cfb0ad37fcf 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 c999a12d422..79061ffe4af 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -425,19 +425,19 @@ False - + - kmshell.rsm + kmshell.exe true - + kmshell.exe true - + kmshell.exe true @@ -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 2edcfbbcf70..9b5f779c8f1 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 b4e5e833502..487a818ddfc 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 16/43] 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 2ed824ec0c5..e146f8fa691 100644 --- a/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecuteHistory.pas @@ -22,7 +22,7 @@ function RecordKeymanStarted : Boolean; 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 @@ function HasKeymanRun : Boolean; 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 17/43] 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 fb878347830..00000000000 --- 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 6d6eb9b4265..589bca7582e 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 @@ interface Keyman.System.ExecuteHistory, UfrmDownloadProgress; +const + CheckPeriod: Integer = 7; // Days between checking for updates + type EUpdateStateMachine = class(Exception); @@ -93,10 +96,8 @@ TState = class abstract 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 @@ IdleState = class(TState) 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 @@ UpdateAvailableState = class(TState) 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 @@ DownloadingState = class(TState) 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 @@ WaitingRestartState = class(TState) 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 @@ InstallingState = class(TState) 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 @@ RetryState = class(TState) 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 @@ TUpdateStateMachine = class 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 @@ TUpdateStateMachine = class 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 @@ procedure TUpdateStateMachine.ShutDown; Application.Terminate; end; - { TOnlineUpdateSharedData } constructor TOnlineUpdateSharedData.Create(AParams: TUpdateStateMachineParams); @@ -417,31 +395,41 @@ function TOnlineUpdateSharedData.Params: TUpdateStateMachineParams; 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 @@ function TUpdateStateMachine.CheckRegistryState : TUpdateState; // I2329 // 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 @@ function TUpdateStateMachine.SetRegistryInstallMode (InstallMode : Boolean): Boo 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 @@ function TUpdateStateMachine.CheckRegistryInstallMode : Boolean; // 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 @@ function TUpdateStateMachine.CheckRegistryInstallMode : Boolean; Result := InstallMode; end; - function TUpdateStateMachine.CheckUpdateSchedule: Boolean; begin try @@ -639,21 +626,11 @@ function TUpdateStateMachine.HandleKmShell; 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 @@ function TUpdateStateMachine.CurrentStateName: string; Result := CurrentState.StateName; end; - - { State Class Memebers } constructor TState.Create(Context: TUpdateStateMachine); begin @@ -679,10 +654,11 @@ constructor TState.Create(Context: TUpdateStateMachine); 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 @@ procedure IdleState.HandleCheck; 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 @@ procedure IdleState.HandleCheck; // 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; - // else staty in idle state -end; - -procedure IdleState.HandleDownload; -begin + // if Result = wucSuccess then + // begin + // ChangeState(UpdateAvailableState); + // end; + // else staty in idle state 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 @@ procedure IdleState.HandleInstall; end; -procedure IdleState.HandleMSIInstallComplete; -begin - -end; - procedure IdleState.HandleAbort; begin @@ -804,16 +777,11 @@ procedure UpdateAvailableState.HandleCheck; 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 @@ procedure UpdateAvailableState.HandleInstall; end; -procedure UpdateAvailableState.HandleMSIInstallComplete; -begin - -end; - procedure UpdateAvailableState.HandleAbort; begin @@ -869,16 +832,14 @@ procedure DownloadingState.HandleCheck; 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 @@ procedure DownloadingState.HandleInstall; ChangeState(InstallingState); end; -procedure DownloadingState.HandleMSIInstallComplete; -begin - -end; - procedure DownloadingState.HandleAbort; begin end; @@ -926,7 +882,6 @@ function DownloadingState.StateName; Result := 'DownloadingState'; end; - function DownloadingState.DownloadUpdatesBackground: Boolean; var DownloadBackGroundSavePath : String; @@ -946,7 +901,6 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; // kmcom.Keyboards.Apply; // kmcom.Packages.Refresh; // end; - finally DownloadUpdate.Free; end; @@ -956,7 +910,8 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; procedure WaitingRestartState.Enter; begin - // Enter DownloadingState + // Enter WaitingRestartState + KL.Log('WaitingRestartState.HandleKmShell Enter'); bucStateContext.SetRegistryState(usWaitingRestart); end; @@ -970,11 +925,6 @@ procedure WaitingRestartState.HandleCheck; end; -procedure WaitingRestartState.HandleDownload; -begin - -end; - function WaitingRestartState.HandleKmShell; var SavedPath : String; @@ -985,7 +935,7 @@ function WaitingRestartState.HandleKmShell; 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 @@ procedure WaitingRestartState.HandleInstall; end; -procedure WaitingRestartState.HandleMSIInstallComplete; -begin - -end; - procedure WaitingRestartState.HandleAbort; begin @@ -1032,7 +977,6 @@ procedure WaitingRestartState.HandleInstallNow; bucStateContext.SetRegistryInstallMode(True); // Notify User to install ChangeState(InstallingState); - end; function WaitingRestartState.StateName; @@ -1042,7 +986,6 @@ function WaitingRestartState.StateName; end; { InstallingState } - function InstallingState.DoInstallPackage(Package: TUpdateStateMachineParamsPackage): Boolean; var FPackage: IKeymanPackageFile2; @@ -1136,16 +1079,11 @@ procedure InstallingState.Enter; end; procedure InstallingState.Exit; -begin - // Exit DownloadingState -end; - -procedure InstallingState.HandleCheck; begin end; -procedure InstallingState.HandleDownload; +procedure InstallingState.HandleCheck; begin end; @@ -1163,11 +1101,6 @@ procedure InstallingState.HandleInstall; end; -procedure InstallingState.HandleMSIInstallComplete; -begin - -end; - procedure InstallingState.HandleAbort; begin ChangeState(IdleState); @@ -1180,7 +1113,6 @@ procedure InstallingState.HandleInstallNow; function InstallingState.StateName; begin - Result := 'InstallingState'; end; @@ -1188,21 +1120,15 @@ function InstallingState.StateName; 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; +procedure RetryState.HandleCheck; begin end; @@ -1218,11 +1144,6 @@ procedure RetryState.HandleInstall; end; -procedure RetryState.HandleMSIInstallComplete; -begin - -end; - procedure RetryState.HandleAbort; begin @@ -1251,7 +1172,7 @@ procedure PostInstallState.Enter; procedure PostInstallState.Exit; begin - // Exit downloading state + end; procedure PostInstallState.HandleCheck; @@ -1259,11 +1180,6 @@ procedure PostInstallState.HandleCheck; // 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 @@ function ConfigCheckContinue: Boolean; 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 77fe071786d..4eea8adb592 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 @@ function UpdateState: Boolean; 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 18/43] 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 487a818ddfc..b963ae63be4 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 19/43] 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 | 224 +++++++----------- windows/src/desktop/kmshell/main/initprog.pas | 9 +- 2 files changed, 100 insertions(+), 133 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 589bca7582e..f7cfd9ef288 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 @@ TState = class abstract 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 @@ IdleState = class(TState) 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 @@ DownloadingState = class(TState) 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 @@ WaitingRestartState = class(TState) 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 @@ InstallingState = class(TState) 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 @@ RetryState = class(TState) 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 @@ PostInstallState = class(TState) 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 @@ TUpdateStateMachine = class 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 @@ TUpdateStateMachine = class 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 @@ destructor TUpdateStateMachine.Destroy; 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 @@ function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; 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; - 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 + ']'); + UpdateState := usIdle; // do we need a unknown state ? + KL.Log('CheckRegistryState State reg value not found default:[' + Registry.ReadString(SRegValue_Update_State) + ']'); end; - Result := True; finally - Free; + Registry.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; + Result := UpdateState; 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 @@ function TUpdateStateMachine.CheckUpdateSchedule: Boolean; 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 @@ function TUpdateStateMachine.HandleKmShell; Result := CurrentState.HandleKmShell; end; -procedure TUpdateStateMachine.HandleInstall; +procedure TUpdateStateMachine.HandleDownload; begin - CurrentState.HandleInstall; + CurrentState.HandleDownload; end; procedure TUpdateStateMachine.HandleAbort; @@ -732,9 +687,9 @@ function IdleState.HandleKmShell; Result := kmShellContinue; end; -procedure IdleState.HandleInstall; +procedure IdleState.HandleDownload; begin - + // Do Nothing end; procedure IdleState.HandleAbort; @@ -744,7 +699,6 @@ procedure IdleState.HandleAbort; 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 @@ function IdleState.StateName; { 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 @@ procedure UpdateAvailableState.HandleAbort; procedure UpdateAvailableState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); ChangeState(DownloadingState); end; @@ -815,27 +782,7 @@ procedure DownloadingState.Enter; 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'); -end; - -procedure DownloadingState.Exit; -begin - // Exit DownloadingState -end; - -procedure DownloadingState.HandleCheck; -begin - -end; - -function DownloadingState.HandleKmShell; -var DownloadResult : Boolean; -begin - {## 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} @@ -846,25 +793,41 @@ function DownloadingState.HandleKmShell; 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; +procedure DownloadingState.Exit; begin - ChangeState(InstallingState); + // Exit DownloadingState +end; + +procedure DownloadingState.HandleCheck; +begin + +end; + +function DownloadingState.HandleKmShell; +begin + // Downloading state, in other process, so continue + Result := kmShellContinue; +end; + +procedure DownloadingState.HandleDownload; +var DownloadResult, FResult : Boolean; +RootPath: string; +begin + // Enter Already Downloading + KL.Log('DownloadingState.HandleDownload already downloading'); end; procedure DownloadingState.HandleAbort; @@ -873,8 +836,7 @@ procedure DownloadingState.HandleAbort; procedure DownloadingState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); - // Continue downloading + end; function DownloadingState.StateName; @@ -962,7 +924,7 @@ function WaitingRestartState.HandleKmShell; end; end; -procedure WaitingRestartState.HandleInstall; +procedure WaitingRestartState.HandleDownload; begin end; @@ -974,8 +936,9 @@ procedure WaitingRestartState.HandleAbort; 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 @@ function InstallingState.HandleKmShell; Result := kmShellContinue; end; -procedure InstallingState.HandleInstall; +procedure InstallingState.HandleDownload; begin end; @@ -1139,7 +1102,7 @@ function RetryState.HandleKmShell; Result := kmShellContinue end; -procedure RetryState.HandleInstall; +procedure RetryState.HandleDownload; begin end; @@ -1151,7 +1114,6 @@ procedure RetryState.HandleAbort; procedure RetryState.HandleInstallNow; begin - bucStateContext.SetRegistryInstallMode(True); // TODO: #10038 handle retry counts ChangeState(InstallingState); end; @@ -1182,15 +1144,13 @@ procedure PostInstallState.HandleCheck; 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 284695bfd48..88d08ef4d17 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -83,6 +83,7 @@ procedure Main(Owner: TComponent = nil); fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, fmBackgroundUpdateCheck, + fmBackgroundDownload, fmApplyInstallNow, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 @@ -254,6 +255,7 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF 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 20/43] 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 c20c4baa0f2..efd60b4148d 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -178,9 +178,7 @@ interface 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 21/43] 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 2756c63893d..2787f8b3472 100644 --- a/common/windows/delphi/general/klog.pas +++ b/common/windows/delphi/general/klog.pas @@ -23,7 +23,7 @@ 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 711bae6f806..e40c30ad462 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 4b0bb489e52..ee141cc12d3 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 905e4ac8eb6..ab0e47ff7db 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 cfb0ad37fcf..45913bd470b 100644 --- a/windows/src/desktop/insthelp/insthelp.dproj +++ b/windows/src/desktop/insthelp/insthelp.dproj @@ -98,11 +98,6 @@ - - - - - Cfg_2 Base 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 22/43] 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 f7cfd9ef288..edcc649da44 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 @@ interface 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 @@ function WaitingRestartState.HandleKmShell; 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 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 4414a07c101..66d008bf2c5 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 dce20b6ccef..7b88ddbeab4 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -9,11 +9,10 @@ interface 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 23/43] 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 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 24/43] 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 824ce9dfc7c..62125399ff7 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 25/43] 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 efd60b4148d..0d1ba5484ee 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -162,7 +162,6 @@ interface 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 @@ interface 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 59c735eeb2a..160d54b2c10 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 5df7c2aa6a7..5f16aeb047e 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -847,8 +847,6 @@ procedure TfrmMain.Update_ApplyNow; 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 88d08ef4d17..79f1f60a60c 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -253,6 +253,8 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 4eea8adb592..6b0fe419b20 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 @@ function UpdateState: Boolean; 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 @@ function UpdateState: Boolean; 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 9f1dbc9b3f0..8fcce5c0145 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -78,11 +78,8 @@ procedure RunProgram; 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 26/43] 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 | 8 ++++---- .../main/Keyman.System.UpdateStateMachine.pas | 2 +- 4 files changed, 15 insertions(+), 11 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 e146f8fa691..08fd015c88d 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 @@ function RecordKeymanStarted : Boolean; 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 @@ function HasKeymanRun : Boolean; 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 805a79aa58a..13cf5a79dec 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 79061ffe4af..3715f946592 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -359,7 +359,7 @@ - +
Form1
dfm @@ -425,19 +425,19 @@ False - + kmshell.exe true - + kmshell.exe true - + kmshell.exe true diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index f7cfd9ef288..c91fdc858d8 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 @@ interface 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 27/43] 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 66d008bf2c5..71fcfe27379 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 7b88ddbeab4..419491ca1e5 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstall.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstall.pas @@ -11,6 +11,7 @@ interface TfrmStartInstall = class(TfrmKeymanBase) Install: TButton; Later: TButton; + InstallUpdate: TLabel; procedure InstallClick(Sender: TObject); procedure LaterClick(Sender: TObject); private 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 28/43] 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 0d1ba5484ee..7e7fe54c38e 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -313,8 +313,10 @@ interface 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 d0708d2b4c9..193e2c9f893 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 13cf5a79dec..d0915bd8656 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 3715f946592..6784e9c6cb7 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 d47927dab5c..fa8820553c1 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 @@ interface httpuploader, Keyman.System.UpdateCheckResponse, UfrmStartInstall, + UfrmStartInstallNow, Keyman.System.ExecutionHistory, UfrmDownloadProgress; @@ -215,7 +216,7 @@ PostInstallState = class(TState) TUpdateStateMachine = class private FForce: Boolean; - FAuto: Boolean; + FAutomaticUpdate: Boolean; FParams: TUpdateStateMachineParams; FErrorMessage: string; DownloadTempPath: string; @@ -247,6 +248,9 @@ TUpdateStateMachine = class 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 @@ constructor TUpdateStateMachine.Create(AForce : Boolean); 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 @@ function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 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 @@ procedure UpdateAvailableState.HandleCheck; 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 @@ procedure UpdateAvailableState.HandleAbort; 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 @@ procedure DownloadingState.Enter; 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 @@ procedure DownloadingState.HandleAbort; procedure DownloadingState.HandleInstallNow; begin - + // Already downloading set the registry apply now + bucStateContext.SetApplyNow(True); end; function DownloadingState.StateName; @@ -948,11 +1043,25 @@ procedure WaitingRestartState.HandleAbort; 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 007d854797b..e4cdff54bb3 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 @@ 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 @@ implementation 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 @@ function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates 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 00000000000..415796d5c82 --- /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 00000000000..c1d153199b3 --- /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 79f1f60a60c..d8c19b63099 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -658,6 +658,7 @@ function FirstRun(FQuery, FDisablePackages, FDefaultUILanguage: string): Boolean 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 62125399ff7..678a05d6900 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 341f036c6cd..0b654cfeec5 100644 --- a/windows/src/desktop/setup/RunTools.pas +++ b/windows/src/desktop/setup/RunTools.pas @@ -82,7 +82,7 @@ TRunTools = class 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 @@ TRunTools = class 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 @@ destructor TRunTools.Destroy; 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 @@ function TRunTools.DoInstall(Handle: THandle; Exit(False); end; - ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates, + ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates,AutomaticUpdates, StartDisabled,StartWithConfiguration,InstallDefaults,AutomaticallyReportUsage); Result := True; @@ -588,7 +588,7 @@ procedure TRunTools.WaitFor(hProcess: THandle; var Waiting, Cancelled: Boolean); 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 @@ procedure TRunTools.ConfigFirstRun(StartKeyman,StartWithWindows,CheckForUpdates, 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 293df6a6138..3374324d64d 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.pas +++ b/windows/src/desktop/setup/UfrmRunDesktop.pas @@ -113,6 +113,7 @@ TfrmRunDesktop = class(TForm) FCanUpgrade9: Boolean; FCanUpgrade10: Boolean; FCheckForUpdates: Boolean; + FAutomaticUpdates: Boolean; FStartAfterInstall: Boolean; FStartWithWindows: Boolean; FAutomaticallyReportUsage: Boolean; @@ -523,7 +524,7 @@ procedure TfrmRunDesktop.DoInstall(Silent, PromptForReboot: Boolean); // I3355 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 @@ procedure TfrmRunDesktop.GetDefaultSettings; // I2651 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 2cff0c4b734..dadcca01fc5 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 71cdb756d38..aa70b2ab7bd 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 8fcce5c0145..1b6ed25b302 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -47,7 +47,7 @@ implementation 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 ce1b8af33a8..ec979eb2151 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,14 +121,15 @@ TKeymanOptionInfo = record 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 27aef82ab55..07e0a85f7b4 100644 --- a/windows/src/global/delphi/general/KeymanOptionNames.pas +++ b/windows/src/global/delphi/general/KeymanOptionNames.pas @@ -7,6 +7,7 @@ interface // 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 29/43] feat(windows): changes update form now --- .../main/Keyman.System.UpdateStateMachine.pas | 63 ++++++++++++------- .../kmshell/main/UfrmStartInstallNow.dfm | 27 +++++--- .../kmshell/main/UfrmStartInstallNow.pas | 3 +- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index fa8820553c1..63afb7e4d2a 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 @@ procedure UpdateAvailableState.HandleAbort; procedure UpdateAvailableState.HandleInstallNow; 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 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; + if InstallNow = True then + begin + bucStateContext.SetApplyNow(True); + ChangeState(InstallingState) end; + end; function UpdateAvailableState.StateName; @@ -1043,24 +1054,28 @@ procedure WaitingRestartState.HandleAbort; 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 415796d5c82..ae93b5cd1e7 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 c1d153199b3..aee57e90964 100644 --- a/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas +++ b/windows/src/desktop/kmshell/main/UfrmStartInstallNow.pas @@ -11,7 +11,8 @@ interface 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 30/43] 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 08fd015c88d..4d3900659b6 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 f095341c822..dcc6e28faad 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 63afb7e4d2a..74c2433fa19 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 @@ implementation { 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 5f16aeb047e..0403df92751 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -844,7 +844,7 @@ procedure TfrmMain.Update_ApplyNow; 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 419491ca1e5..f242b863276 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 aee57e90964..6782a166a75 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 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 31/43] 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 a598c2b1bd9..a41e5425c0a 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 4d3900659b6..eb3bb11df3c 100644 --- a/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas +++ b/common/windows/delphi/general/Keyman.System.ExecutionHistory.pas @@ -39,6 +39,7 @@ function RecordKeymanStarted : Boolean; end; except on E: Exception do + // TODO: #10210 convert to sentry error KL.Log(E.ClassName + ': ' + E.Message); end; end; @@ -63,6 +64,7 @@ function HasKeymanRun : Boolean; 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 74c2433fa19..5cdc681692e 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 @@ TUpdateStateMachine = class 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 @@ constructor TUpdateStateMachine.Create(AForce : Boolean); 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 @@ constructor TUpdateStateMachine.Create(AForce : Boolean); 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 @@ destructor TUpdateStateMachine.Destroy; 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 @@ function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; 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 @@ function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 Result := UpdateState; end; -function TUpdateStateMachine.GetAutomaticUpdate: Boolean; // I2329 +function TUpdateStateMachine.GetAutomaticUpdates: Boolean; // I2329 var Registry: TRegistryErrorControlled; @@ -473,14 +472,9 @@ function TUpdateStateMachine.GetAutomaticUpdate: Boolean; // I2329 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 @@ function TUpdateStateMachine.SetApplyNow(Value : Boolean): Boolean; 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 @@ function TUpdateStateMachine.GetApplyNow: Boolean; 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 @@ constructor TState.Create(Context: TUpdateStateMachine); 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 @@ function IdleState.HandleKmShell; //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 @@ procedure UpdateAvailableState.StartDownloadProcess; 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 @@ procedure UpdateAvailableState.HandleInstallNow; 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 @@ procedure DownloadingState.HandleDownload; RootPath: string; begin // Enter Already Downloading - KL.Log('DownloadingState.HandleDownload already downloading'); end; procedure DownloadingState.HandleAbort; @@ -959,10 +949,8 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; 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 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; procedure WaitingRestartState.Enter; begin // Enter WaitingRestartState - KL.Log('WaitingRestartState.HandleKmShell Enter'); bucStateContext.SetRegistryState(usWaitingRestart); end; @@ -999,11 +986,9 @@ function WaitingRestartState.HandleKmShell; 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 @@ function WaitingRestartState.HandleKmShell; 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 @@ function WaitingRestartState.HandleKmShell; 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 @@ procedure WaitingRestartState.HandleInstallNow; if InstallNow = True then begin bucStateContext.SetApplyNow(True); - ChangeState(InstallingState) + ChangeState(InstallingState); end; end; @@ -1127,7 +1110,6 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 @@ procedure PostInstallState.HandleMSIInstallComplete; 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 71fcfe27379..010059e4418 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 f242b863276..2e54d140273 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 ae93b5cd1e7..289745efd5e 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 6782a166a75..2895502e8e0 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 @@ interface 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 3374324d64d..ded19ae75cd 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.pas +++ b/windows/src/desktop/setup/UfrmRunDesktop.pas @@ -1043,7 +1043,7 @@ procedure TfrmRunDesktop.GetDefaultSettings; // I2651 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 32/43] 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 678a05d6900..850f27930c0 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 33/43] feat(windows): rename UI forms to convention --- windows/src/desktop/kmshell/kmshell.dpr | 4 ++-- windows/src/desktop/kmshell/kmshell.dproj | 10 +++++----- windows/src/desktop/kmshell/kmshell.res | Bin 0 -> 7036 bytes ...Keyman.Configuration.UI.UfrmStartInstall.dfm} | 0 ...Keyman.Configuration.UI.UfrmStartInstall.pas} | 2 +- ...man.Configuration.UI.UfrmStartInstallNow.dfm} | 0 ...man.Configuration.UI.UfrmStartInstallNow.pas} | 2 +- .../main/Keyman.System.UpdateStateMachine.pas | 4 ++-- .../engine/kmcomapi/util/utilkeymanoption.pas | 2 +- 9 files changed, 12 insertions(+), 12 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 d0915bd8656..87f9fbf6bfc 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 6784e9c6cb7..5be47294005 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -360,11 +360,11 @@ - +
frmInstallNow
dfm
- +
frmStartInstall
dfm
@@ -429,19 +429,19 @@ False - + kmshell.exe true - + kmshell.exe true - + kmshell.exe true 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 2e54d140273..6628ea570e7 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 2895502e8e0..9603dec5d63 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 5cdc681692e..3e22b1f7fea 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 @@ interface 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 5ceafbc07e4..8a18f3be262 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,7 +121,7 @@ TKeymanOptionInfo = record 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 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 34/43] 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 eb3bb11df3c..52fda002dcb 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 511f36e6d95..5bcbb967927 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -317,7 +317,7 @@ interface 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 bbb79c88692..692a582dd72 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 6628ea570e7..7e2de18a039 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 9603dec5d63..e5d0c2e5015 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 3e22b1f7fea..263702c37a5 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 @@ interface 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 @@ TUpdateStateMachineDownloadParams = record 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 @@ TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) 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 @@ implementation Upload_Settings, utildir, utilexecute, - OnlineUpdateCheckMessages, // todo create own messages + OnlineUpdateCheckMessages, // TODO-WINDOWS-UPDATES: create own messages UfrmOnlineUpdateIcon, UfrmOnlineUpdateNewVersion, utilsystem, @@ -372,10 +363,16 @@ procedure TUpdateStateMachine.SavePackageUpgradesToDownloadTempPath; 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 @@ function TUpdateStateMachine.CheckUpdateSchedule: Boolean; 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 @@ function ConfigCheckContinue: Boolean; 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 35/43] 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 7e2de18a039..dddcedb668e 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 @@ 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 dcc6e28faad..aa4304d7e8f 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 @@ interface 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 @@ implementation 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe // 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 @@ function TDownloadUpdate.CheckAllFilesDownloaded: Boolean; 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 b1738bad936..ba3e8b62eb4 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 @@ function ConfigCheckContinue: Boolean; 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 @@ function ConfigCheckContinue: Boolean; 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 6b0fe419b20..31846bf8e1e 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 @@ function EnginePostInstall(hInstall: MSIHANDLE): UINT; 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 36/43] 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 010059e4418..9084afe1b69 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 dddcedb668e..8571c0b5158 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 @@ TfrmStartInstall = class(TfrmKeymanBase) 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 289745efd5e..eae54930403 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 e5d0c2e5015..5f9c9caefc7 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 @@ TfrmStartInstallNow = class(TfrmKeymanBase) 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 aa4304d7e8f..57c8a2cad06 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 263702c37a5..ea19ab70662 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 @@ interface UfrmDownloadProgress, utilkmshell; -const - CheckPeriod: Integer = 7; // Days between checking for updates - type EUpdateStateMachine = class(Exception); @@ -234,7 +231,6 @@ TUpdateStateMachine = class tempPath. } procedure SavePackageUpgradesToDownloadTempPath; - function checkUpdateSchedule : Boolean; function SetRegistryState (Update : TUpdateState): Boolean; function GetAutomaticUpdates: Boolean; @@ -273,9 +269,6 @@ TOnlineUpdateSharedData = class(TInterfacedObject, IOnlineUpdateSharedData) function Params: TUpdateStateMachineParams; end; -// Private Utility functions -function ConfigCheckContinue: Boolean; - implementation uses @@ -521,43 +514,6 @@ function TUpdateStateMachine.GetApplyNow: Boolean; 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 @@ function PostInstallState.StateName; 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 31846bf8e1e..4871d939764 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 @@ function UpdateState: Boolean; 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 @@ function UpdateState: Boolean; end else begin - // error log + // TODO-WINDOWS-UPDATES: error log end; finally RegCloseKey(hk); @@ -53,7 +54,7 @@ function UpdateState: Boolean; 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 37/43] 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 ea19ab70662..3328b76fac3 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 @@ TUpdateStateMachine = class 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 @@ constructor TUpdateStateMachine.Create(AForce : Boolean); 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 @@ destructor TUpdateStateMachine.Destroy; 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 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 @@ procedure TUpdateStateMachine.SetState(const Value: TStateClass); CurrentState.Exit; end; - SetStateOnly(Value); + SetStateOnly(ConvertStateToEnum(Value)); if Assigned(CurrentState) then begin @@ -539,52 +537,29 @@ procedure TUpdateStateMachine.SetState(const Value: TStateClass); 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 38/43] 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 52fda002dcb..33dff407802 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 - begin - if GetLastError <> ERROR_SUCCESS then - RaiseLastOSError; - Result := True; - end - else + Result := GlobalFindAtom(AtomName) <> 0; + if not Result then + begin + if GetLastError <> ERROR_FILE_NOT_FOUND then 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 5bcbb967927..bacca938c66 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -175,10 +175,10 @@ interface 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 @@ interface 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 39/43] feat(windows): add error checking - private class --- .../main/Keyman.System.DownloadUpdate.pas | 115 +-- .../main/Keyman.System.UpdateStateMachine.pas | 735 +++++++----------- 2 files changed, 326 insertions(+), 524 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index 57c8a2cad06..9a3a27995b6 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 @@ TDownloadUpdate = class 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 @@ constructor TDownloadUpdate.Create; 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe FDownload.TotalSize := 0; FDownload.TotalDownloads := 0; - downloadCount := 0; // 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; - 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); + 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 @@ function TDownloadUpdate.DownloadUpdates: Boolean; 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 3328b76fac3..eaf6b50bd83 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 @@ TState = class abstract 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; + + { 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; - // For convenience - function StateName: string; virtual; abstract; + 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 @@ UpdateAvailableState = class(TState) 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 @@ DownloadingState = class(TState) 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 @@ WaitingRestartState = class(TState) 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 @@ RetryState = class(TState) 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 @@ destructor TUpdateStateMachine.Destroy; 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; -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; +function TUpdateStateMachine.SetRegistryState(Update: TUpdateState): Boolean; var - UpdateStr : string; + UpdateStr: string; Registry: TRegistryErrorControlled; begin Result := False; @@ -423,26 +302,39 @@ function TUpdateStateMachine.SetRegistryState(Update : TUpdateState): Boolean; 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 - begin - // TODO-WINDOWS-UPDATES Check if value in register is valid - UpdateState := TUpdateState(GetEnumValue(TypeInfo(TUpdateState), Registry.ReadString(SRegValue_Update_State))); - end - else + if Registry.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + Registry.ValueExists(SRegValue_Update_State) then 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; // I2329 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 @@ function TUpdateStateMachine.GetApplyNow: Boolean; 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 @@ function TUpdateStateMachine.GetApplyNow: Boolean; 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 @@ procedure TUpdateStateMachine.SetState(const Value: TStateClass); 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 @@ procedure TUpdateStateMachine.SetStateOnly(const enumState: TUpdateState); 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 @@ function TUpdateStateMachine.ConvertStateToEnum(const StateClass: TStateClass): 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 @@ procedure IdleState.HandleCheck; 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 @@ procedure IdleState.HandleCheck; // 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 @@ function IdleState.HandleKmShell; procedure IdleState.HandleDownload; begin - // Do Nothing + // Do Nothing end; procedure IdleState.HandleAbort; @@ -686,21 +617,15 @@ procedure IdleState.HandleAbort; 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 @@ function UpdateAvailableState.HandleKmShell; procedure UpdateAvailableState.HandleDownload; begin - ChangeState(DownloadingState); + ChangeState(DownloadingState); end; procedure UpdateAvailableState.HandleAbort; @@ -753,8 +678,8 @@ procedure UpdateAvailableState.HandleAbort; procedure UpdateAvailableState.HandleInstallNow; var - frmStartInstallNow : TfrmStartInstallNow; - InstallNow : Boolean; + frmStartInstallNow: TfrmStartInstallNow; + InstallNow: Boolean; begin InstallNow := True; @@ -762,7 +687,7 @@ procedure UpdateAvailableState.HandleInstallNow; 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 @@ procedure UpdateAvailableState.HandleInstallNow; 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 @@ function DownloadingState.HandleKmShell; end; procedure DownloadingState.HandleDownload; -var DownloadResult, FResult : Boolean; -RootPath: string; begin // Enter Already Downloading end; @@ -859,28 +777,22 @@ procedure DownloadingState.HandleInstallNow; 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 @@ procedure WaitingRestartState.HandleCheck; 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 @@ function WaitingRestartState.HandleKmShell; 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 @@ procedure WaitingRestartState.HandleAbort; 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 @@ procedure WaitingRestartState.HandleInstallNow; 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 @@ function InstallingState.DoInstallKeyman(SavePath: string) : Boolean; 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 @@ procedure InstallingState.HandleInstallNow; // 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 @@ procedure RetryState.HandleInstallNow; ChangeState(InstallingState); end; -function RetryState.StateName; -begin - - Result := 'RetryState'; -end; - { PostInstallState } procedure PostInstallState.Enter; @@ -1198,18 +1064,19 @@ procedure PostInstallState.HandleDownload; 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 @@ procedure PostInstallState.HandleInstallNow; // 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 40/43] 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 9a3a27995b6..373ba513e5f 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 @@ interface KeymanPaths, OnlineUpdateCheck; -const - DaysBetweenCheckingForUpdates: Integer = 7; // Days between checking for updates - type TDownloadUpdateParams = record TotalSize: Integer; @@ -170,17 +167,13 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe 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 4871d939764..56d33c83392 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 @@ function UpdateState: Boolean; 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 41/43] 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 373ba513e5f..f91c8f6ea4d 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 @@ TDownloadUpdate = class 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 @@ destructor TDownloadUpdate.Destroy; 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 @@ procedure TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateChe 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 ba3e8b62eb4..9e4b0b007e2 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 @@ TRemoteUpdateCheck = class 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 @@ destructor TRemoteUpdateCheck.Destroy; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; // Due to limitations in PHP parsing of query string parameters names with // space or period, we need to split the parameters up. The legacy pattern // is still supported on the server side. Relates to #4886. - 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 @@ function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult; 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 eaf6b50bd83..a3c262d2bc1 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 @@ implementation constructor TState.Create(Context: TUpdateStateMachine); begin + inherited Create; bucStateContext := Context; end; @@ -256,7 +258,7 @@ destructor TUpdateStateMachine.Destroy; 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 @@ function TUpdateStateMachine.SetRegistryState(Update: TUpdateState): Boolean; 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 @@ function TUpdateStateMachine.CheckRegistryState: TUpdateState; 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 @@ function TUpdateStateMachine.GetAutomaticUpdates: Boolean; // I2329 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 @@ function TUpdateStateMachine.SetApplyNow(Value: Boolean): Boolean; 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 @@ function TUpdateStateMachine.GetApplyNow: Boolean; 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 @@ procedure IdleState.HandleCheck; 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 @@ function DownloadingState.DownloadUpdatesBackground: Boolean; 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 @@ function WaitingRestartState.HandleKmShell; 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 d8c19b63099..065d26802a0 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -438,8 +438,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF ShowMessage(MsgFromId(SKOSNotSupported)); Exit; end; - // TODO: #10038 Will add this as part of the background update state machine - // for now just verifing the download happens via -buc switch. + 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 42/43] 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 56d33c83392..6c756bbbac2 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 @@ function ReportFailure(hInstall: MSIHANDLE; const func: string; code: UINT): UIN 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 @@ function UpdateState: Boolean; 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 43/43] 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 0403df92751..cb206e0291b 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -797,7 +797,7 @@ procedure TfrmMain.Support_ProxyConfig; 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 @@ procedure TfrmMain.Update_CheckNow; 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 065d26802a0..b5a84792ce6 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -253,7 +253,7 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 1b6ed25b302..79db9c8baa2 100644 --- a/windows/src/engine/keyman/main.pas +++ b/windows/src/engine/keyman/main.pas @@ -40,14 +40,14 @@ implementation 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;