Merge branch 'epic/windows-updates' into chore/merge-master-into-windows-updates

This commit is contained in:
Marc Durdin 2024-10-11 01:20:41 +02:00 • committed by GitHub
commit cb0a60a1b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 810 additions and 89 deletions

View file

@ -35,6 +35,7 @@ type
TUpdateCheckResponse = record
private
FOriginalData: string;
FInstallSize: Int64;
FInstallURL: string;
FNewVersion: string;
@ -46,9 +47,13 @@ type
FFileName: string;
function ParseKeyboards(nodes: TJSONObject): Boolean;
function ParseLanguages(i: Integer; v: TJSONValue): Boolean;
function DoParse(const message, app, currentVersion: string): Boolean;
public
function Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
procedure SaveToFile(const Filename: string);
function LoadFromFile(const Filename, app, currentVersion: string): Boolean;
property CurrentVersion: string read FCurrentVersion;
property NewVersion: string read FNewVersion;
property NewVersionWithTag: string read FNewVersionWithTag;
@ -58,25 +63,32 @@ type
property ErrorMessage: string read FErrorMessage;
property Status: TUpdateCheckResponseStatus read FStatus;
property Packages: TUpdateCheckResponsePackages read FPackages;
property OriginalData: string read FOriginalData;
end;
implementation
uses
System.Classes,
System.Generics.Collections,
versioninfo;
{ TUpdateCheckResponse }
function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
begin
Result := DoParse(string(UTF8String(message)), app, currentVersion);
end;
function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean;
var
node, doc: TJSONObject;
begin
FOriginalData := message;
FCurrentVersion := currentVersion;
FStatus := ucrsNoUpdate;
// TODO: test with UTF8 characters in response
doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject;
doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject;
if doc = nil then
begin
FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]);
@ -168,4 +180,29 @@ begin
Result := True;
end;
function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean;
var
ss: TStringStream;
begin
ss := TStringStream.Create('', TEncoding.UTF8);
try
ss.LoadFromFile(Filename);
Result := DoParse(ss.DataString, app, currentVersion);
finally
ss.Free;
end;
end;
procedure TUpdateCheckResponse.SaveToFile(const Filename: string);
var
ss: TStringStream;
begin
ss := TStringStream.Create(FOriginalData, TEncoding.UTF8);
try
ss.SaveToFile(Filename);
finally
ss.Free;
end;
end;
end.

View file

@ -17,6 +17,8 @@ type
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 @@ type
const S_FallbackKeyboardPath = 'Keyboards\';
const S__Package = '_Package\';
const S_MCompileExe = 'mcompile.exe';
const S_UpdateCache_Metadata = 'cache.json';
class function ErrorLogPath(const app: string = ''): string; static;
class function KeymanHelpPath(const HelpFile: string): string; static;
class function KeymanUpdateCachePath(const filename: string = ''): string; static;
class function KeymanDesktopInstallPath(const filename: string = ''): string; static;
class function KeymanEngineInstallPath(const filename: string = ''): string; static;
class function KeymanDesktopInstallDir: string; static;
@ -423,6 +427,11 @@ begin
Result := '';
end;
class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string;
begin
Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename;
end;
class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
begin
// On developer machines, if we are running within the source repo, then use

View file

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

View file

@ -354,6 +354,9 @@
<DCCReference Include="web\Keyman.Configuration.System.HttpServer.App.Locale.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas"/>
<DCCReference Include="main\Keyman.Configuration.System.Main.pas"/>
<DCCReference Include="render\UpdateXMLRenderer.pas"/>
<DCCReference Include="main\Keyman.System.UpdateCheckStorage.pas"/>
<DCCReference Include="main\Keyman.System.RemoteUpdateCheck.pas"/>
<None Include="Profiling\AQtimeModule1.aqt"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
@ -415,9 +418,9 @@
<Platform value="Win64">False</Platform>
</Platforms>
<Deployment Version="3">
<DeployFile LocalName="kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>kmshell.rsm</RemoteName>
<RemoteName>kmshell.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
@ -427,9 +430,9 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
<DeployFile LocalName="bin\Win32\Debug\kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<RemoteName>kmshell.rsm</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>

View file

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

View file

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

View file

