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 @@
-
+