Merge branch 'ios-load-model' into ios-load-model-integrate

This commit is contained in:
Joshua Horton 2019-04-24 15:25:31 +07:00 committed by GitHub
commit 5cbcbf384a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 130 additions and 118 deletions

View file

@ -1,3 +1,3 @@
github "marmelroy/Zip"
github "DaveWoodCom/XCGLogger" ~> 6.1.0
github "dennisweissmann/DeviceKit" ~> 1.3
github "dennisweissmann/DeviceKit" ~> 1.11

View file

@ -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"

View file

@ -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.

View file

@ -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.")

View file

@ -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) {

View file

@ -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)
}

View file

@ -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.

View file

@ -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
}

View file

@ -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

View file

@ -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,

View file

@ -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)
}
}