Index: iOS/AddSiteController.swift ================================================================== --- iOS/AddSiteController.swift +++ iOS/AddSiteController.swift @@ -60,32 +60,20 @@ UIAlertAction(title: "OK", style: .default, handler: nil)) controller.present(alert, animated: true, completion: nil) } @IBAction func done(_ sender: Any) { - guard let name = nameField?.text?.ofObject else { return } - guard let lengthString = lengthField?.text?.ofObject else { return } + guard let name = nameField?.text else { return } + guard let lengthString = lengthField?.text else { return } - guard name.length > 0 else { + guard name.count > 0 else { showAlert(controller: self, title: "Name missing", message: "Please enter a name.") return } - var lengthValid = true - var length: size_t = 0 - OFException.try({ - length = lengthString.decimalValue - - if length < 3 || length > 64 { - lengthValid = false - } - }, catch: { (OFException) in - lengthValid = false - }) - - guard lengthValid else { + guard let length = UInt(lengthString), length >= 3, length <= 64 else { showAlert(controller: self, title: "Invalid length", message: "Please enter a number between 3 and 64.") return } @@ -95,14 +83,13 @@ showAlert(controller: self, title: "Site Already Exists", message: "Please pick a name that does not exist yet.") return } - let keyFile = self.keyFile?.ofObject siteStorage.setSite(name, length: length, - legacy: legacySwitch?.isOn ?? false, - keyFile: keyFile) + isLegacy: legacySwitch?.isOn ?? false, + keyFile: self.keyFile) mainViewController?.reset() navigationController?.popViewController(animated: true) } @IBAction func cancel(_ sender: Any) { Index: iOS/MainViewController.swift ================================================================== --- iOS/MainViewController.swift +++ iOS/MainViewController.swift @@ -23,11 +23,11 @@ import UIKit import ObjFW class MainViewController: UIViewController, UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource { - public var sites = OFArray() + public var sites: [String] = [] public var siteStorage = SiteStorage() @IBOutlet var searchBar: UISearchBar? @IBOutlet var tableView: UITableView? override func viewDidLoad() { @@ -49,16 +49,16 @@ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "site") ?? UITableViewCell(style: .default, reuseIdentifier: "site") - cell.textLabel?.text = sites[indexPath.row].nsObject + cell.textLabel?.text = sites[indexPath.row] return cell } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { - sites = siteStorage.sites(withFilter: searchBar.text?.ofObject) + sites = siteStorage.sites(withFilter: searchBar.text) tableView?.reloadData() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { Index: iOS/ShowDetailsController.swift ================================================================== --- iOS/ShowDetailsController.swift +++ iOS/ShowDetailsController.swift @@ -30,14 +30,14 @@ @IBOutlet var legacySwitch: UISwitch? @IBOutlet var keyFileField: UITextField? @IBOutlet var passphraseField: UITextField? public var mainViewController: MainViewController? - private var name: OFString = "".ofObject - private var length: size_t = 0 + private var name: String = "" + private var length: UInt = 0 private var isLegacy: Bool = false - private var keyFile: OFString? = nil + private var keyFile: String? = nil override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) guard let mainViewController = self.mainViewController else { return } @@ -48,14 +48,14 @@ name = mainViewController.sites[indexPath.row] length = siteStorage.length(forSite: name) isLegacy = siteStorage.isLegacy(site: name) keyFile = siteStorage.keyFile(forSite: name) - nameField?.text = name.nsObject + nameField?.text = name lengthField?.text = "\(length)" legacySwitch?.isOn = isLegacy - keyFileField?.text = keyFile?.nsObject + keyFileField?.text = keyFile tableView.deselectRow(at: indexPath, animated: true) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -132,22 +132,23 @@ } private func generateWithCallback(_ block: (_: NSMutableString) -> ()) { let generator: PasswordGenerator = isLegacy ? LegacyPasswordGenerator() : NewPasswordGenerator() - generator.site = name - generator.length = length + generator.site = name.ofObject + generator.length = size_t(length) if let keyFile = keyFile { guard let documentDirectory = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true).first else { print("Could not get key files: No documents directory") return } - let keyFilePath = documentDirectory.ofObject.appending(keyFile) + let keyFilePath = + documentDirectory.ofObject.appending(keyFile.ofObject) generator.keyFile = OFMutableData(contentsOfFile: keyFilePath) } let passphraseText = (passphraseField?.text ?? "") as NSString let passphrase = of_strdup(passphraseText.utf8String!)! Index: iOS/SiteStorage.swift ================================================================== --- iOS/SiteStorage.swift +++ iOS/SiteStorage.swift @@ -22,20 +22,17 @@ import ObjFW import ObjFWBridge class SiteStorage: OFObject { - private typealias Storage = - OFMutableDictionary> - - private static let lengthField = OFNumber(uInt8: 0) - private static let legacyField = OFNumber(uInt8: 1) - private static let keyFileField = OFNumber(uInt8: 2) + private static let lengthField = NSNumber(value: 0) + private static let legacyField = NSNumber(value: 1) + private static let keyFileField = NSNumber(value: 2) private var path: OFString - private var storage: Storage - private var sites: OFArray + private var storage: [String: [NSNumber: AnyObject]] + private var sites: [String] override init() { let fileManager = OFFileManager.default let userDataPath = OFSystemInfo.userDataPath! @@ -43,93 +40,82 @@ fileManager.createDirectory(atPath: userDataPath) } let path = userDataPath.appendingPathComponent( OFString(utf8String: "sites.msgpack")) - var storage: Storage? = nil - OFException.try({ - storage = OFData(contentsOfFile: path).messagePackValue as? Storage - }, catch: { (OFException) in - storage = OFMutableDictionary() - }) - - self.path = path - self.storage = storage! - self.sites = self.storage.allKeys.sorted - } - - func sites(withFilter filter: OFString?) -> OFArray { - // FIXME: We need case folding here, but there is no method for it yet. - let filter = filter?.lowercase - - return storage.allKeys.sorted.filteredArray({ - (name: Any, index: size_t) -> Bool in - if filter == nil { - return true - } - - let name = name as! OFString - return name.lowercase.contains(filter!) - }) - } - - func hasSite(_ name: OFString) -> Bool { - return (storage[name] != nil) - } - - func length(forSite name: OFString) -> size_t { - guard let site = storage[name] else { - OFInvalidArgumentException().throw() - } - - return (site[SiteStorage.lengthField] as! OFNumber).sizeValue - } - - func isLegacy(site name: OFString) -> Bool { - guard let site = storage[name] else { - OFInvalidArgumentException().throw() - } - - return (site[SiteStorage.legacyField] as! OFNumber).boolValue - } - - func keyFile(forSite name: OFString) -> OFString? { - guard let site = storage[name] else { - OFInvalidArgumentException().throw() - } - - let keyFile = site[SiteStorage.keyFileField] - if keyFile is OFNull { - return nil - } - - return keyFile as? OFString - } - - func setSite(_ name: OFString, length: size_t, legacy: Bool, - keyFile: OFString?) { - let siteDictionary = OFMutableDictionary() - - siteDictionary.setObject(OFNumber(size: length), - forKey: SiteStorage.lengthField) - siteDictionary.setObject(OFNumber(bool: legacy), - forKey: SiteStorage.legacyField) - if keyFile != nil { - siteDictionary.setObject(keyFile!, forKey: SiteStorage.keyFileField) - } - - siteDictionary.makeImmutable() - storage.setObject(siteDictionary, forKey: name) - - self.update() - } - - func removeSite(_ name: OFString) { - self.storage.removeObject(forKey: name) - self.update() - } - - private func update() { - storage.messagePackRepresentation.write(toFile: path) - sites = storage.allKeys.sorted + + var storage: [String: [NSNumber: AnyObject]]? = nil + OFException.try({ + let decoded = (OFData(contentsOfFile: path).messagePackValue) + as? OFDictionary> + storage = + (decoded?.nsObject as? [String: [NSNumber: AnyObject]]) ?? [:] + }, catch: { (OFException) in + storage = [:] + }) + + self.path = path + self.storage = storage! + self.sites = self.storage.keys.sorted() + } + + func sites(withFilter filter: String?) -> [String] { + return storage.keys.sorted().filter({ (name) in + if let filter = filter { + return name.localizedCaseInsensitiveContains(filter) + } + return true + }) + } + + func hasSite(_ name: String) -> Bool { + return (storage[name] != nil) + } + + func length(forSite name: String) -> UInt { + guard let site = storage[name] else { + OFInvalidArgumentException().throw() + } + + return (site[SiteStorage.lengthField] as! NSNumber).uintValue + } + + func isLegacy(site name: String) -> Bool { + guard let site = storage[name] else { return false } + return (site[SiteStorage.legacyField] as! NSNumber).boolValue + } + + func keyFile(forSite name: String) -> String? { + guard let site = storage[name] else { return nil } + + guard let keyFile = site[SiteStorage.keyFileField], !(keyFile is NSNull) + else { + return nil + } + + return keyFile as? String + } + + func setSite(_ name: String, length: UInt, isLegacy: Bool, + keyFile: String?) { + var siteDictionary: [NSNumber: AnyObject] = [ + SiteStorage.lengthField: NSNumber(value: length), + SiteStorage.legacyField: NSNumber(value: isLegacy), + ] + siteDictionary[SiteStorage.keyFileField] = keyFile as AnyObject? + + storage[name] = siteDictionary + self.update() + } + + func removeSite(_ name: String) { + storage[name] = nil + self.update() + } + + private func update() { + let ofStorage = (storage as NSDictionary).ofObject + + ofStorage.messagePackRepresentation.write(toFile: path) + sites = storage.keys.sorted() } }