diff --git a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas index 918c3964119..adc90ab1520 100644 --- a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas +++ b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas @@ -35,6 +35,7 @@ TUpdateCheckResponsePackage = record TUpdateCheckResponse = record private + FOriginalData: string; FInstallSize: Int64; FInstallURL: string; FNewVersion: string; @@ -46,9 +47,13 @@ TUpdateCheckResponse = record FFileName: string; function ParseKeyboards(nodes: TJSONObject): Boolean; function ParseLanguages(i: Integer; v: TJSONValue): Boolean; + function DoParse(const message, app, currentVersion: string): Boolean; public function Parse(const message: AnsiString; const app, currentVersion: string): Boolean; + procedure SaveToFile(const Filename: string); + function LoadFromFile(const Filename, app, currentVersion: string): Boolean; + property CurrentVersion: string read FCurrentVersion; property NewVersion: string read FNewVersion; property NewVersionWithTag: string read FNewVersionWithTag; @@ -58,25 +63,32 @@ TUpdateCheckResponse = record property ErrorMessage: string read FErrorMessage; property Status: TUpdateCheckResponseStatus read FStatus; property Packages: TUpdateCheckResponsePackages read FPackages; + property OriginalData: string read FOriginalData; end; implementation uses + System.Classes, System.Generics.Collections, versioninfo; { TUpdateCheckResponse } function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean; +begin + Result := DoParse(string(UTF8String(message)), app, currentVersion); +end; + +function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean; var node, doc: TJSONObject; begin + FOriginalData := message; FCurrentVersion := currentVersion; FStatus := ucrsNoUpdate; - // TODO: test with UTF8 characters in response - doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject; + doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject; if doc = nil then begin FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]); @@ -168,4 +180,29 @@ function TUpdateCheckResponse.ParseLanguages(i: Integer; v: TJSONValue): Boolean Result := True; end; +function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean; +var + ss: TStringStream; +begin + ss := TStringStream.Create('', TEncoding.UTF8); + try + ss.LoadFromFile(Filename); + Result := DoParse(ss.DataString, app, currentVersion); + finally + ss.Free; + end; +end; + +procedure TUpdateCheckResponse.SaveToFile(const Filename: string); +var + ss: TStringStream; +begin + ss := TStringStream.Create(FOriginalData, TEncoding.UTF8); + try + ss.SaveToFile(Filename); + finally + ss.Free; + end; +end; + end. diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index bc534b9f526..4d43d8a52ed 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -17,6 +17,8 @@ TKeymanPaths = class const S_CEF_SubProcess = 'kmbrowserhost.exe'; const S_CEF_SubProcess_Developer = 'kmdbrowserhost.exe'; const S_CustomisationFilename = 'desktop_pro.pxx'; + + const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\'; public const S_KMShell = 'kmshell.exe'; const S_TSysInfoExe = 'tsysinfo.exe'; @@ -27,8 +29,10 @@ TKeymanPaths = class const S_FallbackKeyboardPath = 'Keyboards\'; const S__Package = '_Package\'; const S_MCompileExe = 'mcompile.exe'; + const S_UpdateCache_Metadata = 'cache.json'; class function ErrorLogPath(const app: string = ''): string; static; class function KeymanHelpPath(const HelpFile: string): string; static; + class function KeymanUpdateCachePath(const filename: string = ''): string; static; class function KeymanDesktopInstallPath(const filename: string = ''): string; static; class function KeymanEngineInstallPath(const filename: string = ''): string; static; class function KeymanDesktopInstallDir: string; static; @@ -423,6 +427,11 @@ class function TKeymanPaths.KeymanHelpPath(const HelpFile: string): string; Result := ''; end; +class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string; +begin + Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename; +end; + class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean; begin // On developer machines, if we are running within the source repo, then use diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 3726d81cd2d..829006912a4 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -177,7 +177,10 @@ uses Keyman.Configuration.System.HttpServer.App.TextEditorFonts in 'startup\help\Keyman.Configuration.System.HttpServer.App.TextEditorFonts.pas', Keyman.Configuration.System.HttpServer.App.Locale in 'web\Keyman.Configuration.System.HttpServer.App.Locale.pas', Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', - Keyman.Configuration.System.Main in 'main\Keyman.Configuration.System.Main.pas'; + Keyman.Configuration.System.Main in 'main\Keyman.Configuration.System.Main.pas', + UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', + Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas', + Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas'; {$R VERSION.RES} {$R manifest.res} diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index d5d3922e2a0..45c71ffb03f 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -354,6 +354,9 @@ + + + Cfg_2 @@ -415,9 +418,9 @@ False - + - kmshell.rsm + kmshell.exe true @@ -427,9 +430,9 @@ true - + - .\ + kmshell.rsm true diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas new file mode 100644 index 00000000000..0eebd52ff93 --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -0,0 +1,378 @@ +{ + * Keyman is copyright (C) SIL International. MIT License. + * + * Keyman.System.RemoteUpdateCheck: Checks for keyboard package and Keyman + for Windows updates. +} +unit Keyman.System.RemoteUpdateCheck; // I3306 + +interface + +uses + System.Classes, + System.SysUtils, + KeymanPaths, + httpuploader, + Keyman.System.UpdateCheckResponse, + OnlineUpdateCheck; + +type + ERemoteUpdateCheck = class(Exception); + + TRemoteUpdateCheckResult = (wucUnknown, wucSuccess, wucNoUpdates, wucFailure, wucOffline); + + TRemoteUpdateCheckDownloadParams = record + TotalSize: Integer; + TotalDownloads: Integer; + StartPosition: Integer; + end; + + 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); + function DoRun: TRemoteUpdateCheckResult; + public + + constructor Create(AForce : Boolean; ACheckOnly: Boolean = False); + destructor Destroy; override; + function Run: TRemoteUpdateCheckResult; + property ShowErrors: Boolean read FShowErrors write FShowErrors; + end; + +procedure LogMessage(LogMessage: string); + +implementation + +uses + System.WideStrUtils, + Winapi.Windows, + Winapi.WinINet, + + GlobalProxySettings, + KLog, + keymanapi_TLB, + KeymanVersion, + Keyman.System.UpdateCheckStorage, + kmint, + ErrorControlledRegistry, + RegistryKeys, + Upload_Settings, + + OnlineUpdateCheckMessages; + +{ TRemoteUpdateCheck } + +constructor TRemoteUpdateCheck.Create(AForce, ACheckOnly: Boolean); +begin + inherited Create; + + FShowErrors := True; + FRemoteResult := wucUnknown; + + FForce := AForce; + FCheckOnly := ACheckOnly; + + KL.Log('TRemoteUpdateCheck.Create'); +end; + +destructor TRemoteUpdateCheck.Destroy; +begin + if (FErrorMessage <> '') and FShowErrors then + LogMessage(FErrorMessage); + + KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = '+FErrorMessage); + KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = '+IntToStr(Ord(FRemoteResult))); + + inherited Destroy; +end; + +function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult; +begin + Result := DoRun; + + if Result in [ wucSuccess] then + begin + kmcom.Keyboards.Refresh; + kmcom.Keyboards.Apply; + kmcom.Packages.Refresh; + end; + + 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; +begin + {FProxyHost := ''; + FProxyPort := 0;} + + { Check if user is currently online } + if not InternetGetConnectedState(@flags, 0) then + begin + Result := wucOffline; + Exit; + end; + + { Verify that it has been at least 7 days since last update check } + try + 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) < 7) 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 + begin + Result := wucFailure; + FErrorMessage := E.Message; + Exit; + end; + end; + + Result := wucNoUpdates; + + try + 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'); + + for i := 0 to kmcom.Packages.Count - 1 do + begin + pkg := kmcom.Packages[i]; + + // Due to limitations in PHP parsing of query string parameters names with + // space or period, we need to split the parameters up. The legacy pattern + // is still supported on the server side. Relates to #4886. + http.Fields.Add(AnsiString('packageid_'+IntToStr(i)), AnsiString(pkg.ID)); + http.Fields.Add(AnsiString('packageversion_'+IntToStr(i)), AnsiString(pkg.Version)); + pkg := nil; + end; + + http.Proxy.Server := GetProxySettings.Server; + http.Proxy.Port := GetProxySettings.Port; + http.Proxy.Username := GetProxySettings.Username; + http.Proxy.Password := GetProxySettings.Password; + + http.Request.HostName := API_Server; + http.Request.Protocol := API_Protocol; + http.Request.UrlPath := API_Path_UpdateCheck_Windows; + //OnStatus := + http.Upload; + if http.Response.StatusCode = 200 then + 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; + end + else + begin + FErrorMessage := ucr.ErrorMessage; + Result := wucFailure; + end; + end + else + raise ERemoteUpdateCheck.Create('Error '+IntToStr(http.Response.StatusCode)); + finally + http.Free; + end; + except + on E:EHTTPUploader do + begin + if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) + then FErrorMessage := S_OnlineUpdate_UnableToContact + else FErrorMessage := WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message]); + Result := wucFailure; + end; + on E:Exception do + begin + FErrorMessage := E.Message; + Result := wucFailure; + end; + end; + + registry := TRegistryErrorControlled.Create; // I2890 + try + if registry.OpenKey(SRegKey_KeymanDesktop_CU, True) then + registry.WriteDateTime(SRegValue_LastUpdateCheckTime, Now); + finally + 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; + +end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas new file mode 100644 index 00000000000..f1a156bfa06 --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -0,0 +1,52 @@ +unit Keyman.System.UpdateCheckStorage; + +interface + +uses + Keyman.System.UpdateCheckResponse; + +type + TUpdateCheckStorage = class sealed + private + class function MetadataFilename: string; static; + public + class function HasUpdates: Boolean; static; + class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static; + class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static; + end; + +implementation + +uses + System.SysUtils, + + KeymanPaths, + KeymanVersion; + +{ TUpdateCheckStorage } + +class function TUpdateCheckStorage.MetadataFilename: string; +begin + Result := TKeymanPaths.KeymanUpdateCachePath(TKeymanPaths.S_UpdateCache_Metadata); +end; + +class procedure TUpdateCheckStorage.SaveUpdateCacheData( + const data: TUpdateCheckResponse); +begin + ForceDirectories(TKeymanPaths.KeymanUpdateCachePath); + data.SaveToFile(MetadataFilename); +end; + +class function TUpdateCheckStorage.HasUpdates: Boolean; +begin + Result := FileExists(MetadataFilename); +end; + +class function TUpdateCheckStorage.LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; +begin + Result := + HasUpdates and + data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version); +end; + +end. diff --git a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas index 15fb06f4e96..c202742813f 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -98,6 +98,7 @@ TOnlineUpdateCheck = class FShowErrors: Boolean; FDownload: TOnlineUpdateCheckDownloadParams; + FCheckOnly: Boolean; function DownloadUpdates: Boolean; procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean); @@ -112,7 +113,9 @@ TOnlineUpdateCheck = class public public - constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean); + function ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; + + constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean = False); destructor Destroy; override; function Run: TOnlineUpdateCheckResult; property ShowErrors: Boolean read FShowErrors write FShowErrors; @@ -146,6 +149,7 @@ implementation KLog, keymanapi_TLB, KeymanVersion, + Keyman.System.UpdateCheckStorage, kmint, ErrorControlledRegistry, RegistryKeys, @@ -165,7 +169,7 @@ implementation { TOnlineUpdateCheck } -constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean); +constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean); begin inherited Create; @@ -176,6 +180,7 @@ constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Bool FSilent := ASilent; FForce := AForce; + FCheckOnly := ACheckOnly; KL.Log('TOnlineUpdateCheck.Create'); end; @@ -477,10 +482,9 @@ procedure TOnlineUpdateCheck.ShutDown; function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; var flags: DWord; - i, n: Integer; - pkg: IKeymanPackage; - j: Integer; + i: Integer; ucr: TUpdateCheckResponse; + pkg: IKeymanPackage; begin {FProxyHost := ''; FProxyPort := 0;} @@ -567,45 +571,15 @@ function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; begin if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin - SetLength(FParams.Packages,0); - for i := Low(ucr.Packages) to High(ucr.Packages) do - begin - n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); - if n >= 0 then - begin - pkg := kmcom.Packages[n]; - j := Length(FParams.Packages); - SetLength(FParams.Packages, j+1); - FParams.Packages[j].NewID := ucr.Packages[i].NewID; - FParams.Packages[j].ID := ucr.Packages[i].ID; - FParams.Packages[j].Description := ucr.Packages[i].Name; - FParams.Packages[j].OldVersion := pkg.Version; - FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; - FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; - FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; - FParams.Packages[j].FileName := ucr.Packages[i].FileName; - pkg := nil; - end - else - FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; - end; + ResponseToParams(ucr); - case ucr.Status of - ucrsNoUpdate: - begin - FErrorMessage := ucr.ErrorMessage; - end; - ucrsUpdateReady: - begin - FParams.Keyman.OldVersion := ucr.CurrentVersion; - FParams.Keyman.NewVersion := ucr.NewVersion; - FParams.Keyman.DownloadURL := ucr.InstallURL; - FParams.Keyman.DownloadSize := ucr.InstallSize; - FParams.Keyman.FileName := ucr.FileName; - end; - end; - - if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + if FCheckOnly then + begin + // TODO: Refactor this + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := FParams.Result; + end + else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then begin if not FSilent then ShowUpdateForm @@ -651,6 +625,52 @@ function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; end; end; +function TOnlineUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; +var + i, j, n: Integer; + pkg: IKeymanPackage; +begin + SetLength(FParams.Packages,0); + for i := Low(ucr.Packages) to High(ucr.Packages) do + begin + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + begin + pkg := kmcom.Packages[n]; + j := Length(FParams.Packages); + SetLength(FParams.Packages, j+1); + FParams.Packages[j].NewID := ucr.Packages[i].NewID; + FParams.Packages[j].ID := ucr.Packages[i].ID; + FParams.Packages[j].Description := ucr.Packages[i].Name; + FParams.Packages[j].OldVersion := pkg.Version; + FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; + FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; + FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; + FParams.Packages[j].FileName := ucr.Packages[i].FileName; + pkg := nil; + end + else + FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; + end; + + case ucr.Status of + ucrsNoUpdate: + begin + FErrorMessage := ucr.ErrorMessage; + end; + ucrsUpdateReady: + begin + FParams.Keyman.OldVersion := ucr.CurrentVersion; + FParams.Keyman.NewVersion := ucr.NewVersion; + FParams.Keyman.DownloadURL := ucr.InstallURL; + FParams.Keyman.DownloadSize := ucr.InstallSize; + FParams.Keyman.FileName := ucr.FileName; + end; + end; + + Result := FParams; +end; + procedure OnlineUpdateAdmin(OwnerForm: TCustomForm; Path: string); var Package: TOnlineUpdateCheckParamsPackage; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index d9a6f8b4417..a779fdcdd2b 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -148,6 +148,7 @@ TfrmMain = class(TfrmWebContainer) procedure OpenSite(params: TStringList); procedure DoApply; procedure DoRefresh; + procedure Update_CheckNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -202,6 +203,7 @@ implementation UfrmTextEditor, uninstall, Upload_Settings, + UpdateXMLRenderer, utildir, utilexecute, utilkmshell, @@ -301,6 +303,7 @@ procedure TfrmMain.Do_Content_Render; FXMLRenderers.Add(TOptionsXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TLanguagesXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TSupportXMLRenderer.Create(FXMLRenderers)); + FXMLRenderers.Add(TUpdateXMLRenderer.Create(FXMLRenderers)); xml := FXMLRenderers.RenderToString(s); sharedData.Init( @@ -345,6 +348,8 @@ procedure TfrmMain.FireCommand(const command: WideString; params: TStringList); else if command = 'support_updatecheck' then Support_UpdateCheck else if command = 'support_proxyconfig' then Support_ProxyConfig + else if command = 'update_checknow' then Update_CheckNow + else if command = 'contact_support' then Support_ContactSupport(params) // I4390 else if command = 'opensite' then OpenSite(params) @@ -815,6 +820,17 @@ procedure TfrmMain.Support_UpdateCheck; end; end; +procedure TfrmMain.Update_CheckNow; +begin + with TOnlineUpdateCheck.Create(Self, True, True, True) do + try + Run; + finally + Free; + end; + DoRefresh; +end; + procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index c22591e79c9..4076ab21015 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -82,6 +82,7 @@ procedure Main(Owner: TComponent = nil); fmMigrate, fmSplash, fmStart, fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548 fmOnlineUpdateAdmin, fmTextEditor, + fmBackgroundUpdateCheck, fmFirstRun, // I2562 fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 @@ -120,6 +121,7 @@ implementation KMShellHints, KeymanMutex, OnlineUpdateCheck, + Keyman.System.RemoteUpdateCheck, RegistryKeys, UfrmBaseKeyboard, UfrmKeymanBase, @@ -249,6 +251,7 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, else if s = '-h' then FMode := fmHelp else if s = '-t' then FMode := fmTextEditor else if s = '-ouc' then FMode := fmOnlineUpdateCheck + else if s = '-buc' then FMode := fmBackgroundUpdateCheck else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169 else if s = '-nowelcome' then FNoWelcome := True else if s = '-kw' then FMode := fmKeyboardWelcome // I2569 @@ -381,6 +384,7 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 + RemoteUpdateCheck: TRemoteUpdateCheck; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -427,6 +431,19 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF ShowMessage(MsgFromId(SKOSNotSupported)); Exit; end; + // TODO: #10038 Will add this as part of the background update state machine + // for now just verifing the download happens via -buc switch. + RemoteUpdateCheck := TRemoteUpdateCheck.Create(False, False); + try + if (FMode = fmBackgroundUpdateCheck) then + begin + RemoteUpdateCheck.Run; + Exit; + end + finally + RemoteUpdateCheck.Free; + end; + if not FSilent or (FMode = fmUpgradeMnemonicLayout) then // I4553 begin diff --git a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas new file mode 100644 index 00000000000..32fd7b2cf67 --- /dev/null +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -0,0 +1,93 @@ +(* + Name: UpdateXMLRenderer + Copyright: Copyright (C) SIL International. +*) +unit UpdateXMLRenderer; + +interface + +uses + XMLRenderer, + Windows; + +type + TUpdateXMLRenderer = class(TXMLRenderer) + protected + function XMLData: WideString; override; + end; + +implementation + +uses + StrUtils, + SysUtils, + VersionInfo, + kmint, + KeymanVersion, + keymanapi_TLB, + Keyman.System.LocaleStrings, + Keyman.System.UpdateCheckResponse, + Keyman.System.UpdateCheckStorage, + MessageIdentifierConsts, + MessageIdentifiers, + utilxml; + +{ TUpdateXMLRenderer } + +function TUpdateXMLRenderer.XMLData: WideString; +var + xml: string; + ucr: TUpdateCheckResponse; + i, n : Integer; + pkg: IKeymanPackage; +begin + xml := ''; + + if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then + begin + if (ucr.InstallURL <> '') then + begin + xml := xml + + ''+ + '0'+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [ucr.NewVersion]))+''+ + ''+xmlencode(ucr.NewVersion)+''+ + ''+xmlencode(ucr.CurrentVersion)+''+ + ''+xmlencode(Format('%d', [ucr.InstallSize div 1024]))+'KB'+ + ''+xmlencode(ucr.InstallURL)+''+ + ''+ + ''; + end; + + for i := 0 to High(ucr.Packages) do + begin + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + pkg := kmcom.Packages[n]; + begin + xml := xml + + ''+ + ''+IntToStr(i+1)+''+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText, + [ucr.Packages[i].Name, ucr.Packages[i].NewVersion]))+''+ + ''+xmlencode(ucr.Packages[i].NewVersion)+''+ + ''+xmlencode(pkg.version)+''+ + ''+xmlencode(Format('%d', [ucr.Packages[i].DownloadSize div 1024]))+'KB'+ + ''+xmlencode(ucr.Packages[i].DownloadURL)+''+ + ''+ + ''; + pkg := nil; + end; + // else Package not found, skip + end; + end; + + Result := ''+xml+''; +end; + +end. + diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index 851964112bf..4a433082a12 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -1093,19 +1093,11 @@ th height: 16px; } -#keepintouch_content { +#update_content { height: 100%; overflow: hidden; } -#keepintouch_frame { - box-sizing: border-box; - width:100%; - height:100%; - border:none; - user-select: none; -} - /* QRCodes */ .qrcode { diff --git a/windows/src/desktop/kmshell/xml/config.js b/windows/src/desktop/kmshell/xml/config.js index cf761ce6c2c..47b66ca89c1 100644 --- a/windows/src/desktop/kmshell/xml/config.js +++ b/windows/src/desktop/kmshell/xml/config.js @@ -35,7 +35,7 @@ function windowResize() e = _$('subcontent_pro'); if(e) e.style.height = h; _$('subcontent_support').style.height = h; - _$('subcontent_keepintouch').style.height = h; + _$('subcontent_update').style.height = h; } } diff --git a/windows/src/desktop/kmshell/xml/keyman.xsl b/windows/src/desktop/kmshell/xml/keyman.xsl index 94c7ed7e955..a774e0aa9c6 100644 --- a/windows/src/desktop/kmshell/xml/keyman.xsl +++ b/windows/src/desktop/kmshell/xml/keyman.xsl @@ -12,7 +12,7 @@ - + @@ -50,7 +50,7 @@
-
+
diff --git a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl b/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl deleted file mode 100644 index 780166619f1..00000000000 --- a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -
- - -
- -
-
- -
-
-
-
\ No newline at end of file diff --git a/windows/src/desktop/kmshell/xml/keyman_menu.xsl b/windows/src/desktop/kmshell/xml/keyman_menu.xsl index 4fc1b0f2254..99d5e05facf 100644 --- a/windows/src/desktop/kmshell/xml/keyman_menu.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_menu.xsl @@ -9,20 +9,20 @@ - + - + diff --git a/windows/src/desktop/kmshell/xml/keyman_support.xsl b/windows/src/desktop/kmshell/xml/keyman_support.xsl index 4bf93f89bd5..b36a30a0d88 100644 --- a/windows/src/desktop/kmshell/xml/keyman_support.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_support.xsl @@ -49,7 +49,6 @@
  • keyman:link?url=/keyman.com
  • -
  • keyman:link?url=/go//support
  • diff --git a/windows/src/desktop/kmshell/xml/keyman_update.xsl b/windows/src/desktop/kmshell/xml/keyman_update.xsl new file mode 100644 index 00000000000..9acfd28d3b8 --- /dev/null +++ b/windows/src/desktop/kmshell/xml/keyman_update.xsl @@ -0,0 +1,125 @@ + + + + + + + +
    + + +
    + +
    +
    + +
    + +
    + +
    +  
    +
    + +
    Updates are available which will be applied when Windows is next restarted:
    + +
    + + + + + + + + + +
    + +
    + +
    + + + keyman:update_applynow + 220px + + + + + keyman:update_checknow + 220px + +
    + + + +
    +
    +
    + + + + + + javascript:updateTick(""); + Update_ + checked + Update_ + + + Update__RequiresAdmin + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + + + +
    + + + + +
    +
    + +
    + +
    \ No newline at end of file