diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceDownloadQueue.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceDownloadQueue.swift index 8961d7dbe8..3ad1fb3d91 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceDownloadQueue.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceDownloadQueue.swift @@ -544,8 +544,9 @@ class ResourceDownloadQueue: HTTPDownloadDelegate { public func installLexicalModelPackage(downloadedPackageFile: URL) -> InstallableLexicalModel? { var installedLexicalModel: InstallableLexicalModel? = nil - ResourceFileManager.shared.prepareKMPInstall(from: downloadedPackageFile, completionHandler: { kmp, error in - if let kmp = kmp as! LexicalModelKeymanPackage? { + do { + let package = try ResourceFileManager.shared.prepareKMPInstall(from: downloadedPackageFile) + if let kmp = package as? LexicalModelKeymanPackage { do { ResourceFileManager.shared.finalizePackageInstall(kmp, isCustom: false, completionHandler: { error in if error != nil { @@ -562,9 +563,11 @@ class ResourceDownloadQueue: HTTPDownloadDelegate { log.error("Error installing the lexical model: \(error)") } } else { - log.error("Error extracting the lexical model from the package: \(String(describing: error))") + log.error("Provided package did not contain lexical models.") } - }) + } catch { + log.error("Error extracting the lexical model from the package: \(String(describing: error))") + } return installedLexicalModel } } diff --git a/ios/engine/KMEI/KeymanEngineTests/FileManagementTests.swift b/ios/engine/KMEI/KeymanEngineTests/FileManagementTests.swift index 96bc4f6dfc..2ad47e0686 100644 --- a/ios/engine/KMEI/KeymanEngineTests/FileManagementTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/FileManagementTests.swift @@ -21,70 +21,13 @@ class FileManagementTests: XCTestCase { TestUtils.standardTearDown() } - func testKeyboardInstallation() { - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) { kmp, error in - XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") - XCTAssertNil(error, "Error occurred while preparing KMP for installation") - XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "KMP resource type improperly recognized - expected a keyboard package!") + func testKeyboardInstallation() throws { + let kmp = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) + XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") + XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "KMP resource type improperly recognized - expected a keyboard package!") - ResourceFileManager.shared.finalizePackageInstall(kmp!, isCustom: true) { innerError in - XCTAssertNil(innerError, "Error occurred while finalizing KMP installation") - - let installURL = Storage.active.keyboardURL(forID: "khmer_angkor", version: "1.0.6") - - XCTAssertTrue(FileManager.default.fileExists(atPath: installURL.path), - "Could not find installed keyboard file") - - let keyboards = Storage.active.userDefaults.userKeyboards! - - XCTAssertEqual(keyboards.count, 1, "Unexpected number of keyboards were installed") - XCTAssertEqual(keyboards[0].id, "khmer_angkor", "Installed keyboard ID mismatch") - - let fontURL = Storage.active.fontURL(forKeyboardID: "khmer_angkor", filename: "Mondulkiri-R.ttf") - XCTAssertTrue(FileManager.default.fileExists(atPath: fontURL.path)) - } - } - } - - func testLexicalModelInstallation() { - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) { kmp, error in - XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") - XCTAssertNil(error, "Error occurred while preparing KMP for installation") - XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "KMP resource type improperly recognized - expected a lexical model package!") - - ResourceFileManager.shared.finalizePackageInstall(kmp!, isCustom: true) { innerError in - XCTAssertNil(innerError, "Error occurred while finalizing KMP installation") - - let installURL = Storage.active.lexicalModelURL(forID: "nrc.en.mtnt", version: "0.1.4") - - XCTAssertTrue(FileManager.default.fileExists(atPath: installURL.path), - "Could not find installed lexical model file") - - let models = Storage.active.userDefaults.userLexicalModels! - - // Yep, the model auto-installs for all language ids, even when there's no matching keyboard. - // That's the current state of affairs in Keyman Engine for iOS. - XCTAssertEqual(models.count, 3, "Unexpected number of models were installed") - XCTAssertEqual(models[0].id, "nrc.en.mtnt", "Installed lexical model ID mismatch") - } - } - } - - func testInstallKeyboardFromPackage() throws { - // Standard installation - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) { kmp, error in - XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") - XCTAssertNil(error, "Error occurred while preparing KMP for installation") - guard let kmp = kmp as? KeyboardKeymanPackage else { - XCTFail("KMP resource type improperly recognized - expected a keyboard package!") - return - } - - do { - try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.khmer_angkor.fullID, from: kmp) - } catch { - XCTFail("Unexpected error during KeyboardPackage install") - } + ResourceFileManager.shared.finalizePackageInstall(kmp, isCustom: true) { innerError in + XCTAssertNil(innerError, "Error occurred while finalizing KMP installation") let installURL = Storage.active.keyboardURL(forID: "khmer_angkor", version: "1.0.6") @@ -96,27 +39,18 @@ class FileManagementTests: XCTestCase { XCTAssertEqual(keyboards.count, 1, "Unexpected number of keyboards were installed") XCTAssertEqual(keyboards[0].id, "khmer_angkor", "Installed keyboard ID mismatch") - // While the LanguageResource definition we provided lacks font definitions, the - // KMP's definition has that data. By default, the KMP's definition takes precedence. let fontURL = Storage.active.fontURL(forKeyboardID: "khmer_angkor", filename: "Mondulkiri-R.ttf") XCTAssertTrue(FileManager.default.fileExists(atPath: fontURL.path)) } } - func testInstallLexicalModelFromPackage() { - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) { kmp, error in - XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") - XCTAssertNil(error, "Error occurred while preparing KMP for installation") - guard let kmp = kmp as? LexicalModelKeymanPackage else { - XCTFail("KMP resource type improperly recognized - expected a lexical model package!") - return - } + func testLexicalModelInstallation() throws { + let kmp = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) + XCTAssertNotNil(kmp, "Failed to prepare KMP for installation") + XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "KMP resource type improperly recognized - expected a lexical model package!") - do { - try ResourceFileManager.shared.install(resourceWithID: TestUtils.LexicalModels.mtnt.fullID, from: kmp) - } catch { - XCTFail("Unexpected error during LexicalModelPackage install") - } + ResourceFileManager.shared.finalizePackageInstall(kmp, isCustom: true) { innerError in + XCTAssertNil(innerError, "Error occurred while finalizing KMP installation") let installURL = Storage.active.lexicalModelURL(forID: "nrc.en.mtnt", version: "0.1.4") @@ -125,9 +59,67 @@ class FileManagementTests: XCTestCase { let models = Storage.active.userDefaults.userLexicalModels! - // This variant is selective - only a single pairing should be installed for the model. - XCTAssertEqual(models.count, 1, "Unexpected number of models were installed") + // Yep, the model auto-installs for all language ids, even when there's no matching keyboard. + // That's the current state of affairs in Keyman Engine for iOS. + XCTAssertEqual(models.count, 3, "Unexpected number of models were installed") XCTAssertEqual(models[0].id, "nrc.en.mtnt", "Installed lexical model ID mismatch") } } + + func testInstallKeyboardFromPackage() throws { + // Standard installation + let rawKMP = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) + XCTAssertNotNil(rawKMP, "Failed to prepare KMP for installation") + guard let kmp = rawKMP as? KeyboardKeymanPackage else { + XCTFail("KMP resource type improperly recognized - expected a keyboard package!") + return + } + + do { + try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.khmer_angkor.fullID, from: kmp) + } catch { + XCTFail("Unexpected error during KeyboardPackage install") + } + + let installURL = Storage.active.keyboardURL(forID: "khmer_angkor", version: "1.0.6") + + XCTAssertTrue(FileManager.default.fileExists(atPath: installURL.path), + "Could not find installed keyboard file") + + let keyboards = Storage.active.userDefaults.userKeyboards! + + XCTAssertEqual(keyboards.count, 1, "Unexpected number of keyboards were installed") + XCTAssertEqual(keyboards[0].id, "khmer_angkor", "Installed keyboard ID mismatch") + + // While the LanguageResource definition we provided lacks font definitions, the + // KMP's definition has that data. By default, the KMP's definition takes precedence. + let fontURL = Storage.active.fontURL(forKeyboardID: "khmer_angkor", filename: "Mondulkiri-R.ttf") + XCTAssertTrue(FileManager.default.fileExists(atPath: fontURL.path)) + } + + func testInstallLexicalModelFromPackage() throws { + let rawKMP = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) + XCTAssertNotNil(rawKMP, "Failed to prepare KMP for installation") + guard let kmp = rawKMP as? LexicalModelKeymanPackage else { + XCTFail("KMP resource type improperly recognized - expected a lexical model package!") + return + } + + do { + try ResourceFileManager.shared.install(resourceWithID: TestUtils.LexicalModels.mtnt.fullID, from: kmp) + } catch { + XCTFail("Unexpected error during LexicalModelPackage install") + } + + let installURL = Storage.active.lexicalModelURL(forID: "nrc.en.mtnt", version: "0.1.4") + + XCTAssertTrue(FileManager.default.fileExists(atPath: installURL.path), + "Could not find installed lexical model file") + + let models = Storage.active.userDefaults.userLexicalModels! + + // This variant is selective - only a single pairing should be installed for the model. + XCTAssertEqual(models.count, 1, "Unexpected number of models were installed") + XCTAssertEqual(models[0].id, "nrc.en.mtnt", "Installed lexical model ID mismatch") + } } diff --git a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift index 3295eb0849..ef14a8f81e 100644 --- a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift @@ -19,23 +19,21 @@ class KeymanPackageTests: XCTestCase { // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. do { - try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL, complete: { kmp in - if let kmp = kmp { - // Run assertions on the package's kmp.info. - // Assumes the KMP used for testing here has the same kmp.info used for those tests. - let kmp_json_testcase = KMPJSONTests() - kmp_json_testcase.kmp_info_khmer_angkor_assertions(kmp.metadata) + if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { + // Run assertions on the package's kmp.info. + // Assumes the KMP used for testing here has the same kmp.info used for those tests. + let kmp_json_testcase = KMPJSONTests() + kmp_json_testcase.kmp_info_khmer_angkor_assertions(kmp.metadata) - XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "Keyboard KMP test extraction did not yield a keyboard package!") - XCTAssertTrue(kmp.isKeyboard(), "Keyboard KMP test extraction did not yield a keyboard package!") + XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "Keyboard KMP test extraction did not yield a keyboard package!") + XCTAssertTrue(kmp.isKeyboard(), "Keyboard KMP test extraction did not yield a keyboard package!") - // extracted ok, test kmp - XCTAssert(kmp.sourceFolder == destinationFolderURL, - "The KMP's reported 'source folder' should match the specified destination folder") - } else { - XCTAssert(false, "KeymanPackage.extract failed") - } - }) + // extracted ok, test kmp + XCTAssert(kmp.sourceFolder == destinationFolderURL, + "The KMP's reported 'source folder' should match the specified destination folder") + } else { + XCTAssert(false, "KeymanPackage.extract failed") + } } catch { XCTFail("KeymanPackage.extract failed with error \(error)") } @@ -50,54 +48,48 @@ class KeymanPackageTests: XCTestCase { // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. do { - try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL, complete: { kmp in - if let kmp = kmp { - // Run assertions on the package's kmp.info. - // Assumes the KMP used for testing here has the same kmp.info used for those tests. - let kmp_json_testcase = KMPJSONTests() + if let kmp = try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL) { + // Run assertions on the package's kmp.info. + // Assumes the KMP used for testing here has the same kmp.info used for those tests. + let kmp_json_testcase = KMPJSONTests() - // As this test takes place after construction of the LexicalModelPackage, - // the version will be set accordingly, unlike in the other JSON-related tests. - kmp_json_testcase.kmp_info_nrc_en_mtnt_assertions(kmp.metadata, version: "0.1.4") + // As this test takes place after construction of the LexicalModelPackage, + // the version will be set accordingly, unlike in the other JSON-related tests. + kmp_json_testcase.kmp_info_nrc_en_mtnt_assertions(kmp.metadata, version: "0.1.4") - XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "Lexical model KMP test extraction yielded a keyboard package!") - XCTAssertTrue(!kmp.isKeyboard(), "Lexical model KMP test extraction yielded a keyboard package!") + XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "Lexical model KMP test extraction yielded a keyboard package!") + XCTAssertTrue(!kmp.isKeyboard(), "Lexical model KMP test extraction yielded a keyboard package!") - // extracted ok, test kmp - XCTAssert(kmp.sourceFolder == destinationFolderURL, - "The KMP's reported 'source folder' should match the specified destination folder") - } else { - XCTAssert(false, "KeymanPackage.extract failed") - } - }) + // extracted ok, test kmp + XCTAssert(kmp.sourceFolder == destinationFolderURL, + "The KMP's reported 'source folder' should match the specified destination folder") + } else { + XCTAssert(false, "KeymanPackage.extract failed") + } } catch { XCTFail("KeymanPackage.extract failed with error \(error)") } } - func testPackageFindResourceMatch() { - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) { kmp, _ in - guard let kmp = kmp as? KeyboardKeymanPackage else { - XCTFail("Incorrect package type loaded for test") - return - } - XCTAssertNotNil(kmp.findResource(withID: TestUtils.Keyboards.khmer_angkor.fullID)) - // This keyboard's not in the specified testing package. - XCTAssertNil(kmp.findResource(withID: TestUtils.Keyboards.khmer10.fullID)) - - // Thanks to our package typing hierarchy, it's impossible to even TRY finding - // a FullLexicalModelID within a KeyboardKeymanPackage! + func testPackageFindResourceMatch() throws { + guard let kmp1 = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) as? KeyboardKeymanPackage else { + XCTFail("Incorrect package type loaded for test") + return } + XCTAssertNotNil(kmp1.findResource(withID: TestUtils.Keyboards.khmer_angkor.fullID)) + // This keyboard's not in the specified testing package. + XCTAssertNil(kmp1.findResource(withID: TestUtils.Keyboards.khmer10.fullID)) - ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) { kmp, _ in - guard let kmp = kmp as? LexicalModelKeymanPackage else { - XCTFail("Incorrect package type loaded for test") - return - } - XCTAssertNotNil(kmp.findResource(withID: TestUtils.LexicalModels.mtnt.fullID)) + // Thanks to our package typing hierarchy, it's impossible to even TRY finding + // a FullLexicalModelID within a KeyboardKeymanPackage! - // Thanks to our package typing hierarchy, it's impossible to even TRY finding - // a FullKeyboardID within a LexicalModelKeymanPackage! + guard let kmp2 = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) as? LexicalModelKeymanPackage else { + XCTFail("Incorrect package type loaded for test") + return } + XCTAssertNotNil(kmp2.findResource(withID: TestUtils.LexicalModels.mtnt.fullID)) + + // Thanks to our package typing hierarchy, it's impossible to even TRY finding + // a FullKeyboardID within a LexicalModelKeymanPackage! } } diff --git a/ios/keyman/Keyman/Keyman/AppDelegate.swift b/ios/keyman/Keyman/Keyman/AppDelegate.swift index 57a0e46bcd..6a635af216 100644 --- a/ios/keyman/Keyman/Keyman/AppDelegate.swift +++ b/ios/keyman/Keyman/Keyman/AppDelegate.swift @@ -32,13 +32,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } if let vc = window?.rootViewController { - rfm.prepareKMPInstall(from: destinationUrl, - alertHost: vc, - completionHandler: { package in - // We choose to prompt the user for comfirmation, rather - // than automatically installing the package. - rfm.promptPackageInstall(of: package, in: vc, isCustom: true) - }) + if let package = rfm.prepareKMPInstall(from: destinationUrl, alertHost: vc) { + // We choose to prompt the user for comfirmation, rather + // than automatically installing the package. + rfm.promptPackageInstall(of: package, in: vc, isCustom: true) + } } else { log.error("Cannot find app's root UIViewController") } diff --git a/ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift b/ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift index 473c13dde0..639c81d791 100644 --- a/ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift +++ b/ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift @@ -63,16 +63,14 @@ class PackageBrowserViewController: UIDocumentBrowserViewController, UIDocumentB return } - rfm.prepareKMPInstall(from: destinationUrl, - alertHost: self, - completionHandler: { package in - // We choose to prompt the user for comfirmation, rather - // than automatically installing the package. - rfm.promptPackageInstall(of: package, in: self, isCustom: true, successHandler: { _ in - // Auto-dismiss the document browser upon successful KMP install. - // It's likely quite rare that someone would want to install 2+ at once. - self.navigationController?.popViewController(animated: true) - }) - }) + if let package = rfm.prepareKMPInstall(from: destinationUrl, alertHost: self) { + // We choose to prompt the user for comfirmation, rather + // than automatically installing the package. + rfm.promptPackageInstall(of: package, in: self, isCustom: true, successHandler: { _ in + // Auto-dismiss the document browser upon successful KMP install. + // It's likely quite rare that someone would want to install 2+ at once. + self.navigationController?.popViewController(animated: true) + }) + } } }