mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-15 13:19:23 +00:00
Merge branch 'ios-load-model' into ios-load-model-integrate
This commit is contained in:
commit
5cbcbf384a
11 changed files with 130 additions and 118 deletions
|
|
@ -1,3 +1,3 @@
|
|||
github "marmelroy/Zip"
|
||||
github "DaveWoodCom/XCGLogger" ~> 6.1.0
|
||||
github "dennisweissmann/DeviceKit" ~> 1.3
|
||||
github "dennisweissmann/DeviceKit" ~> 1.11
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
github "DaveWoodCom/XCGLogger" "6.1.0"
|
||||
github "dennisweissmann/DeviceKit" "1.11.0"
|
||||
github "dennisweissmann/DeviceKit" "1.13.0"
|
||||
github "marmelroy/Zip" "1.1.0"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ To build in Xcode,
|
|||
|
||||
The framework will be built to **engine/KMEI/build/(Debug|Release)-universal/KeymanEngine.framework**.
|
||||
|
||||
If it doesn't build, and you have upgraded from Xcode 10.0 (or earlier) to 10.1 (or later), it may not
|
||||
build due to "Could not find any available simulators for iOS" error from Carthage, probably while
|
||||
building DeviceKit. Xcode 10.1 changed the output format which confuses Carthage. Upgrade Carthage:
|
||||
```
|
||||
brew upgrade carthage
|
||||
brew link --overwrite carthage
|
||||
```
|
||||
|
||||
### Linking with KeymanEngine
|
||||
1. Add KeymanEngine.framework to 'Linked Frameworks and Libraries' and 'Embedded Binaries' in your project targets.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,22 +16,18 @@ public enum APILexicalModelFetchError: Error {
|
|||
}
|
||||
|
||||
public class APILexicalModelRepository: LexicalModelRepository {
|
||||
private let languagesAPIURL = URLComponents(string: "https://api.keyman.com/cloud/4.0/languages")!
|
||||
private let modelsAPIURL = URLComponents(string: "https://api.keyman.com/model")! //?q=bcp47:en
|
||||
|
||||
public weak var delegate: LexicalModelRepositoryDelegate?
|
||||
public private(set) var languages: [String: Language]?
|
||||
public private(set) var lexicalModels: [String: LexicalModel]?
|
||||
private(set) var options: Options?
|
||||
|
||||
public func fetch(completionHandler: CompletionHandler?) {
|
||||
let deviceType = UIDevice.current.userInterfaceIdiom == .phone ? "iphone" : "ipad"
|
||||
let keymanVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
var urlComponents = languagesAPIURL
|
||||
var urlComponents = modelsAPIURL
|
||||
urlComponents.queryItems = [
|
||||
URLQueryItem(name: "dateformat", value: "seconds"),
|
||||
URLQueryItem(name: "device", value: deviceType),
|
||||
URLQueryItem(name: "version", value: keymanVersion),
|
||||
URLQueryItem(name: "languageidtype", value: "bcp47")
|
||||
URLQueryItem(name: "q", value: "bcp47:en") //+languages?[0]
|
||||
]
|
||||
log.info("Connecting to Keyman cloud: \(urlComponents.url!).")
|
||||
let task = URLSession.shared.dataTask(with: urlComponents.url!) { (data, response, error) in
|
||||
|
|
@ -74,23 +70,22 @@ public class APILexicalModelRepository: LexicalModelRepository {
|
|||
return
|
||||
}
|
||||
|
||||
options = result.options
|
||||
languages = Dictionary(uniqueKeysWithValues: result.languages.map { ($0.id, $0) })
|
||||
|
||||
let lexicalModelsWithID = result.languages.flatMap { language in
|
||||
language.lexicalModels?.map { kb in (kb.id, kb) } ?? []
|
||||
language.lexicalModels?.map { lm in (lm.id, lm) } ?? []
|
||||
}
|
||||
lexicalModels = Dictionary(lexicalModelsWithID) { old, new in
|
||||
var kb = old
|
||||
var lm = old
|
||||
if old.languages == nil {
|
||||
kb.languages = new.languages
|
||||
return kb
|
||||
lm.languages = new.languages
|
||||
return lm
|
||||
}
|
||||
if let newLanguages = new.languages {
|
||||
let oldLanguageIDs = Set(old.languages!.map { $0.id })
|
||||
kb.languages!.append(contentsOf: newLanguages.filter { !oldLanguageIDs.contains($0.id) })
|
||||
let oldLanguageIDs = Set(old.languages!.map { $0 })
|
||||
lm.languages!.append(contentsOf: newLanguages.filter { !oldLanguageIDs.contains($0) })
|
||||
}
|
||||
return kb
|
||||
return lm
|
||||
}
|
||||
|
||||
log.info("Request completed -- \(result.languages.count) languages.")
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public extension LexicalModelRepository {
|
|||
}
|
||||
|
||||
// If the lexicalModel (still) supports the requested language, use that one.
|
||||
guard let language = lexicalModel.languages?.first(where: {$0.id == languageID}) ??
|
||||
guard let language = lexicalModel.languages?.first(where: {$0 == languageID}) ??
|
||||
// Otherwise, just use the first language listed for the lexicalModel.
|
||||
lexicalModel.languages?.first ??
|
||||
// In cases where the lexicalModel fails to specify any language, use the requested one if it's in the collection.
|
||||
|
|
@ -35,7 +35,7 @@ public extension LexicalModelRepository {
|
|||
return nil
|
||||
}
|
||||
|
||||
return InstallableLexicalModel(lexicalModel: lexicalModel, language: language, isCustom: false)
|
||||
return InstallableLexicalModel(lexicalModel: lexicalModel, languageID: language as! String, isCustom: false)
|
||||
}
|
||||
|
||||
public func fetch(completionHandler: CompletionHandler? = nil) {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
cell.selectionStyle = .none
|
||||
let lm = userLexicalModels[indexPath.row]
|
||||
|
||||
cell.textLabel?.text = lm.languageName
|
||||
cell.textLabel?.text = lm.languageID // maybe do a lookup for lm.languageName
|
||||
cell.detailTextLabel?.text = lm.name
|
||||
cell.tag = indexPath.row
|
||||
|
||||
|
|
@ -326,7 +326,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
}
|
||||
|
||||
private func checkUpdates() -> Bool {
|
||||
if Manager.shared.apiLexicalModelRepository.languages == nil {
|
||||
if Manager.shared.apiLexicalModelRepository.lexicalModels == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
func showAddLexicalModel() {
|
||||
let button: UIButton? = (navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
|
||||
button?.isEnabled = false
|
||||
let vc = LanguageViewController(Manager.shared.apiLexicalModelRepository)
|
||||
let vc = LanguageViewController(Manager.shared.apiLexicalModelRepository) //may need to be different for models
|
||||
navigationController?.pushViewController(vc, animated: true)
|
||||
setIsDoneButtonEnabled(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -651,7 +651,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
|
|||
id: lexicalModelID,
|
||||
name: name,
|
||||
languageID: languageId,
|
||||
languageName: languageName,
|
||||
// languageName: languageName,
|
||||
version: version,
|
||||
isCustom: false))
|
||||
}
|
||||
|
|
@ -958,78 +958,77 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
|
|||
languageID: String,
|
||||
isUpdate: Bool,
|
||||
fetchRepositoryIfNeeded: Bool = true) {
|
||||
guard let lexicalModels = apiLexicalModelRepository.lexicalModels,
|
||||
let options = apiLexicalModelRepository.options
|
||||
else {
|
||||
if fetchRepositoryIfNeeded {
|
||||
log.info("Fetching repository from API for lexical model download")
|
||||
apiLexicalModelRepository.fetch { error in
|
||||
if let error = error {
|
||||
self.downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
} else {
|
||||
log.info("Fetched repository. Continuing with lexical model download.")
|
||||
self.downloadLexicalModel(withID: lexicalModelID,
|
||||
languageID: languageID,
|
||||
isUpdate: isUpdate,
|
||||
fetchRepositoryIfNeeded: false)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
let message = "Lexical model repository not yet fetched"
|
||||
let error = NSError(domain: "Keyman", code: 0, userInfo: [NSLocalizedDescriptionKey: message])
|
||||
downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
return
|
||||
}
|
||||
// guard let lexicalModels = apiLexicalModelRepository.lexicalModels,
|
||||
// else {
|
||||
// if fetchRepositoryIfNeeded {
|
||||
// log.info("Fetching repository from API for lexical model download")
|
||||
// apiLexicalModelRepository.fetch { error in
|
||||
// if let error = error {
|
||||
// self.downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
// } else {
|
||||
// log.info("Fetched repository. Continuing with lexical model download.")
|
||||
// self.downloadLexicalModel(withID: lexicalModelID,
|
||||
// languageID: languageID,
|
||||
// isUpdate: isUpdate,
|
||||
// fetchRepositoryIfNeeded: false)
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// let message = "Lexical model repository not yet fetched"
|
||||
// let error = NSError(domain: "Keyman", code: 0, userInfo: [NSLocalizedDescriptionKey: message])
|
||||
// downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
// return
|
||||
// }
|
||||
|
||||
guard let lexicalModel = apiLexicalModelRepository.installableLexicalModel(withID: lexicalModelID, languageID: languageID),
|
||||
let filename = lexicalModels[lexicalModelID]?.filename
|
||||
else {
|
||||
let message = "Lexical model not found with id: \(lexicalModelID), languageID: \(languageID)"
|
||||
let error = NSError(domain: "Keyman", code: 0,
|
||||
userInfo: [NSLocalizedDescriptionKey: message])
|
||||
downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
return
|
||||
}
|
||||
|
||||
guard downloadQueue == nil else {
|
||||
let error = NSError(domain: "Keyman", code: 0,
|
||||
userInfo: [NSLocalizedDescriptionKey: "Download queue is busy"])
|
||||
downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels : [lexicalModel]
|
||||
return
|
||||
}
|
||||
|
||||
guard reachability.currentReachabilityStatus() != NotReachable else {
|
||||
let error = NSError(domain: "Keyman", code: 0,
|
||||
userInfo: [NSLocalizedDescriptionKey: "No internet connection"])
|
||||
downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels : [lexicalModel]
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Storage.active.lexicalModelDir(forID: lexicalModelID),
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
log.error("Could not create dir for download: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
let lexicalModelURL = options.lexicalModelBaseURL?.appendingPathComponent(filename)
|
||||
|
||||
// TODO: Better typing
|
||||
downloadQueue = HTTPDownloader(self)
|
||||
let commonUserData: [String: Any] = [
|
||||
Key.lexicalModelInfo: [lexicalModel],
|
||||
Key.update: isUpdate
|
||||
]
|
||||
downloadQueue!.userInfo = commonUserData
|
||||
|
||||
let request = HTTPDownloadRequest(url: lexicalModelURL!, userInfo: commonUserData)
|
||||
request.destinationFile = Storage.active.lexicalModelURL(for: lexicalModel).path
|
||||
request.tag = 0
|
||||
downloadQueue!.addRequest(request)
|
||||
|
||||
downloadQueue!.run()
|
||||
// guard let lexicalModel = apiLexicalModelRepository.installableLexicalModel(withID: lexicalModelID, languageID: languageID),
|
||||
// let filename = lexicalModels[lexicalModelID]?.filename
|
||||
// else {
|
||||
// let message = "Lexical model not found with id: \(lexicalModelID), languageID: \(languageID)"
|
||||
// let error = NSError(domain: "Keyman", code: 0,
|
||||
// userInfo: [NSLocalizedDescriptionKey: message])
|
||||
// downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// guard downloadQueue == nil else {
|
||||
// let error = NSError(domain: "Keyman", code: 0,
|
||||
// userInfo: [NSLocalizedDescriptionKey: "Download queue is busy"])
|
||||
// downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels : [lexicalModel]
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// guard reachability.currentReachabilityStatus() != NotReachable else {
|
||||
// let error = NSError(domain: "Keyman", code: 0,
|
||||
// userInfo: [NSLocalizedDescriptionKey: "No internet connection"])
|
||||
// downloadFailed(forKeyboards: [], error: error) //??? forLexicalModels : [lexicalModel]
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// do {
|
||||
// try FileManager.default.createDirectory(at: Storage.active.lexicalModelDir(forID: lexicalModelID),
|
||||
// withIntermediateDirectories: true)
|
||||
// } catch {
|
||||
// log.error("Could not create dir for download: \(error)")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let lexicalModelURL = lexicalModelBaseURL?.appendingPathComponent(filename)
|
||||
//
|
||||
// // TODO: Better typing
|
||||
// downloadQueue = HTTPDownloader(self)
|
||||
// let commonUserData: [String: Any] = [
|
||||
// Key.lexicalModelInfo: [lexicalModel],
|
||||
// Key.update: isUpdate
|
||||
// ]
|
||||
// downloadQueue!.userInfo = commonUserData
|
||||
//
|
||||
// let request = HTTPDownloadRequest(url: lexicalModelURL!, userInfo: commonUserData)
|
||||
// request.destinationFile = Storage.active.lexicalModelURL(for: lexicalModel).path
|
||||
// request.tag = 0
|
||||
// downloadQueue!.addRequest(request)
|
||||
//
|
||||
// downloadQueue!.run()
|
||||
}
|
||||
|
||||
private func lexicalModelFontURLs(forFont font: Font?, options: Options) -> [URL] {
|
||||
|
|
@ -1083,15 +1082,14 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
|
|||
}
|
||||
}
|
||||
|
||||
/// Assumes that lexical model has font and oskFont set and ignores fonts contained in Language.
|
||||
private func downloadLexicalModel(_ lexicalModelAPI: LexicalModelAPICall) {
|
||||
let lexicalModel = lexicalModelAPI.lexicalModel
|
||||
let lexicalModel = lexicalModelAPI.lexicalModels[0]
|
||||
let installableLexicalModels = lexicalModel.languages!.map { language in
|
||||
InstallableLexicalModel(lexicalModel: lexicalModel, language: language, isCustom: true)
|
||||
InstallableLexicalModel(lexicalModel: lexicalModel, languageID: language, isCustom: true)
|
||||
}
|
||||
|
||||
let filename = lexicalModel.filename
|
||||
let lexicalModelURL = lexicalModelAPI.options.lexicalModelBaseURL!.appendingPathComponent(filename)
|
||||
let packageFilename = lexicalModel.packageFilename
|
||||
let lexicalModelURL = URL(string: "https://api.keyman.com/model")!.appendingPathComponent(packageFilename)
|
||||
|
||||
if downloadQueue != nil {
|
||||
// Download queue is active.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ public struct InstallableLexicalModel: Codable {
|
|||
public var id: String
|
||||
public var name: String
|
||||
public var languageID: String
|
||||
public var languageName: String
|
||||
public var version: String
|
||||
public var isCustom: Bool
|
||||
|
||||
|
|
@ -24,22 +23,21 @@ public struct InstallableLexicalModel: Codable {
|
|||
public init(id: String,
|
||||
name: String,
|
||||
languageID: String,
|
||||
languageName: String,
|
||||
version: String,
|
||||
isCustom: Bool) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.languageID = languageID
|
||||
self.languageName = languageName
|
||||
// self.languageName = languageName
|
||||
self.version = version
|
||||
self.isCustom = isCustom
|
||||
}
|
||||
|
||||
public init(lexicalModel: LexicalModel, language: Language, isCustom: Bool) {
|
||||
public init(lexicalModel: LexicalModel, languageID: String, isCustom: Bool) {
|
||||
self.id = lexicalModel.id
|
||||
self.name = lexicalModel.name
|
||||
self.languageID = language.id
|
||||
self.languageName = language.name
|
||||
self.languageID = languageID
|
||||
// self.languageName = language.name
|
||||
self.version = lexicalModel.version
|
||||
self.isCustom = isCustom
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class KMPLexicalModel
|
|||
for language in self.languages {
|
||||
let lexicalModel = InstallableLexicalModel(id: lexicalModelId!, name: name!,
|
||||
languageID: language.languageId,
|
||||
languageName: language.name,
|
||||
// languageName: language.name,
|
||||
version: version,
|
||||
isCustom: true) //update this based on adhoc vs api
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ public struct LexicalModel: Codable {
|
|||
public var id: String
|
||||
|
||||
/// Name of the lexicalModel. `.js` file which should be appended to `Options.lexicalModel.BaseURI`.
|
||||
public var filename: String
|
||||
/// Name of the lexicalModel KMP file.
|
||||
public var packageFilename: String
|
||||
|
||||
/// The lexicalModel. is the recommended default for the language.
|
||||
public var isDefault: Bool
|
||||
|
|
@ -31,13 +32,13 @@ public struct LexicalModel: Codable {
|
|||
/// Dot-decimal version number of the lexicalModel..
|
||||
public var version: String
|
||||
|
||||
/// Language objects linked to the lexicalModel..
|
||||
public var languages: [Language]?
|
||||
/// Tags of languages supported by this lexicalModel..
|
||||
public var languages: [String]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case id
|
||||
case filename
|
||||
case packageFilename
|
||||
case isDefault = "default"
|
||||
case lastModified
|
||||
case fileSize
|
||||
|
|
@ -47,15 +48,15 @@ public struct LexicalModel: Codable {
|
|||
|
||||
public init(name: String,
|
||||
id: String,
|
||||
filename: String,
|
||||
packageFilename: String,
|
||||
isDefault: Bool?,
|
||||
lastModified: Date,
|
||||
fileSize: Int?,
|
||||
version: String,
|
||||
languages: [Language]?) {
|
||||
languages: [String]?) {
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.filename = filename
|
||||
self.packageFilename = packageFilename
|
||||
self.isDefault = isDefault ?? false
|
||||
self.lastModified = lastModified
|
||||
self.fileSize = fileSize
|
||||
|
|
@ -68,17 +69,17 @@ public struct LexicalModel: Codable {
|
|||
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
let id = try container.decode(String.self, forKey: .id)
|
||||
let filename = try container.decode(String.self, forKey: .filename)
|
||||
let packageFilename = try container.decode(String.self, forKey: .packageFilename)
|
||||
let isDefault = try container.decodeIfPresent(Bool.self, forKey: .isDefault)
|
||||
// TODO: Handle both seconds and ISO 8601
|
||||
let lastModified = try container.decode(Date.self, forKey: .lastModified)
|
||||
let fileSize = try container.decodeIfPresent(Int.self, forKey: .fileSize)
|
||||
let version = try container.decode(String.self, forKey: .version)
|
||||
let languages = try container.decodeIfPresent([Language].self, forKey: .languages)
|
||||
let languages = try container.decodeIfPresent([String].self, forKey: .languages)
|
||||
|
||||
self.init(name: name,
|
||||
id: id,
|
||||
filename: filename,
|
||||
packageFilename: packageFilename,
|
||||
isDefault: isDefault,
|
||||
lastModified: lastModified,
|
||||
fileSize: fileSize,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,18 @@
|
|||
import Foundation
|
||||
|
||||
struct LexicalModelAPICall: Codable {
|
||||
let options: Options
|
||||
let lexicalModel: LexicalModel
|
||||
let lexicalModels: [LexicalModel]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case lexicalModels
|
||||
}
|
||||
|
||||
enum LexicalModelCodingKeys: String, CodingKey {
|
||||
case lexicalModel
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let lexicalModelContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.lexicalModels = try lexicalModelContainer.decode([LexicalModel].self, forKey: .lexicalModels)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue