mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
fix(developer): prevent clone of legacy keyboards with no source
If a Keyman Cloud keyboard is legacy and has no source available, make this more obvious to the author when they attempt to clone it. Add also messages to help the author complete the other required steps to clone a keyboard. Fixes: #15606 Test-bot: skip
This commit is contained in:
parent
58a565be21
commit
647a9ebc98
3 changed files with 269 additions and 120 deletions
|
|
@ -86,6 +86,8 @@ function API_UserAgent: string; // = 'Keyman for Windows/<ver>...'
|
|||
function API_UserAgent_Developer: string; // = 'Keyman Developer/<ver>...'
|
||||
function API_UserAgent_Diagnostics: string;
|
||||
|
||||
function API_Path_Keyboard(const id: string): string;
|
||||
|
||||
function KeymanCom_Protocol_Server: string; // = 'https://keyman.com';
|
||||
|
||||
function MakeAPIURL(path: string): string;
|
||||
|
|
@ -121,6 +123,8 @@ const
|
|||
S_KeymanCom_Staging = 'https://keyman.com'; // #7227 disabling: 'https://keyman-staging.com';
|
||||
S_APIServer_Staging = 'api.keyman.com'; // #7227 disabling: 'api.keyman-staging.com';
|
||||
|
||||
S_API_Path_Keyboard = '/keyboard/%0:s';
|
||||
|
||||
const
|
||||
URLPath_PackageDownload_Format = '/go/package/download/%0:s?platform=windows&tier=%1:s&bcp47=%2:s&update=%3:d';
|
||||
URL_KeymanDeveloper_HelpKmcMessage_Format = S_Host_KmnSh+'/%0:s';
|
||||
|
|
@ -187,4 +191,9 @@ begin
|
|||
Result := Format(URL_KeymanDeveloper_HelpKmcMessage_Format, [id.ToLower]);
|
||||
end;
|
||||
|
||||
function API_Path_Keyboard(const id: string): string;
|
||||
begin
|
||||
Result := Format(S_API_Path_Keyboard, [id]);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ inherited frmCloneKeymanCloudProjectParameters: TfrmCloneKeymanCloudProjectParam
|
|||
ExplicitHeight = 545
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object lblMessage: TLabel
|
||||
Left = 8
|
||||
Top = 478
|
||||
Width = 314
|
||||
Height = 13
|
||||
Caption = 'The keyboard %0:s has no source available. It cannot be cloned.'
|
||||
FocusControl = editPath
|
||||
end
|
||||
object cmdOK: TButton
|
||||
Left = 680
|
||||
Top = 473
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ type
|
|||
cmdBrowse: TButton;
|
||||
editPath: TEdit;
|
||||
chkRelocateExternal: TCheckBox;
|
||||
lblMessage: TLabel;
|
||||
procedure cmdOKClick(Sender: TObject);
|
||||
procedure editSourceProjectFilenameChange(Sender: TObject);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
|
|
@ -57,6 +58,7 @@ type
|
|||
cef: TframeCEFHost;
|
||||
dlgBrowse: TBrowse4Folder;
|
||||
FKeymanID: string;
|
||||
FSourceAvailable: Boolean;
|
||||
frmDownloadProgress: TfrmDownloadProgress;
|
||||
function GetBasePath: string;
|
||||
function GetKeyboardID: string;
|
||||
|
|
@ -64,6 +66,7 @@ type
|
|||
procedure EnableControls;
|
||||
procedure SetKeyboardID(const Value: string);
|
||||
procedure UpdateProjectFilename;
|
||||
procedure UpdateMessage;
|
||||
function GetProjectFilename: string;
|
||||
function GetSourceProjectFilename: string;
|
||||
function GetRelocateExternal: Boolean;
|
||||
|
|
@ -71,6 +74,7 @@ type
|
|||
procedure cefLoadEnd(Sender: TObject);
|
||||
procedure DownloadCallback(Owner: TfrmDownloadProgress; var Result: Boolean);
|
||||
procedure DownloadWrapperCallback(var Cancelled: Boolean);
|
||||
function IsKeyboardSourceAvailable(const id: string): Boolean;
|
||||
protected
|
||||
function GetHelpTopic: string; override;
|
||||
public
|
||||
|
|
@ -86,9 +90,11 @@ function ShowCloneKeymanCloudProjectParameters(Owner: TComponent): Boolean;
|
|||
implementation
|
||||
|
||||
uses
|
||||
System.JSON,
|
||||
System.Net.UrlClient,
|
||||
Vcl.ComCtrls,
|
||||
|
||||
HttpUploader,
|
||||
KeymanDeveloperOptions,
|
||||
Keyman.Developer.System.KmcWrapper,
|
||||
Keyman.Developer.System.HelpTopics,
|
||||
|
|
@ -182,6 +188,7 @@ begin
|
|||
dlgBrowse.Root := Desktop;
|
||||
dlgBrowse.Title := 'Select folder to save project to';
|
||||
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
|
|
@ -201,9 +208,97 @@ begin
|
|||
not u.Path.StartsWith(URLSubPath_KeymanDeveloper_Clone_Keyboards_Custom)
|
||||
then FKeymanID := u.Path.Substring(URLSubPath_KeymanDeveloper_Clone_Keyboards.Length)
|
||||
else FKeymanID := '';
|
||||
|
||||
FSourceAvailable := IsKeyboardSourceAvailable(FKeymanID);
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
function TfrmCloneKeymanCloudProjectParameters.IsKeyboardSourceAvailable(const id: string): Boolean;
|
||||
|
||||
function GetKeyboardDataFromApiServer(const id: string): string;
|
||||
var
|
||||
http: THTTPUploader;
|
||||
begin
|
||||
http := THTTPUploader.Create(nil);
|
||||
try
|
||||
http.Request.HostName := API_Server;
|
||||
http.Request.Protocol := API_Protocol;
|
||||
http.Request.UrlPath := API_Path_Keyboard(id);
|
||||
try
|
||||
http.Upload;
|
||||
except
|
||||
// Silently swallow network errors
|
||||
on E:Exception do Exit('');
|
||||
end;
|
||||
|
||||
if (http.Response.StatusCode < 200) or (http.Response.StatusCode > 299) then
|
||||
begin
|
||||
// Keyboard not found or invalid response
|
||||
Exit('');
|
||||
end;
|
||||
|
||||
Result := UTF8ToString(PAnsiChar(http.Response.MessageBodyAsString));
|
||||
finally
|
||||
FreeAndNil(http);
|
||||
end;
|
||||
end;
|
||||
|
||||
function GetSourcePathFromBody(const body: string): string;
|
||||
var
|
||||
val: TJSONValue;
|
||||
obj: TJSONObject;
|
||||
begin
|
||||
try
|
||||
val := TJSONObject.ParseJSONValue(body);
|
||||
except
|
||||
// Not a valid response
|
||||
Exit('');
|
||||
end;
|
||||
|
||||
if not (val is TJSONObject) then
|
||||
begin
|
||||
// Not a valid response
|
||||
Exit('');
|
||||
end;
|
||||
|
||||
obj := val as TJSONObject;
|
||||
val := obj.Values['sourcePath'];
|
||||
if not Assigned(val) or not (val is TJSONString) then
|
||||
begin
|
||||
// no sourcePath property
|
||||
Exit('');
|
||||
end;
|
||||
|
||||
Result := (val as TJSONString).Value;
|
||||
end;
|
||||
|
||||
var
|
||||
body, sourcePath: string;
|
||||
begin
|
||||
if id = '' then
|
||||
begin
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
body := GetKeyboardDataFromApiServer(id);
|
||||
if body = '' then
|
||||
begin
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
sourcePath := GetSourcePathFromBody(body);
|
||||
if sourcePath = '' then
|
||||
begin
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
// Keyboards in legacy/ do not have source available. Keyboards in
|
||||
// release/ and experimental/ have source, and other new categories will
|
||||
// also have source in future.
|
||||
Result := not sourcePath.startsWith('legacy');
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.cmdBrowseClick(Sender: TObject);
|
||||
begin
|
||||
dlgBrowse.InitialDir := editPath.Text;
|
||||
|
|
@ -221,22 +316,26 @@ end;
|
|||
procedure TfrmCloneKeymanCloudProjectParameters.editKeyboardIDChange(Sender: TObject);
|
||||
begin
|
||||
UpdateProjectFilename;
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.editSourceProjectFilenameChange(Sender: TObject);
|
||||
begin
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.editPathChange(Sender: TObject);
|
||||
begin
|
||||
UpdateProjectFilename;
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.editVersionChange(Sender: TObject);
|
||||
begin
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
|
|
@ -248,7 +347,8 @@ begin
|
|||
(FKeymanID <> '') and
|
||||
(Trim(editPath.Text) <> '') and
|
||||
(Trim(editKeyboardID.Text) <> '') and
|
||||
TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True);
|
||||
TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) and
|
||||
FSourceAvailable;
|
||||
|
||||
cmdOK.Enabled := e;
|
||||
end;
|
||||
|
|
@ -292,9 +392,41 @@ end;
|
|||
procedure TfrmCloneKeymanCloudProjectParameters.SetKeyboardID(const Value: string);
|
||||
begin
|
||||
editKeyboardID.Text := Value;
|
||||
UpdateMessage;
|
||||
EnableControls;
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.UpdateMessage;
|
||||
var
|
||||
msg: string;
|
||||
begin
|
||||
if FKeymanID = '' then
|
||||
begin
|
||||
msg := 'Please choose a keyboard from the search form above.';
|
||||
end
|
||||
else if not FSourceAvailable then
|
||||
begin
|
||||
msg := Format('The keyboard %0:s has no source available. It cannot be cloned.', [FKeymanID]);
|
||||
end
|
||||
else if Trim(editPath.Text) = '' then
|
||||
begin
|
||||
msg := 'A valid destination path must be selected.';
|
||||
end
|
||||
else if Trim(editKeyboardID.Text) = '' then
|
||||
begin
|
||||
msg := 'Please enter a valid new project identifier.';
|
||||
end
|
||||
else if not TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) then
|
||||
begin
|
||||
msg := 'Please enter a valid new project identifier.';
|
||||
end
|
||||
else
|
||||
begin
|
||||
msg := '';
|
||||
end;
|
||||
lblMessage.Caption := msg;
|
||||
end;
|
||||
|
||||
procedure TfrmCloneKeymanCloudProjectParameters.UpdateProjectFilename;
|
||||
begin
|
||||
editProjectFilename.Text :=
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue