Commit d5522f2c by Demid Merzlyakov

WdtWeatherSource: add checks to avoid updating one location multiple times simultaneously.

parent aa1bfd9d
...@@ -10,10 +10,12 @@ import CoreLocation ...@@ -10,10 +10,12 @@ import CoreLocation
import XMLCoder import XMLCoder
public enum WdtWeatherSourceError: Error { public enum WdtWeatherSourceError: Error {
case insufficientLocationInfo
case badUrl case badUrl
case networkError(Error?) case networkError(Error?)
case badServerResponse(Error?) case badServerResponse(Error?)
case dataMergeError(String) case dataMergeError(String)
case alreadyBeingUpdated
} }
public class WdtWeatherSource: WeatherSource { public class WdtWeatherSource: WeatherSource {
...@@ -21,10 +23,39 @@ public class WdtWeatherSource: WeatherSource { ...@@ -21,10 +23,39 @@ public class WdtWeatherSource: WeatherSource {
private static let updateUrlMega = "https://1weather.onelouder.com/feeds/onelouder/mega.php" private static let updateUrlMega = "https://1weather.onelouder.com/feeds/onelouder/mega.php"
private static let updateUrlMicro = "https://1weather.onelouder.com/feeds/onelouder2/fm.php" private static let updateUrlMicro = "https://1weather.onelouder.com/feeds/onelouder2/fm.php"
/// This queue is needed to synchronize access to locationsBeingUpdated. Also, to make logging more clear.
private let internalQueue: OperationQueue = {
let queue = OperationQueue()
queue.name = "WdtWeatherSource Queue"
queue.maxConcurrentOperationCount = 1
return queue
}()
/// This is used
private var locationsBeingUpdated = Set<Location>()
public let weatherUpdateInterval = TimeInterval(15 * 60) // 15 minutes public let weatherUpdateInterval = TimeInterval(15 * 60) // 15 minutes
public func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion) { public func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion) {
log.debug("Start update.") internalQueue.addOperation { [weak self] in
let extendedCompletion: WeatherSourceCompletion = { [weak self] (updatedLocation, error) in
self?.internalQueue.addOperation {
completion(updatedLocation, error)
self?.locationsBeingUpdated.remove(location)
}
}
self?.updateWeatherInternal(for: location, completion: extendedCompletion)
}
}
/// This method should only be run from the internalQueue.
private func updateWeatherInternal(for location: Location, 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 { guard var urlComponents = URLComponents(string: WdtWeatherSource.updateUrlMega) else {
assertionFailure("Should never happen. The URL should be correct.") assertionFailure("Should never happen. The URL should be correct.")
return return
...@@ -42,6 +73,11 @@ public class WdtWeatherSource: WeatherSource { ...@@ -42,6 +73,11 @@ public class WdtWeatherSource: WeatherSource {
queryParameters["STATE"] = location.region queryParameters["STATE"] = location.region
queryParameters["COUNTRY"] = location.countryName queryParameters["COUNTRY"] = location.countryName
} }
guard !queryParameters.isEmpty else {
completion(nil, WdtWeatherSourceError.insufficientLocationInfo)
log.error("Not enough information about location.")
return
}
queryParameters["UNITS"] = "all" queryParameters["UNITS"] = "all"
urlComponents.queryItems = queryParameters.map { URLQueryItem(name: $0, value: $1) } urlComponents.queryItems = queryParameters.map { URLQueryItem(name: $0, value: $1) }
...@@ -49,6 +85,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -49,6 +85,7 @@ public class WdtWeatherSource: WeatherSource {
completion(nil, WdtWeatherSourceError.badUrl) completion(nil, WdtWeatherSourceError.badUrl)
return return
} }
log.debug("query params: \(queryParameters)")
let urlSession = URLSession.shared let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { [weak self] (data, reponse, error) in let dataTask = urlSession.dataTask(with: url) { [weak self] (data, reponse, error) in
...@@ -60,7 +97,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -60,7 +97,7 @@ public class WdtWeatherSource: WeatherSource {
let decoder = XMLDecoder() let decoder = XMLDecoder()
do { do {
let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data) let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data)
guard let newLocation = try self.update(location: location, from: locationResponse) else { guard let newLocation = try self.applyChangesTo(location: location, from: locationResponse) else {
completion(nil, WdtWeatherSourceError.badServerResponse(error)) completion(nil, WdtWeatherSourceError.badServerResponse(error))
return return
} }
...@@ -74,7 +111,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -74,7 +111,7 @@ public class WdtWeatherSource: WeatherSource {
dataTask.resume() dataTask.resume()
} }
func update(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? { private func applyChangesTo(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? {
guard let wdtLocation = locationResponse.locations.first else { guard let wdtLocation = locationResponse.locations.first else {
return nil return nil
} }
......
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