unit Keyman.System.UpdateCheckResponse;

interface

uses
  System.JSON,
  System.SysUtils;

type
  EUpdateCheckResponse = class(Exception);

  TUpdateCheckResponseStatus = (ucrsNoUpdate, ucrsUpdateReady);

  TUpdateCheckResponseLanguage = record
    ID: string;
    displayName: string;
  end;

  TUpdateCheckResponseLanguages = TArray<TUpdateCheckResponseLanguage>;

  TUpdateCheckResponsePackage = record
    ID: string;
    NewID: string;
    Name: string;
    OldVersion, NewVersion: string;
    DownloadURL: string;
    SavePath: string;
    FileName: string;
    DownloadSize: Integer;
    Install: Boolean;
    Languages: TUpdateCheckResponseLanguages;
  end;

  TUpdateCheckResponsePackages = TArray<TUpdateCheckResponsePackage>;

  TUpdateCheckResponse = record
  private
    FInstallSize: Int64;
    FInstallURL: string;
    FNewVersion: string;
    FNewVersionWithTag: string;
    FStatus: TUpdateCheckResponseStatus;
    FErrorMessage: string;
    FCurrentVersion: string;
    FPackages: TUpdateCheckResponsePackages;
    FFileName: string;
    function ParseKeyboards(nodes: TJSONObject): Boolean;
    function ParseLanguages(i: Integer; v: TJSONValue): Boolean;
  public
    function Parse(const message: AnsiString; const app, currentVersion: string): Boolean;

    property CurrentVersion: string read FCurrentVersion;
    property NewVersion: string read FNewVersion;
    property NewVersionWithTag: string read FNewVersionWithTag;
    property FileName: string read FFileName;
    property InstallURL: string read FInstallURL;
    property InstallSize: Int64 read FInstallSize;
    property ErrorMessage: string read FErrorMessage;
    property Status: TUpdateCheckResponseStatus read FStatus;
    property Packages: TUpdateCheckResponsePackages read FPackages;
  end;

implementation

uses
  System.Generics.Collections,
  versioninfo;

{ TUpdateCheckResponse }

function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
var
  node, doc: TJSONObject;
begin
  FCurrentVersion := currentVersion;
  FStatus := ucrsNoUpdate;

  // TODO: test with UTF8 characters in response
  doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject;
  if doc = nil then
  begin
    FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]);
    Exit(False);
  end;

  if doc.Values[app] is TJSONObject then
  begin
    node := doc.Values[app] as TJSONObject;
    if CompareVersions(node.Values['version'].Value, FCurrentVersion) < 0 then
    begin
      FNewVersion := node.Values['version'].Value;
      FNewVersionWithTag := FNewVersion;
      if (node.Values['stability'] <> nil) and (node.Values['stability'].Value <> '') then
        FNewVersionWithTag := FNewVersionWithTag + '-' + node.Values['stability'].Value;
      FFileName := node.Values['file'].Value;
      FInstallURL := node.Values['url'].Value;
      if node.Values['size'] is TJSONNumber then
      begin
        FInstallSize := (node.Values['size'] as TJSONNumber).AsInt64;
        FStatus := ucrsUpdateReady;
      end
      else
      begin
        FInstallSize := 0;
        FStatus := ucrsNoUpdate;
        FErrorMessage := 'No valid updates found.';
      end;
    end
    else
    begin
      FErrorMessage := 'No updates are currently available.';
      FStatus := ucrsNoUpdate;
    end;
  end
  else if doc.Values['message'] <> nil then
  begin
    FErrorMessage := doc.Values['message'].Value;
    Exit(False);
  end;

  if doc.Values['keyboards'] is TJSONObject
    then Result := ParseKeyboards(doc.Values['keyboards'] as TJSONObject)
    else Result := True;
end;

function TUpdateCheckResponse.ParseKeyboards(nodes: TJSONObject): Boolean;
var
  node: TJSONObject;
  i: Integer;
begin
  SetLength(FPackages,nodes.Count);
  for i := 0 to nodes.Count - 1 do
  begin
    node := nodes.Pairs[i].JsonValue as TJSONObject;
    FPackages[i].NewID := node.Values['id'].Value;
    FPackages[i].ID := nodes.Pairs[i].JsonString.Value;
    FPackages[i].Name := node.Values['name'].Value;
    //FPackages[j].OldVersion := pkg.Version;
    FPackages[i].NewVersion := node.Values['version'].Value;
    FPackages[i].DownloadSize := (node.Values['packageFileSize'] as TJSONNumber).AsInt64;
    FPackages[i].DownloadURL := node.Values['url'].Value;
    FPackages[i].FileName := node.Values['packageFilename'].Value;
    if not ParseLanguages(i, node.Values['languages']) then
      Exit(False);
  end;

  Result := True;
end;

function TUpdateCheckResponse.ParseLanguages(i: Integer; v: TJSONValue): Boolean;
var
  nodes, node: TJSONObject;
  pair: TJSONPair;
  j: Integer;
begin
  if not Assigned(v) then
    Exit(True);

  nodes := v.AsType<TJSONObject>;
  SetLength(FPackages[i].Languages, nodes.Count);
  for j := 0 to nodes.Count - 1 do
  begin
    pair := nodes.Pairs[j];
    node := pair.JsonValue as TJSONObject;
    FPackages[i].Languages[j].ID := pair.JsonString.Value;
    FPackages[i].Languages[j].displayName := node.Values['displayName'].Value;
  end;
  Result := True;
end;

end.
