Commit 2cfcb142 by Demid Merzlyakov

Storage: CoreData: saving and loading works.

parent a760d66e
......@@ -21,6 +21,7 @@ public class LocationManager {
private let weatherUpdateSource: WeatherSource
private let healthSource: HealthSource
private let storage: Storage
private var defaultLocation = Location(deviceLocation: false,
coordinates: .init(latitude: 37.3230, longitude: -122.0322), // Cupertino
timeZone: TimeZone(abbreviation: "PST")!) {
......@@ -61,7 +62,7 @@ public class LocationManager {
guard let self = self else { return }
delegate.locationManager(self, updatedLocationsList: self.locations)
}
storage.save(locations: locations, selectedIndex: selectedLocationIndex)
}
}
......@@ -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 init(weatherUpdateSource: WeatherSource, healthSource: HealthSource) {
public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource, storage: Storage) {
self.weatherUpdateSource = weatherUpdateSource
self.healthSource = healthSource
self.deviceLocationMonitor = DeviceLocationMonitor()
self.storage = storage
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() {
......
......@@ -9,7 +9,7 @@ import Foundation
import CoreData
public class CoreDataStorage: Storage {
private let log = Logger(componentName: "CoreDataStorage")
private let log = Logger(componentName: "CoreDataStorage 💾")
private lazy var managedContext: NSManagedObjectContext? = {
persistentContainer.newBackgroundContext()
}()
......@@ -23,7 +23,8 @@ public class CoreDataStorage: Storage {
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
guard let self = self else { return }
guard let context = self.managedContext else {
......@@ -36,20 +37,28 @@ public class CoreDataStorage: Storage {
context.insert(coreAppData)
try self.save(context: context)
}
self.log.info("Save: success")
}
catch {
self.log.error("Error during saving: \(error)")
self.log.error("Save: error: \(error)")
}
}
}
public func load(completion: @escaping StorageCompletion) {
log.info("Load: start")
managedContext?.perform { [weak self] in
guard let self = self else { return }
guard let context = self.managedContext else {
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 {
completion(locations, selectedIndex, error)
}
......@@ -73,19 +82,29 @@ public class CoreDataStorage: Storage {
}
private func deleteAll(in context: NSManagedObjectContext) throws {
log.debug("Delete: start")
let fetchRequest: NSFetchRequest<CoreAppData> = CoreAppData.fetchRequest()
let appDataObjects = try context.fetch(fetchRequest)
if appDataObjects.count > 1 {
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 {
context.delete(appData)
}
log.info("Delete: success")
}
private func save(context: NSManagedObjectContext) throws {
log.info("Context save: start")
if context.hasChanges {
try context.save()
log.info("Context save: success")
}
else {
log.info("Context save: no need")
}
}
}
......@@ -68,7 +68,8 @@ struct CoreDataUtils {
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 {
return nil
}
......
......@@ -14,7 +14,7 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable
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.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 {
result.precipitationProbability = Percent(precipProb.uintValue)
}
......@@ -29,12 +29,12 @@ open class CoreCurrentWeather: _CoreCurrentWeather, CoreDataAppModelConvertable
result.sunrise = self.sunrise
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.moonset = self.moonset
result.approximateMoonrise = self.approximateMoonrise
result.moonState = try CoreDataUtils.appValue(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValue(name: "moonPhase", value: self.moonPhase, in: self)
result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
return result
}
......
......@@ -15,19 +15,19 @@ open class CoreDailyWeather: _CoreDailyWeather, CoreDataAppModelConvertable {
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.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 {
result.precipitationProbability = Percent(precipProb.uintValue)
}
result.sunrise = self.sunrise
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.moonset = self.moonset
result.moonState = try CoreDataUtils.appValue(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValue(name: "moonPhase", value: self.moonPhase, in: self)
result.moonState = try CoreDataUtils.appValueOptional(name: "moonState", value: self.moonState, in: self)
result.moonPhase = try CoreDataUtils.appValueOptional(name: "moonPhase", value: self.moonPhase, in: self)
return result
}
......
......@@ -15,7 +15,7 @@ open class CoreHourlyWeather: _CoreHourlyWeather, CoreDataAppModelConvertable {
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.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 {
result.precipitationProbability = Percent(precipProb.uintValue)
}
......
......@@ -47,6 +47,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable {
return nil
}
self.init(managedObjectContext: context)
self.deviceLocation = appModel.deviceLocation
self.lastWeatherUpdateDate = appModel.lastWeatherUpdateDate
if let coordinates = appModel.coordinates {
......@@ -61,7 +62,7 @@ open class CoreLocation: _CoreLocation, CoreDataAppModelConvertable {
self.nickname = appModel.nickname
self.zip = appModel.zip
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)})
......
......@@ -10,6 +10,6 @@ import Foundation
public typealias StorageCompletion = ([Location]?, Int?, Error?) -> ()
public protocol Storage {
func save(locations: [Location], selectedIndex: Int)
func save(locations: [Location], selectedIndex: Int?)
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