@ -98,6 +98,7 @@ type
FShowErrors: Boolean;
FDownload: TOnlineUpdateCheckDownloadParams;
FCheckOnly: Boolean;
function DownloadUpdates: Boolean;
procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean);
@ -112,7 +113,9 @@ type
public
public
constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean);
function ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams;
constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean = False);
destructor Destroy; override;
function Run: TOnlineUpdateCheckResult;
property ShowErrors: Boolean read FShowErrors write FShowErrors;
@ -146,6 +149,7 @@ uses
KLog,
keymanapi_TLB,
KeymanVersion,
Keyman.System.UpdateCheckStorage,
kmint,
ErrorControlledRegistry,
RegistryKeys,
@ -165,7 +169,7 @@ const
{ TOnlineUpdateCheck }
constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean);
constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean);
begin
inherited Create;
@ -176,6 +180,7 @@ begin
FSilent := ASilent;
FForce := AForce;
FCheckOnly := ACheckOnly;
KL.Log('TOnlineUpdateCheck.Create');
end;
@ -477,10 +482,9 @@ end;
function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult;
var
flags: DWord;
i, n: Integer;
pkg: IKeymanPackage;
j: Integer;
i: Integer;
ucr: TUpdateCheckResponse;
pkg: IKeymanPackage;
begin
{FProxyHost := '';
FProxyPort := 0;}
@ -567,45 +571,15 @@ begin
begin
if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then
begin
SetLength(FParams.Packages,0);
for i := Low(ucr.Packages) to High(ucr.Packages) do
ResponseToParams(ucr);
if FCheckOnly then
begin
n := kmcom.Packages.IndexOf(ucr.Packages[i].ID);
if n >= 0 then
begin
pkg := kmcom.Packages[n];
j := Length(FParams.Packages);
SetLength(FParams.Packages, j+1);
FParams.Packages[j].NewID := ucr.Packages[i].NewID;
FParams.Packages[j].ID := ucr.Packages[i].ID;
FParams.Packages[j].Description := ucr.Packages[i].Name;
FParams.Packages[j].OldVersion := pkg.Version;
FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion;
FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize;
FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL;
FParams.Packages[j].FileName := ucr.Packages[i].FileName;
pkg := nil;
end
else
FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID;
end;
case ucr.Status of
ucrsNoUpdate:
begin
FErrorMessage := ucr.ErrorMessage;
end;
ucrsUpdateReady:
begin
FParams.Keyman.OldVersion := ucr.CurrentVersion;
FParams.Keyman.NewVersion := ucr.NewVersion;
FParams.Keyman.DownloadURL := ucr.InstallURL;
FParams.Keyman.DownloadSize := ucr.InstallSize;
FParams.Keyman.FileName := ucr.FileName;
end;
end;
if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then
// TODO: Refactor this
TUpdateCheckStorage.SaveUpdateCacheData(ucr);
Result := FParams.Result;
end
else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then
begin
if not FSilent then
ShowUpdateForm
@ -651,6 +625,52 @@ begin
end;
end;
function TOnlineUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams;
var
i, j, n: Integer;
pkg: IKeymanPackage;
begin
SetLength(FParams.Packages,0);
for i := Low(ucr.Packages) to High(ucr.Packages) do
begin
n := kmcom.Packages.IndexOf(ucr.Packages[i].ID);
if n >= 0 then
begin
pkg := kmcom.Packages[n];
j := Length(FParams.Packages);
SetLength(FParams.Packages, j+1);
FParams.Packages[j].NewID := ucr.Packages[i].NewID;
FParams.Packages[j].ID := ucr.Packages[i].ID;
FParams.Packages[j].Description := ucr.Packages[i].Name;
FParams.Packages[j].OldVersion := pkg.Version;
FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion;
FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize;
FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL;
FParams.Packages[j].FileName := ucr.Packages[i].FileName;
pkg := nil;
end
else
FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID;
end;
case ucr.Status of
ucrsNoUpdate:
begin
FErrorMessage := ucr.ErrorMessage;
end;
ucrsUpdateReady:
begin
FParams.Keyman.OldVersion := ucr.CurrentVersion;
FParams.Keyman.NewVersion := ucr.NewVersion;
FParams.Keyman.DownloadURL := ucr.InstallURL;
FParams.Keyman.DownloadSize := ucr.InstallSize;
FParams.Keyman.FileName := ucr.FileName;
end;
end;
Result := FParams;
end;
procedure OnlineUpdateAdmin(OwnerForm: TCustomForm; Path: string);
var
Package: TOnlineUpdateCheckParamsPackage;

View file

@ -148,6 +148,7 @@ type
procedure OpenSite(params: TStringList);
procedure DoApply;
procedure DoRefresh;
procedure Update_CheckNow;
protected
procedure FireCommand(const command: WideString; params: TStringList); override;
@ -202,6 +203,7 @@ uses
UfrmTextEditor,
uninstall,
Upload_Settings,
UpdateXMLRenderer,
utildir,
utilexecute,
utilkmshell,
@ -301,6 +303,7 @@ begin
FXMLRenderers.Add(TOptionsXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TLanguagesXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TSupportXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TUpdateXMLRenderer.Create(FXMLRenderers));
xml := FXMLRenderers.RenderToString(s);
sharedData.Init(
@ -345,6 +348,8 @@ begin
else if command = 'support_updatecheck' then Support_UpdateCheck
else if command = 'support_proxyconfig' then Support_ProxyConfig
else if command = 'update_checknow' then Update_CheckNow
else if command = 'contact_support' then Support_ContactSupport(params) // I4390
else if command = 'opensite' then OpenSite(params)
@ -815,6 +820,17 @@ begin
end;
end;
procedure TfrmMain.Update_CheckNow;
begin
with TOnlineUpdateCheck.Create(Self, True, True, True) do
try
Run;
finally
Free;
end;
DoRefresh;
end;
procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
inherited;

View file

@ -82,6 +82,7 @@ type
fmMigrate, fmSplash, fmStart,
fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548
fmOnlineUpdateAdmin, fmTextEditor,
fmBackgroundUpdateCheck,
fmFirstRun, // I2562
fmKeyboardWelcome, // I2569
fmKeyboardPrint, // I2329
@ -120,6 +121,7 @@ uses
KMShellHints,
KeymanMutex,
OnlineUpdateCheck,
Keyman.System.RemoteUpdateCheck,
RegistryKeys,
UfrmBaseKeyboard,
UfrmKeymanBase,
@ -249,6 +251,7 @@ begin
else if s = '-h' then FMode := fmHelp
else if s = '-t' then FMode := fmTextEditor
else if s = '-ouc' then FMode := fmOnlineUpdateCheck
else if s = '-buc' then FMode := fmBackgroundUpdateCheck
else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
@ -381,6 +384,7 @@ var
kdl: IKeymanDefaultLanguage;
FIcon: string;
FMutex: TKeymanMutex; // I2720
RemoteUpdateCheck: TRemoteUpdateCheck;
function FirstKeyboardFileName: WideString;
begin
if KeyboardFileNames.Count = 0
@ -427,6 +431,19 @@ begin
ShowMessage(MsgFromId(SKOSNotSupported));
Exit;
end;
// TODO: #10038 Will add this as part of the background update state machine
// for now just verifing the download happens via -buc switch.
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

View file

@ -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 +
'<Update>'+
'<index>0</index>'+
IfThen(not kmcom.SystemInfo.IsAdministrator, '<RequiresAdmin />')+
'<Keyman>'+
'<Text>'+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [ucr.NewVersion]))+'</Text>'+
'<NewVersion>'+xmlencode(ucr.NewVersion)+'</NewVersion>'+
'<OldVersion>'+xmlencode(ucr.CurrentVersion)+'</OldVersion>'+
'<DownloadSize>'+xmlencode(Format('%d', [ucr.InstallSize div 1024]))+'KB</DownloadSize>'+
'<DownloadURL>'+xmlencode(ucr.InstallURL)+'</DownloadURL>'+
'</Keyman>'+
'</Update>';
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 +
'<Update>'+
'<index>'+IntToStr(i+1)+'</index>'+
IfThen(not kmcom.SystemInfo.IsAdministrator, '<RequiresAdmin />')+
'<Package>'+
'<Text>'+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText,
[ucr.Packages[i].Name, ucr.Packages[i].NewVersion]))+'</Text>'+
'<NewVersion>'+xmlencode(ucr.Packages[i].NewVersion)+'</NewVersion>'+
'<OldVersion>'+xmlencode(pkg.version)+'</OldVersion>'+
'<DownloadSize>'+xmlencode(Format('%d', [ucr.Packages[i].DownloadSize div 1024]))+'KB</DownloadSize>'+
'<DownloadURL>'+xmlencode(ucr.Packages[i].DownloadURL)+'</DownloadURL>'+
'</Package>'+
'</Update>';
pkg := nil;
end;
// else Package not found, skip
end;
end;
Result := '<Updates>'+xml+'</Updates>';
end;
end.

