diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 1a79eab4a4..bd2af0e969 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -344,7 +344,24 @@ public class Manager: NSObject, UIGestureRecognizerDelegate { /// The keyboard must be downloaded (see `downloadKeyboard()`) or preloaded (see `preloadLanguageFile()`) @available(*, deprecated, message: "Deprecated in favor of ResourceFileManager.install(resourceWithID:from:)") public func addKeyboard(_ keyboard: InstallableKeyboard) { - ResourceFileManager.shared.addResource(keyboard) + var kbdToInstall = keyboard + + // 3rd party installation of keyboards (13.0 and before): + // - Manager.shared.preloadFiles was called first to import the file resources + // - Then Manager.shared.addKeyboard installs the keyboard. + // + // Since 3rd-party uses never provided kmp.json files, we need to instant-migrate + // them here. + if !Migrations.resourceHasPackageMetadata(keyboard) { + let wrappedKbds = Migrations.migrateToKMPFormat([keyboard]) + guard wrappedKbds.count == 1 else { + log.error("Could not properly import keyboard") + return + } + kbdToInstall = wrappedKbds[0] + } else { + ResourceFileManager.shared.addResource(kbdToInstall) + } } @@ -391,7 +408,24 @@ public class Manager: NSObject, UIGestureRecognizerDelegate { */ @available(*, deprecated, message: "Deprecated in favor of ResourceFileManager.install(resourceWithID:from:)") static public func addLexicalModel(_ lexicalModel: InstallableLexicalModel) { - ResourceFileManager.shared.addResource(lexicalModel) + var modelToInstall = lexicalModel + + // Potential 3rd party installation of lexical models (12.0, 13.0): + // - Manager.shared.preloadFiles was called first to import the file resources + // - Then Manager.addLexicalModel installs the lexical model. + // + // Since 3rd-party uses never provided kmp.json files, we need to instant-migrate + // them here. + if !Migrations.resourceHasPackageMetadata(lexicalModel) { + let wrappedModels = Migrations.migrateToKMPFormat([lexicalModel]) + guard wrappedModels.count == 1 else { + log.error("Could not properly import lexical model") + return + } + modelToInstall = wrappedModels[0] + } else { + ResourceFileManager.shared.addResource(modelToInstall) + } } /// Removes a keyboard from the list in the keyboard picker if it exists. diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index 6d4dc0a850..d5fc6706d5 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -698,4 +698,10 @@ public enum Migrations { return matched + mappedResources } + + internal static func resourceHasPackageMetadata(_ resource: Resource) -> Bool { + var resourceDir = Storage.active.resourceDir(for: resource)! + resourceDir.appendPathComponent("kmp.json") + return FileManager.default.fileExists(atPath: resourceDir.path) + } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift index 26f5cf9982..6a72f23916 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift @@ -217,6 +217,12 @@ public class ResourceFileManager { } } + /** + * Searches the specified package for a language resource with the indicated resource-language-code "full ID" key, + * importing the package's files and installing the indicated resource-language pairing upon success. + * + * The`resourcesWithIDs:` variant is better optimized for installing multiple resources from the same package. + */ public func install> ( resourceWithID fullID: ResourceType.FullID, @@ -225,6 +231,10 @@ public class ResourceFileManager { try install(resourcesWithIDs: [fullID], from: package) } + /** + * Searches the specified package for language resources with the indicated resource-language-code "full ID" keys + * importing the package's files and installing the indicated resource-language pairings upon success. + */ public func install> ( resourcesWithIDs fullIDs: [Resource.FullID], from package: Package) throws {