Merge branch 'optimize-serialization'

This commit is contained in:
Davide De Rosa 2018-11-04 15:14:57 +01:00
commit fafc34180f
4 changed files with 56 additions and 44 deletions

View File

@ -57,7 +57,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
func applicationWillResignActive(_ application: UIApplication) { func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
TransientStore.shared.serialize() // synchronize TransientStore.shared.serialize(withProfiles: true) // synchronize
} }
func applicationDidEnterBackground(_ application: UIApplication) { func applicationDidEnterBackground(_ application: UIApplication) {

View File

@ -421,7 +421,7 @@ extension OrganizerViewController {
extension OrganizerViewController: ConnectionServiceDelegate { extension OrganizerViewController: ConnectionServiceDelegate {
func connectionService(didAdd profile: ConnectionProfile) { func connectionService(didAdd profile: ConnectionProfile) {
TransientStore.shared.serialize() // add TransientStore.shared.serialize(withProfiles: false) // add
reloadModel() reloadModel()
tableView.reloadData() tableView.reloadData()
@ -444,14 +444,14 @@ extension OrganizerViewController: ConnectionServiceDelegate {
} }
func connectionService(didRename oldProfile: ConnectionProfile, to newProfile: ConnectionProfile) { func connectionService(didRename oldProfile: ConnectionProfile, to newProfile: ConnectionProfile) {
TransientStore.shared.serialize() // rename TransientStore.shared.serialize(withProfiles: false) // rename
reloadModel() reloadModel()
tableView.reloadData() tableView.reloadData()
} }
func connectionService(didRemoveProfileWithKey key: ConnectionService.ProfileKey) { func connectionService(didRemoveProfileWithKey key: ConnectionService.ProfileKey) {
TransientStore.shared.serialize() // delete TransientStore.shared.serialize(withProfiles: false) // delete
splitViewController?.serviceViewController?.hideProfileIfDeleted() splitViewController?.serviceViewController?.hideProfileIfDeleted()
} }
@ -459,13 +459,13 @@ extension OrganizerViewController: ConnectionServiceDelegate {
// XXX: deactivate + activate leads to a redundant serialization // XXX: deactivate + activate leads to a redundant serialization
func connectionService(willDeactivate profile: ConnectionProfile) { func connectionService(willDeactivate profile: ConnectionProfile) {
TransientStore.shared.serialize() // deactivate TransientStore.shared.serialize(withProfiles: false) // deactivate
tableView.reloadData() tableView.reloadData()
} }
func connectionService(didActivate profile: ConnectionProfile) { func connectionService(didActivate profile: ConnectionProfile) {
TransientStore.shared.serialize() // activate TransientStore.shared.serialize(withProfiles: false) // activate
tableView.reloadData() tableView.reloadData()
} }

View File

@ -115,8 +115,6 @@ class ConnectionService: Codable {
private var cache: [ProfileKey: ConnectionProfile] private var cache: [ProfileKey: ConnectionProfile]
private var pendingRemoval: Set<ProfileKey>
private(set) var activeProfileKey: ProfileKey? { private(set) var activeProfileKey: ProfileKey? {
willSet { willSet {
if let oldProfile = activeProfile { if let oldProfile = activeProfile {
@ -160,7 +158,6 @@ class ConnectionService: Codable {
preferences = EditablePreferences() preferences = EditablePreferences()
cache = [:] cache = [:]
pendingRemoval = []
} }
// MARK: Codable // MARK: Codable
@ -181,7 +178,6 @@ class ConnectionService: Codable {
preferences = try container.decode(EditablePreferences.self, forKey: .preferences) preferences = try container.decode(EditablePreferences.self, forKey: .preferences)
cache = [:] cache = [:]
pendingRemoval = []
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
@ -232,42 +228,42 @@ class ConnectionService: Codable {
func saveProfiles() { func saveProfiles() {
let encoder = JSONEncoder() let encoder = JSONEncoder()
ensureDirectoriesExistence()
for profile in cache.values {
saveProfile(profile, withEncoder: encoder, checkDirectories: false)
}
}
private func ensureDirectoriesExistence() {
let fm = FileManager.default let fm = FileManager.default
try? fm.createDirectory(at: providersURL, withIntermediateDirectories: false, attributes: nil) try? fm.createDirectory(at: providersURL, withIntermediateDirectories: false, attributes: nil)
try? fm.createDirectory(at: hostsURL, withIntermediateDirectories: false, attributes: nil) try? fm.createDirectory(at: hostsURL, withIntermediateDirectories: false, attributes: nil)
}
for key in pendingRemoval {
let url = profileURL(key) private func saveProfile(_ profile: ConnectionProfile, withEncoder encoder: JSONEncoder, checkDirectories: Bool) {
try? fm.removeItem(at: url) if checkDirectories {
if let cfg = configurationURL(for: key) { ensureDirectoriesExistence()
try? fm.removeItem(at: cfg)
}
} }
for entry in cache.values { do {
if let profile = entry as? ProviderConnectionProfile { let url = profileURL(ProfileKey(profile))
do { var optData: Data?
let url = profileURL(ProfileKey(.provider, entry.id)) if let providerProfile = profile as? ProviderConnectionProfile {
let data = try encoder.encode(profile) optData = try encoder.encode(providerProfile)
try data.write(to: url) } else if let hostProfile = profile as? HostConnectionProfile {
log.debug("Saved provider '\(profile.id)'") optData = try encoder.encode(hostProfile)
} catch let e { } else if let placeholder = profile as? PlaceholderConnectionProfile {
log.error("Could not save provider '\(profile.id)': \(e)")
continue
}
} else if let profile = entry as? HostConnectionProfile {
do {
let url = profileURL(ProfileKey(.host, entry.id))
let data = try encoder.encode(profile)
try data.write(to: url)
log.debug("Saved host '\(profile.id)'")
} catch let e {
log.error("Could not save host '\(profile.id)': \(e)")
continue
}
} else if let placeholder = entry as? PlaceholderConnectionProfile {
log.debug("Skipped \(placeholder.context) '\(placeholder.id)'") log.debug("Skipped \(placeholder.context) '\(placeholder.id)'")
} else {
fatalError("Attempting to add an unhandled profile type: \(type(of: profile))")
} }
guard let data = optData else {
return
}
try data.write(to: url)
log.debug("Serialized \(profile.context) profile '\(profile.id)'")
} catch let e {
log.warning("Could not serialize \(profile.context) profile '\(profile.id)': \(e)")
} }
} }
@ -336,13 +332,14 @@ class ConnectionService: Codable {
func addOrReplaceProfile(_ profile: ConnectionProfile, credentials: Credentials?) { func addOrReplaceProfile(_ profile: ConnectionProfile, credentials: Credentials?) {
let key = ProfileKey(profile) let key = ProfileKey(profile)
cache[key] = profile cache[key] = profile
pendingRemoval.remove(key)
try? setCredentials(credentials, for: profile) try? setCredentials(credentials, for: profile)
if cache.count == 1 { if cache.count == 1 {
activeProfileKey = key activeProfileKey = key
} }
delegate?.connectionService(didAdd: profile) delegate?.connectionService(didAdd: profile)
// serialization (can fail)
saveProfile(profile, withEncoder: JSONEncoder(), checkDirectories: true)
} }
@discardableResult func renameProfile(_ key: ProfileKey, to newId: String) -> ConnectionProfile? { @discardableResult func renameProfile(_ key: ProfileKey, to newId: String) -> ConnectionProfile? {
@ -392,14 +389,27 @@ class ConnectionService: Codable {
guard let profile = cache[key] else { guard let profile = cache[key] else {
return return
} }
cache.removeValue(forKey: key) cache.removeValue(forKey: key)
removeCredentials(for: profile) removeCredentials(for: profile)
pendingRemoval.insert(key)
if cache.isEmpty { if cache.isEmpty {
activeProfileKey = nil activeProfileKey = nil
} }
delegate?.connectionService(didRemoveProfileWithKey: key) delegate?.connectionService(didRemoveProfileWithKey: key)
// serialization (can fail)
do {
let fm = FileManager.default
if let cfg = configurationURL(for: key) {
try? fm.removeItem(at: cfg)
}
let url = profileURL(key)
try fm.removeItem(at: url)
log.debug("Deleted removed profile '\(profile.id)'")
} catch let e {
log.warning("Could not delete profile '\(profile.id)': \(e)")
}
} }
func containsProfile(_ key: ProfileKey) -> Bool { func containsProfile(_ key: ProfileKey) -> Bool {

View File

@ -77,8 +77,10 @@ class TransientStore {
} }
} }
func serialize() { func serialize(withProfiles: Bool) {
try? JSONEncoder().encode(service).write(to: TransientStore.serviceURL) try? JSONEncoder().encode(service).write(to: TransientStore.serviceURL)
service.saveProfiles() if withProfiles {
service.saveProfiles()
}
} }
} }