Commit 2cfcb142 by Demid Merzlyakov

Storage: CoreData: saving and loading works.

parent a760d66e
...@@ -21,6 +21,7 @@ public class LocationManager { ...@@ -21,6 +21,7 @@ public class LocationManager {
private let weatherUpdateSource: WeatherSource private let weatherUpdateSource: WeatherSource
private let healthSource: HealthSource private let healthSource: HealthSource
private let storage: Storage
private var defaultLocation = Location(deviceLocation: false, private var defaultLocation = Location(deviceLocation: false,
coordinates: .init(latitude: 37.3230, longitude: -122.0322), // Cupertino coordinates: .init(latitude: 37.3230, longitude: -122.0322), // Cupertino
timeZone: TimeZone(abbreviation: "PST")!) { timeZone: TimeZone(abbreviation: "PST")!) {
...@@ -61,7 +62,7 @@ public class LocationManager { ...@@ -61,7 +62,7 @@ public class LocationManager {
guard let self = self else { return } guard let self = self else { return }
delegate.locationManager(self, updatedLocationsList: self.locations) delegate.locationManager(self, updatedLocationsList: self.locations)
} }
storage.save(locations: locations, selectedIndex: selectedLocationIndex)
} }
} }
...@@ -125,14 +126,29 @@ public class LocationManager { ...@@ -125,14 +126,29 @@ public class LocationManager {
} }
} }
public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource(), healthSource: BlendHealthSource()) public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource(), healthSource: BlendHealthSource(), storage: CoreDataStorage())
public let maxLocationsCount = 12 public let maxLocationsCount = 12
public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource) { public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource, storage: Storage) {
self.weatherUpdateSource = weatherUpdateSource self.weatherUpdateSource = weatherUpdateSource
self.healthSource = healthSource self.healthSource = healthSource
self.deviceLocationMonitor = DeviceLocationMonitor() self.deviceLocationMonitor = DeviceLocationMonitor()
self.storage = storage
self.deviceLocationMonitor.delegate = self self.deviceLocationMonitor.delegate = self
storage.load { [weak self] (locations, selectedIndex, error) in
guard let self = self else { return }
guard error == nil else {
self.log.error("Error while loading locations: \(error!)")
return
}
guard let locations = locations else {
assertionFailure("Either error or locations have to be nil, not both")
return
}
self.locations = locations
self.selectedLocationIndex = selectedIndex
}
} }
public func updateAllWeatherIfNeeded() { public func updateAllWeatherIfNeeded() {
......
...@@ -9,7 +9,7 @@ import Foundation ...@@ -9,7 +9,7 @@ import Foundation
import CoreData import CoreData
public class CoreDataStorage: Storage { public class CoreDataStorage: Storage {
private let log = Logger(componentName: "CoreDataStorage") private let log = Logger(componentName: "CoreDataStorage 💾")
private lazy var managedContext: NSManagedObjectContext? = { private lazy var managedContext: NSManagedObjectContext? = {
persistentContainer.newBackgroundContext() persistentContainer.newBackgroundContext()
}() }()
...@@ -23,7 +23,8 @@ public class CoreDataStorage: Storage { ...@@ -23,7 +23,8 @@ public class CoreDataStorage: Storage {
return container return container
}() }()
public func save(locations: [Location], selectedIndex: Int) { public func save(locations: [Location], selectedIndex: Int?) {
log.info("Save: start")
managedContext?.perform { [weak self] in managedContext?.perform { [weak self] in
guard let self = self else { return } guard let self = self else { return }
guard let context = self.managedContext else { guard let context = self.managedContext else {
...@@ -36,20 +37,28 @@ public class CoreDataStorage: Storage { ...@@ -36,20 +37,28 @@ public class CoreDataStorage: Storage {
context.insert(coreAppData) context.insert(coreAppData)
try self.save(context: context) try self.save(context: context)
} }
self.log.info("Save: success")
} }
catch { catch {
self.log.error("Error during saving: \(error)") self.log.error("Save: error: \(error)")
} }
} }
} }
public func load(completion: @escaping StorageCompletion) { public func load(completion: @escaping StorageCompletion) {
log.info("Load: start")
managedContext?.perform { [weak self] in managedContext?.perform { [weak self] in
guard let self = self else { return } guard let self = self else { return }
guard let context = self.managedContext else { guard let context = self.managedContext else {
return return
} }
let completionOnMain: StorageCompletion = { (locations, selectedIndex, error) in let completionOnMain: StorageCompletion = { [weak self] (locations, selectedIndex, error) in
if error != nil {
self?.log.error("Load: error.")
}
else {
self?.log.info("Load: success. \(String(describing: locations?.count)) locations, selected: \(String(describing: selectedIndex))")
}
DispatchQueue.main.async { DispatchQueue.main.async {
completion(locations, selectedIndex, error) completion(locations, selectedIndex, error)
} }
...@@ -73,19 +82,29 @@ public class CoreDataStorage: Storage { ...@@ -73,19 +82,29 @@ public class CoreDataStorage: Storage {
} }
private func deleteAll(in context: NSManagedObjectContext) throws { private func deleteAll(in context: NSManagedObjectContext) throws {
log.debug("Delete: start")
let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest() let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
let appDataObjects = try context.fetch(fetchRequest) let appDataObjects = try context.fetch(fetchRequest)
if appDataObjects.count > 1 { if appDataObjects.count > 1 {
log.warning("Somehow we ended up with more than 1 CoreAppData objects in the DB... deleting them all.") log.warning("Somehow we ended up with more than 1 CoreAppData objects in the DB... deleting them all.")
} }
if appDataObjects.count > 0 {
log.debug("Delete: \(appDataObjects.count) objects...")
}
for appData in appDataObjects { for appData in appDataObjects {
context.delete(appData) context.delete(appData)
} }
log.info("Delete: success")
} }
private func save(context: NSManagedObjectContext) throws { private func save(context: NSManagedObjectContext) throws {
log.info("Context save: start")
if context.hasChanges { if context.hasChanges {
try context.save() try context.save()
log.info("Context save: success")
}
else {
log.info("Context save: no need")
} }
} }
} }
...@@ -68,7 +68,8 @@ struct CoreDataUtils { ...@@ -68,7 +68,8 @@ struct CoreDataUtils {
return result return result
} }
public static func appValue<T>(name: String, value: T.RawValue?, in entity: Any) throws -> T? where T: RawRepresentable { // I tried calling it just appValue, so that Swift would figure out wether to use this one or non-optional one, but it gets stuck in infinite recursion in this method occasionally.
public static func appValueOptional<T>(name: String, value: T.RawValue?, in entity: Any) throws -> T? where T: RawRepresentable {
guard let value = value else { guard let value = value else {
return nil return nil
} }
......
...@@ -14,7 +14,7 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable ...@@ -14,7 +14,7 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable
result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp") result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp")
result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp") result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed") result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValue(name: "windDirection", value: self.windDirection, in: self) result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability { if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue) result.precipitationProbability = Percent(precipProb.uintValue)
} }
...@@ -29,12 +29,12 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable ...@@ -29,12 +29,12 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable
result.sunrise = self.sunrise result.sunrise = self.sunrise
result.sunset = self.sunset result.sunset = self.sunset
result.sunState = try CoreDataUtils.appValue(name: "sunState", value: self.sunState, in: self) result.sunState = try CoreDataUtils.appValueOptional(name: "sunState", value: self.sunState, in: self)
result.moonrise = self.moonrise result.moonrise = self.moonrise
result.moonset = self.moonset result.moonset = self.moonset
result.approximateMoonrise = self.approximateMoonrise result.approximateMoonrise = self.approximateMoonrise
result.moonState = try CoreDataUtils.appValue(name: "moonState", value: self.moonState, in: self) result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValue(name: "moonPhase", value: self.moonPhase, in: self) result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
return result return result
} }
......
...@@ -15,19 +15,19 @@ open class CoreDailyWeather: _CoreDailyWeather, CoreDataAppModelConvertable { ...@@ -15,19 +15,19 @@ open class CoreDailyWeather: _CoreDailyWeather, CoreDataAppModelConvertable {
result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp") result.minTemp = try CoreDataUtils.measurement(from: self.minTemp, in: self, attributeName: "minTemp")
result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp") result.maxTemp = try CoreDataUtils.measurement(from: self.maxTemp, in: self, attributeName: "maxTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed") result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValue(name: "windDirection", value: self.windDirection, in: self) result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability { if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue) result.precipitationProbability = Percent(precipProb.uintValue)
} }
result.sunrise = self.sunrise result.sunrise = self.sunrise
result.sunset = self.sunset result.sunset = self.sunset
result.sunState = try CoreDataUtils.appValue(name: "sunState", value: self.sunState, in: self) result.sunState = try CoreDataUtils.appValueOptional(name: "sunState", value: self.sunState, in: self)
result.moonrise = self.moonrise result.moonrise = self.moonrise
result.moonset = self.moonset result.moonset = self.moonset
result.moonState = try CoreDataUtils.appValue(name: "moonState", value: self.moonState, in: self) result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValue(name: "moonPhase", value: self.moonPhase, in: self) result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
return result return result
} }
......
...@@ -15,7 +15,7 @@ open class CoreHourlyWeather: _CoreHourlyWeather, CoreDataAppModelConvertable { ...@@ -15,7 +15,7 @@ open class CoreHourlyWeather: _CoreHourlyWeather, CoreDataAppModelConvertable {
result.apparentTemp = try CoreDataUtils.measurement(from: self.apparentTemp, in: self, attributeName: "apparentTemp") result.apparentTemp = try CoreDataUtils.measurement(from: self.apparentTemp, in: self, attributeName: "apparentTemp")
result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed") result.windSpeed = try CoreDataUtils.measurement(from: self.windSpeed, in: self, attributeName: "windSpeed")
result.windDirection = try CoreDataUtils.appValue(name: "windDirection", value: self.windDirection, in: self) result.windDirection = try CoreDataUtils.appValueOptional(name: "windDirection", value: self.windDirection, in: self)
if let precipProb = self.precipitationProbability { if let precipProb = self.precipitationProbability {
result.precipitationProbability = Percent(precipProb.uintValue) result.precipitationProbability = Percent(precipProb.uintValue)
} }
......
...@@ -47,6 +47,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable { ...@@ -47,6 +47,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable {
return nil return nil
} }
self.init(managedObjectContext: context) self.init(managedObjectContext: context)
self.deviceLocation = appModel.deviceLocation
self.lastWeatherUpdateDate = appModel.lastWeatherUpdateDate self.lastWeatherUpdateDate = appModel.lastWeatherUpdateDate
if let coordinates = appModel.coordinates { if let coordinates = appModel.coordinates {
...@@ -61,7 +62,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable { ...@@ -61,7 +62,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable {
self.nickname = appModel.nickname self.nickname = appModel.nickname
self.zip = appModel.zip self.zip = appModel.zip
self.fipsCode = appModel.fipsCode self.fipsCode = appModel.fipsCode
self.timeZone = try CoreDataUtils.timeZoneToString(appModel.timeZone, in: self, attributeName: "timeZone")
self.today = skipIfError(attribute: "today", action: {try CoreCurrentWeather(context: context, appModel: appModel.today)}) self.today = skipIfError(attribute: "today", action: {try CoreCurrentWeather(context: context, appModel: appModel.today)})
......
...@@ -10,6 +10,6 @@ import Foundation ...@@ -10,6 +10,6 @@ import Foundation
public typealias StorageCompletion = ([Location]?, Int?, Error?) -> () public typealias StorageCompletion = ([Location]?, Int?, Error?) -> ()
public protocol Storage { public protocol Storage {
func save(locations: [Location], selectedIndex: Int) func save(locations: [Location], selectedIndex: Int?)
func load(completion: @escaping StorageCompletion) func load(completion: @escaping StorageCompletion)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment