mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-10 10:55:33 +00:00
chore(ios/engine): package support-state querying
This commit is contained in:
commit
84ee2a574a
3 changed files with 165 additions and 29 deletions
|
|
@ -54,6 +54,63 @@ public class KeymanPackage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the distribution level and state of the package.
|
||||
*/
|
||||
public enum SupportState: String, Codable {
|
||||
/**
|
||||
* Indicates that the support level for the package is unknown. Generally occurs when a
|
||||
* package is installed via file-sharing before KeymanEngine is able to perform a package-version check.
|
||||
*/
|
||||
case unknown
|
||||
|
||||
/**
|
||||
* Indicates that the package is publicly distributed but no longer maintained. Indicates that other packages targetting
|
||||
* the same targets are more favored.
|
||||
*/
|
||||
case deprecated
|
||||
|
||||
/**
|
||||
* Indicates that this package receives full support and maintenance from the resource development community at large.
|
||||
*/
|
||||
case publiclyReleased = "publicly released"
|
||||
|
||||
/**
|
||||
* Indicates that this package is known to not be publicly distributed.
|
||||
*/
|
||||
case custom
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloud/query related metadata not tracked (or even trackable) within kmp.json regarding the support state of a keyboard.
|
||||
*/
|
||||
public struct SupportStateMetadata: Codable {
|
||||
var latestVersion: String?
|
||||
var timestampForLastQuery: TimeInterval?
|
||||
|
||||
var supportState: SupportState
|
||||
|
||||
init(from queryResult: Queries.PackageVersion.ResultComponent) {
|
||||
if let entry = queryResult as? Queries.PackageVersion.ResultEntry {
|
||||
self.latestVersion = entry.version
|
||||
self.supportState = .publiclyReleased
|
||||
} else /* if queryResult is Queries.PackageVersion.ResultError */ {
|
||||
self.latestVersion = nil
|
||||
// The package-version query knows nothing about it - must be custom.
|
||||
self.supportState = .custom
|
||||
}
|
||||
|
||||
self.timestampForLastQuery = NSDate().timeIntervalSince1970
|
||||
}
|
||||
|
||||
init(fromQuery: Bool = true) {
|
||||
latestVersion = nil
|
||||
timestampForLastQuery = nil
|
||||
|
||||
supportState = fromQuery ? .publiclyReleased : .unknown
|
||||
}
|
||||
}
|
||||
|
||||
static private let kmpFile = "kmp.json"
|
||||
public let sourceFolder: URL
|
||||
public let id: String
|
||||
|
|
@ -225,6 +282,63 @@ public class KeymanPackage {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the package-version query for the specified packages to determine their current support-state.
|
||||
*/
|
||||
public static func querySupportStates(for keys: [Key], withSession session: URLSession = URLSession.shared, completionBlock: (([Key : SupportStateMetadata]?, Error?)-> Void)? = nil) {
|
||||
Queries.PackageVersion.fetch(for: keys, withSession: session) { results, error in
|
||||
guard error == nil, let results = results else {
|
||||
completionBlock?(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
var keyboardStates: [Key : SupportStateMetadata] = [:]
|
||||
keyboardStates.reserveCapacity(results.keyboards?.count ?? 0)
|
||||
|
||||
// Sadly, Dictionaries do not support mapping to other dictionaries when considering the keys.
|
||||
results.keyboards?.forEach { key, value in
|
||||
keyboardStates[KeymanPackage.Key(id: key, type: .keyboard)] = KeymanPackage.SupportStateMetadata(from: value)
|
||||
}
|
||||
|
||||
var lexicalModelStates: [Key : SupportStateMetadata] = [:]
|
||||
lexicalModelStates.reserveCapacity(results.models?.count ?? 0)
|
||||
|
||||
results.models?.forEach { key, value in
|
||||
lexicalModelStates[KeymanPackage.Key(id: key, type: .lexicalModel)] = KeymanPackage.SupportStateMetadata(from: value)
|
||||
}
|
||||
|
||||
// TODO: Save these states to UserDefaults!
|
||||
|
||||
// There will be no 'merge' conflicts, so we ignore them by simply selecting the original.
|
||||
if let completionBlock = completionBlock {
|
||||
let stateSet = keyboardStates.merging(lexicalModelStates, uniquingKeysWith: { lhs, _ in return lhs })
|
||||
completionBlock(stateSet, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the package-version query for the specified packages to determine if any updates are available.
|
||||
*/
|
||||
public static func queryCurrentVersions(for keys: [Key], withSession session: URLSession = URLSession.shared, completionBlock: (([Key : Version]?, Error?) -> Void)? = nil) {
|
||||
querySupportStates(for: keys, withSession: session) { stateSet, error in
|
||||
guard error == nil, let stateSet = stateSet else {
|
||||
completionBlock?(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
let versionSet: [Key: Version] = stateSet.compactMapValues { value in
|
||||
if let versionString = value.latestVersion, let version = Version(versionString) {
|
||||
return version
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
completionBlock?(versionSet, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -399,35 +399,6 @@ public class ResourceDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the package-version query against all installed resources to determine if any updates are available.
|
||||
*/
|
||||
public func fetchAvailableUpdates(completionBlock: (([KeymanPackage.Key]?, Error?) -> Void)? = nil) {
|
||||
let userDefaults = Storage.active.userDefaults
|
||||
let keyboardPackages = userDefaults.userKeyboards?.map { $0.packageKey }
|
||||
let lexicalModelPackages = userDefaults.userLexicalModels?.map { $0.packageKey }
|
||||
|
||||
let packageKeys = (keyboardPackages ?? []) + (lexicalModelPackages ?? [])
|
||||
|
||||
Queries.PackageVersion.fetch(for: packageKeys) { results, error in
|
||||
guard error == nil else {
|
||||
completionBlock?(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
// If no completionBlock was specified, the caller simply wanted a prefetch.
|
||||
// Any further processing we might try to do would go to waste, so stop here.
|
||||
guard let completionBlock = completionBlock else {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for updates among the returned versions IF a completion block is specified.
|
||||
// This facilitates a more proactive update notification.
|
||||
|
||||
// TODO: flesh out!
|
||||
}
|
||||
}
|
||||
|
||||
public func getAvailableUpdates() -> [AnyLanguageResource]? {
|
||||
// Relies upon KMManager's preload; this was the case before the rework.
|
||||
if Manager.shared.apiKeyboardRepository.languages == nil && Manager.shared.apiLexicalModelRepository.languages == nil {
|
||||
|
|
|
|||
|
|
@ -137,4 +137,55 @@ class KeymanPackageTests: XCTestCase {
|
|||
// Deinit should have triggered - were the files automatically cleaned up?
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: tempDir.path))
|
||||
}
|
||||
|
||||
// Analogous to QueryPackageVersionTests.testMockedBatchFetchParse, but with more analysis applied
|
||||
// and more integration.
|
||||
func testQueryCurrentVersions() throws {
|
||||
let mockedURLSession = TestUtils.Downloading.URLSessionMock()
|
||||
|
||||
let expectation = XCTestExpectation(description: "The query completes as expected.")
|
||||
|
||||
// Test setup
|
||||
|
||||
let mockedResult = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_case_1, error: nil)
|
||||
mockedURLSession.queueMockResult(.data(mockedResult))
|
||||
|
||||
let badKbdKey = KeymanPackage.Key(id: "foo", type: .keyboard)
|
||||
let badLexKey = KeymanPackage.Key(id: "bar", type: .lexicalModel)
|
||||
let packageKeys = [KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor),
|
||||
KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin),
|
||||
KeymanPackage.Key(id: "foo", type: .keyboard),
|
||||
KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt),
|
||||
KeymanPackage.Key(id: "bar", type: .lexicalModel)]
|
||||
|
||||
KeymanPackage.queryCurrentVersions(for: packageKeys, withSession: mockedURLSession) { results, error in
|
||||
guard error == nil, let results = results else {
|
||||
XCTFail()
|
||||
expectation.fulfill()
|
||||
return
|
||||
}
|
||||
|
||||
let khmer_angkor = KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor)
|
||||
let sil_euro_latin = KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin)
|
||||
let mtnt = KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt)
|
||||
XCTAssertEqual(results[khmer_angkor], Version("1.0.6"))
|
||||
XCTAssertEqual(results[sil_euro_latin], Version("1.9.1"))
|
||||
XCTAssertEqual(results[mtnt], Version("0.1.4"))
|
||||
XCTAssertNil(results[badKbdKey])
|
||||
XCTAssertNil(results[badLexKey])
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: 5)
|
||||
|
||||
// Post-execution cleanup
|
||||
let queueWasCleared = mockedURLSession.queueIsEmpty
|
||||
Queries.PackageVersion.resetCache()
|
||||
|
||||
if !queueWasCleared {
|
||||
throw NSError(domain: "Keyman",
|
||||
code: 4,
|
||||
userInfo: [NSLocalizedDescriptionKey: "A test did not fully utilize its queued mock results!"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue