Commit 45c821a5 by Demid Merzlyakov

Update weather for all locations.

parent 237e05ae
......@@ -22,5 +22,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
LocationManager.shared.updateAllWeatherIfNeeded()
}
}
......@@ -211,10 +211,11 @@ extension DeviceLocationMonitor: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = (locations.min { $0.horizontalAccuracy < $1.horizontalAccuracy }) else {
log.info("updated location: no location")
log.debug("updated location: no location")
return
}
_lastKnownLocation = newLocation
log.debug("updated location: (\(newLocation.coordinate.latitude); \(newLocation.coordinate.longitude))")
DispatchQueue.main.async {
self.delegate?.deviceLocationMonitor(self, didUpdateLocation: DeviceLocation(location: self._lastKnownLocation))
}
......
......@@ -51,6 +51,7 @@ public class LocationManager {
self.selectedLocationIndex = newValue.count > 0 ? 0 : nil
}
else {
updateAllWeatherIfNeeded() //TODO: the whole flow is not optimal.
self.delegates.invoke { (delegate) in
delegate.locationManager(self, changedSelectedLocation: self.selectedLocation)
}
......@@ -84,9 +85,10 @@ public class LocationManager {
delegate.locationManager(self, changedSelectedLocation: newLocation)
}
}
self.updateWeather(for: newLocation)
updateAllWeatherIfNeeded()
}
}
public var selectedLocation: Location? {
get {
guard let index = selectedLocationIndex else {
......@@ -131,34 +133,52 @@ public class LocationManager {
self.deviceLocationMonitor.delegate = self
}
public func updateWeather(for location: Location?) {
public func updateAllWeatherIfNeeded() {
let locations = self.locations
guard locations.count > 0 else {
log.info("Update all: update default location if needed.")
updateWeather(for: defaultLocation, updateType: .full)
return
}
log.info("Update all \(locations.count) locations if needed...")
if let selectedLocation = self.selectedLocation {
updateWeather(for: selectedLocation, updateType: .full)
}
for location in locations {
if selectedLocation != location {
updateWeather(for: location, updateType: .preferIncremental)
}
}
}
public func updateWeather(for location: Location?, updateType: WeatherUpdateType) {
guard let location = location else {
log.warning("Update weather: empty location.")
return
}
if let lastTimeUpdated = location.lastWeatherUpdateDate {
guard Date().timeIntervalSince(lastTimeUpdated) >= weatherUpdateSource.weatherUpdateInterval else {
log.info("Update weather: fresh enough (last updated at \(lastTimeUpdated)), skip update.")
log.info("Update weather (\(location)): fresh enough (last updated at \(lastTimeUpdated)), skip update.")
return
}
}
log.info("Update weather for location: \(location)")
log.info("Update weather for: \(location); type: \(updateType)")
weatherUpdateSource.updateWeather(for: location) { [weak self] (updatedLocation, error) in
weatherUpdateSource.updateWeather(for: location, type: updateType) { [weak self] (updatedLocation, error) in
guard let self = self else { return }
guard let updatedLocation = updatedLocation else {
if let error = error {
self.log.error("Update weather error: \(error)")
self.log.error("Update weather (\(location)) error: \(error)")
}
else {
self.log.error("Update weather error: unknown error")
self.log.error("Update weather (\(location)) error: unknown error")
}
return
}
DispatchQueue.main.async {
self.log.info("Update weather finished for location: \(location)")
self.log.info("Update weather finished for \(location)")
if let indexToUpdate = self.locations.firstIndex(where: { $0 == updatedLocation }) {
self.locations[indexToUpdate] = updatedLocation
}
......
......@@ -36,7 +36,7 @@ public class WdtWeatherSource: WeatherSource {
public let weatherUpdateInterval = TimeInterval(15 * 60) // 15 minutes
public func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion) {
public func updateWeather(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
internalQueue.addOperation { [weak self] in
let extendedCompletion: WeatherSourceCompletion = { [weak self] (updatedLocation, error) in
self?.internalQueue.addOperation {
......@@ -44,19 +44,28 @@ public class WdtWeatherSource: WeatherSource {
self?.locationsBeingUpdated.remove(location)
}
}
self?.updateWeatherInternal(for: location, completion: extendedCompletion)
self?.updateWeatherInternal(for: location, type: type, completion: extendedCompletion)
}
}
/// This method should only be run from the internalQueue.
private func updateWeatherInternal(for location: Location, completion: @escaping WeatherSourceCompletion) {
private func updateWeatherInternal(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
guard !locationsBeingUpdated.contains(location) else {
completion(nil, WdtWeatherSourceError.alreadyBeingUpdated)
return
}
log.debug("Start update for \(location)")
guard var urlComponents = URLComponents(string: WdtWeatherSource.updateUrlMega) else {
locationsBeingUpdated.insert(location)
var urlToUse = WdtWeatherSource.updateUrlMega
if type == .preferIncremental && location.lastWeatherUpdateDate != nil {
urlToUse = WdtWeatherSource.updateUrlMicro
log.debug("Start update (incremental) for \(location)")
}
else {
log.debug("Start update (MEGA) for \(location)")
}
guard var urlComponents = URLComponents(string: urlToUse) else {
assertionFailure("Should never happen. The URL should be correct.")
return
}
......
......@@ -9,7 +9,15 @@ import Foundation
public typealias WeatherSourceCompletion = (Location?, Error?) -> ()
public enum WeatherUpdateType {
/// Request all the data the weather source can provide for a given location. Suitable for updating the location for the first time or for pull to refresh. Also for selected location.
case full
/// If the location has been requested before, so it has the majority of the data, but some of it may need to be refreshed, then the weather source is allowed to request a small incremental update (e.g. only the data that changes frequently). Otherwise the weather source should go for a full update.
case preferIncremental
}
public protocol WeatherSource {
var weatherUpdateInterval: TimeInterval { get }
func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion)
func updateWeather(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion)
}
......@@ -43,7 +43,7 @@ class ForecastViewModel: ViewModelProtocol {
}
public func updateWeather() {
locationManager.updateWeather(for: locationManager.selectedLocation)
locationManager.updateAllWeatherIfNeeded()
}
public func selectDailyWeatherAt(index:Int) {
......
......@@ -11,20 +11,21 @@ class TodayViewModel: ViewModelProtocol {
//Public
public weak var delegate:ViewModelDelegate?
private(set) var location:Location?
private let locationManager = LocationManager.shared
deinit {
LocationManager.shared.remove(delegate: self)
locationManager.remove(delegate: self)
Settings.shared.delegate.remove(delegate: self)
}
public init() {
self.location = LocationManager.shared.selectedLocation
LocationManager.shared.add(delegate: self)
locationManager.add(delegate: self)
Settings.shared.delegate.add(delegate: self)
}
public func updateWeather() {
LocationManager.shared.updateWeather(for: LocationManager.shared.selectedLocation)
locationManager.updateAllWeatherIfNeeded()
}
}
......
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