View file

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

View file

@ -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;
}
}

View file

@ -12,7 +12,7 @@
<xsl:include href="keyman_options.xsl"/>
<xsl:include href="keyman_hotkeys.xsl"/>
<xsl:include href="keyman_support.xsl"/>
<xsl:include href="keyman_keepintouch.xsl"/>
<xsl:include href="keyman_update.xsl"/>
<xsl:include href="keyman_footer.xsl"/>
@ -50,7 +50,7 @@
<div class="contentpage" id="content_options"><xsl:call-template name="content_options" /></div>
<div class="contentpage" id="content_hotkeys"><xsl:call-template name="content_hotkeys" /></div>
<div class="contentpage" id="content_support"><xsl:call-template name="content_support" /></div>
<div class="contentpage" id="content_keepintouch"><xsl:call-template name="content_keepintouch" /></div>
<div class="contentpage" id="content_update"><xsl:call-template name="content_update" /></div>
</div>
<div id="footerframe">
<xsl:call-template name="footerframe" />

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="content_keepintouch">
<script>
window['menuframe_activate_keepintouch'] = function() {
document.getElementById('keepintouch_frame').src = '<xsl:value-of select='/Keyman/keyman-com' />/go/windows/<xsl:value-of select="/Keyman/version-info/@versionRelease" />/keep-in-touch?embed=1';
}
</script>
<div class="header">
<xsl:call-template name="header_helplinks" />
<xsl:value-of select="$locale/string[@name='S_KeepInTouch']"/>
</div>
<div id="subcontent_keepintouch" class="content">
<div id="keepintouch_content">
<iframe id='keepintouch_frame'></iframe>
</div>
</div>
</xsl:template>
</xsl:stylesheet>

View file

@ -9,20 +9,20 @@
<xsl:variable name="locale_Options"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Options']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Hotkeys"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Hotkeys']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Support"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Support']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_KeepInTouch"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_KeepInTouch']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Update"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Update']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Keyboards_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Keyboards_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Options_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Options_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Hotkeys_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Hotkeys_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Support_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Support_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_KeepInTouch_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_KeepInTouch_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Update_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Update_AccessChar']" /></xsl:call-template></xsl:variable>
<script type="text/javascript">
menuframe_add('/app/', 'keyboardlist', '<xsl:value-of select="$locale_Keyboards"/>', '<xsl:value-of select="$locale_Keyboards_AccessChar"/>');
menuframe_add('/app/', 'options', '<xsl:value-of select="$locale_Options"/>', '<xsl:value-of select="$locale_Options_AccessChar"/>');
menuframe_add('/app/', 'hotkeys', '<xsl:value-of select="$locale_Hotkeys"/>', '<xsl:value-of select="$locale_Hotkeys_AccessChar"/>');
menuframe_add('/app/', 'support', '<xsl:value-of select="$locale_Support"/>', '<xsl:value-of select="$locale_Support_AccessChar"/>');
menuframe_add('/app/', 'keepintouch', '<xsl:value-of select="$locale_KeepInTouch"/>', '<xsl:value-of select="$locale_KeepInTouch_AccessChar"/>');
menuframe_add('/app/', 'update', '<xsl:value-of select="$locale_Update"/>', '<xsl:value-of select="$locale_Update_AccessChar"/>');
</script>
<span class="menuframe" id="menuframe_footer"><xsl:text></xsl:text></span>
</xsl:template>

View file

@ -49,7 +49,6 @@
<ul>
<li><a><xsl:attribute name="href">keyman:link?url=<xsl:value-of select="/Keyman/keyman-com"/>/</xsl:attribute>keyman.com</a></li>
<li><a href="keyman:support_diagnostics"><xsl:value-of select="$locale/string[@name='S_Menu_Diagnostics_Diagnostics']"/></a></li>
<li><a href="keyman:support_updatecheck"><xsl:value-of select="$locale/string[@name='S_Button_CheckForUpdates']"/></a></li>
<li><a>
<xsl:attribute name="href">keyman:link?url=<xsl:value-of select="/Keyman/keyman-com" />/go/<xsl:value-of select="/Keyman/version-info/@versionRelease" />/support</xsl:attribute
><xsl:value-of select="$locale/string[@name='S_Button_OnlineSupport']"/></a></li>

