spiegel-keyman/ios/engine/KMEI/KeymanEngine/Classes/Queries/Queries+PackageVersion.swift

132 lines
4.4 KiB
Swift

//
// Queries+PackageVersion.swift
// KeymanEngine
//
// Created by Joshua Horton on 6/25/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
import os.log
extension Queries {
// Kept internal; the query is 'publicly exposed' through KeymanPackage and ResourceFileManager.
class PackageVersion {
class ResultComponent {}
class ResultEntry: ResultComponent, Decodable {
let version: String
let packageURL: String
enum CodingKeys: String, CodingKey {
case version
case packageURL = "kmp"
}
}
class ResultError: ResultComponent, Decodable {
let error: String
enum CodingKeys: String, CodingKey {
case error
}
}
enum Entry {
case success(ResultEntry)
case failure(ResultError?)
}
struct Result: Decodable {
let keyboards: [String : ResultComponent]?
let models: [String : ResultComponent]?
enum CodingKeys: String, CodingKey {
case keyboards
case models
}
init(from decoder: Decoder) throws {
let rootValues = try decoder.container(keyedBy: CodingKeys.self)
func dictionaryReducer(container: KeyedDecodingContainer<Queries.IDCodingKey>, category: String) -> ([String: ResultComponent], Queries.IDCodingKey) throws -> [String: ResultComponent] {
return { (dict, id) -> [String: ResultComponent] in
var dict = dict
if let entry = try? container.decode(ResultEntry.self, forKey: id) {
dict[id.stringValue] = entry
} else if let error = try? container.decode(ResultError.self, forKey: id) {
dict[id.stringValue] = error
} else {
throw FetchError.decodingError(category, id.stringValue)
}
return dict
}
}
if rootValues.contains(.keyboards) {
let keyboardValueSet = try rootValues.nestedContainer(keyedBy: IDCodingKey.self, forKey: .keyboards)
keyboards = try keyboardValueSet.allKeys.reduce([String: ResultComponent](), dictionaryReducer(container: keyboardValueSet, category: "keyboard"))
} else {
keyboards = nil
}
if rootValues.contains(.models) {
let modelValueSet = try rootValues.nestedContainer(keyedBy: IDCodingKey.self, forKey: .models)
models = try modelValueSet.allKeys.reduce([String: ResultComponent](), dictionaryReducer(container: modelValueSet, category: "lexical model"))
} else {
models = nil
}
}
func entryFor(_ key: KeymanPackage.Key) -> Entry {
var result: ResultComponent?
switch(key.type) {
case .keyboard:
result = keyboards?[key.id]
case .lexicalModel:
result = models?[key.id]
}
if let entry = result as? ResultEntry {
return .success(entry)
} else {
return .failure(result as? ResultError)
}
}
}
private static let PACKAGE_VERSION_ENDPOINT = URLComponents(string: "\(KeymanHosts.API_KEYMAN_COM)/package-version")!
public static func fetch(for packageKeys: [KeymanPackage.Key],
withSession session: URLSession = .shared,
fetchCompletion: @escaping JSONQueryCompletionBlock<Result>) {
// Step 1: build the query
var urlComponents = PACKAGE_VERSION_ENDPOINT
let queryItems: [URLQueryItem] = packageKeys.map { key in
var resourceField: String
switch(key.type) {
case .keyboard:
resourceField = "keyboard"
case .lexicalModel:
resourceField = "model"
}
return URLQueryItem(name: resourceField, value: key.id)
}
urlComponents.queryItems = queryItems + [URLQueryItem(name: "platform", value: "ios")]
let message = "Querying package versions through API endpoint: \(urlComponents.url!)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Step 2: configure the completion closure.
let completionClosure = Queries.jsonDataTaskCompletionAdapter(resultType: Result.self, completionBlock: fetchCompletion)
// Step 3: run the actual query, letting the prepared completion closure take care of the rest.
let task = session.dataTask(with: urlComponents.url!, completionHandler: completionClosure)
task.resume()
}
}
}