View file

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="content_update">
<script>
window['menuframe_activate_update'] = function() {
document.getElementById('update_frame').src = '<xsl:value-of select='/Keyman/keyman-com' />/go/windows/<xsl:value-of select="/Keyman/version-info/@versionRelease" />/keep-in-touch?embed=1';
}
</script>
<div class="header">
<xsl:call-template name="header_helplinks" />
<xsl:value-of select="$locale/string[@name='S_Update']"/>
</div>
<div id="subcontent_update" class="content">
<div id="update_content">
<div class="update_title">
<img src="/app/keyman-desktop.png" />
</div>
<div class="update_edition">
<xsl:value-of select="$locale/string[@name='S_Support_Version']"/>&#160;<xsl:value-of select="/Keyman/version-info/@versionWithTag" /><br />
</div>
<div id="update_status">Updates are available which will be applied when Windows is next restarted:</div>
<div id="update_details">
<table cellspacing="0" cellpadding="0" id="Updates">
<tr>
<th></th>
<th><xsl:value-of select="$locale/string[@name='S_Update_ComponentHead']"/></th>
<th><xsl:value-of select="$locale/string[@name='S_Update_OldVersionHead']"/></th>
<th><xsl:value-of select="$locale/string[@name='S_Update_SizeHead']"/></th>
</tr>
<xsl:apply-templates select="/Keyman/Updates/Update" />
</table>
</div>
<div id="update_controls">
<xsl:call-template name="button">
<xsl:with-param name="caption"><xsl:value-of select="$locale/string[@name='S_Button_Update_ApplyNow']"/></xsl:with-param>
<xsl:with-param name="command">keyman:update_applynow</xsl:with-param>
<xsl:with-param name="width">220px</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="button">
<xsl:with-param name="caption"><xsl:value-of select="$locale/string[@name='S_Button_Update_CheckNow']"/></xsl:with-param>
<xsl:with-param name="command">keyman:update_checknow</xsl:with-param>
<xsl:with-param name="width">220px</xsl:with-param>
</xsl:call-template>
</div>
<!-- <li><a href="keyman:support_updatecheck"><xsl:value-of select="$locale/string[@name='S_Button_CheckForUpdates']"/></a></li> -->
</div>
</div>
</xsl:template>
<xsl:template match="/Keyman/Updates/Update">
<tr>
<td class="UpdateCheckBox">
<input type="checkbox">
<xsl:attribute name="onclick">javascript:updateTick("<xsl:value-of select="index" />");</xsl:attribute>
<xsl:attribute name="id">Update_<xsl:value-of select="index" /></xsl:attribute>
<xsl:if test="not(@DefaultUnchecked)"><xsl:attribute name="checked">checked</xsl:attribute></xsl:if>
<xsl:attribute name="name">Update_<xsl:value-of select="index" /></xsl:attribute>
</input>
<xsl:if test="RequiresAdmin">
<span><xsl:attribute name="id">Update_<xsl:value-of select="index" />_RequiresAdmin</xsl:attribute></span>
</xsl:if>
</td>
<xsl:choose>
<xsl:when test="Package/Text != ''">
<td class='component'>
<label>
<xsl:attribute name="for">Update_<xsl:value-of select="index"/></xsl:attribute>
<xsl:value-of select="Package/Text"/>
</label>
</td>
<td>
<xsl:value-of select="Package/OldVersion"/>
</td>
<td>
<xsl:value-of select="Package/DownloadSize"/>
</td>
</xsl:when>
<xsl:when test="Keyman/DownloadURL != ''">
<td>
<label>
<xsl:attribute name="for">Update_<xsl:value-of select="index"/></xsl:attribute>
<xsl:value-of select="Keyman/Text"/>
</label>
</td>
<td>
<xsl:value-of select="Keyman/OldVersion"/>
</td>
<td>
<xsl:value-of select="Keyman/DownloadSize"/>
</td>
</xsl:when>
<xsl:otherwise>
<td>
<div id="DownloadFrom">
<xsl:value-of select="$locale/string[@name='S_Update_DownloadFrom']"/>
</div>
<div id="URL">
<a href="keyman:openwebsite">
<xsl:value-of select="/Keyman/DownloadURL"/>
</a>
</div>
</td>
<td>
<xsl:value-of select="Keyman/DownloadVersion"/>
</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:template>
</xsl:stylesheet>