Commit bc167948 by Demid Merzlyakov

WdtWeatherSource refactoring.

parent eb04a35b
...@@ -22,6 +22,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -22,6 +22,7 @@ public class WdtWeatherSource: WeatherSource {
private let log = Logger(componentName: "WdtWeatherSource") private let log = Logger(componentName: "WdtWeatherSource")
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"
private static let updateUrlHealth = "https://1weather.onelouder.com/feeds/onelouder/health.php"
/// This queue is needed to synchronize access to locationsBeingUpdated. Also, to make logging more clear. /// This queue is needed to synchronize access to locationsBeingUpdated. Also, to make logging more clear.
private let internalQueue: OperationQueue = { private let internalQueue: OperationQueue = {
...@@ -50,16 +51,13 @@ public class WdtWeatherSource: WeatherSource { ...@@ -50,16 +51,13 @@ public class WdtWeatherSource: WeatherSource {
} }
} }
/// This method should only be run from the internalQueue. private func buildURL(for location: Location, type: WeatherUpdateType, uvRequest: Bool) -> Result<URL, WdtWeatherSourceError> {
private func updateWeatherInternal(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
guard !locationsBeingUpdated.contains(location) else {
completion(nil, WdtWeatherSourceError.alreadyBeingUpdated)
return
}
locationsBeingUpdated.insert(location)
var urlToUse = WdtWeatherSource.updateUrlMega var urlToUse = WdtWeatherSource.updateUrlMega
if type == .preferIncremental && location.lastWeatherUpdateDate != nil { if uvRequest {
log.debug("Start update (health) for \(location)")
urlToUse = WdtWeatherSource.updateUrlHealth
}
else if type == .preferIncremental && location.lastWeatherUpdateDate != nil {
urlToUse = WdtWeatherSource.updateUrlMicro urlToUse = WdtWeatherSource.updateUrlMicro
log.debug("Start update (incremental) for \(location)") log.debug("Start update (incremental) for \(location)")
} }
...@@ -71,12 +69,10 @@ public class WdtWeatherSource: WeatherSource { ...@@ -71,12 +69,10 @@ public class WdtWeatherSource: WeatherSource {
log.error("Couldn't create URLComponents from \(urlToUse)") log.error("Couldn't create URLComponents from \(urlToUse)")
assertionFailure("Should never happen. The URL should be correct.") assertionFailure("Should never happen. The URL should be correct.")
// Should never happen, but a lot of stuff that should never happen happens from time to time, so let's at least handle it gracefully in prod. // Should never happen, but a lot of stuff that should never happen happens from time to time, so let's at least handle it gracefully in prod.
completion(nil, WdtWeatherSourceError.badUrl) return .failure(.badUrl)
return
} }
var queryParameters = [String: String]() var queryParameters = [String: String]()
if let coordinates = location.coordinates { if let coordinates = location.coordinates {
queryParameters["LAT"] = String(format: "%.5f", coordinates.latitude) queryParameters["LAT"] = String(format: "%.5f", coordinates.latitude)
queryParameters["LON"] = String(format: "%.5f", coordinates.longitude) queryParameters["LON"] = String(format: "%.5f", coordinates.longitude)
...@@ -88,45 +84,54 @@ public class WdtWeatherSource: WeatherSource { ...@@ -88,45 +84,54 @@ public class WdtWeatherSource: WeatherSource {
queryParameters["COUNTRY"] = location.countryName queryParameters["COUNTRY"] = location.countryName
} }
guard !queryParameters.isEmpty else { guard !queryParameters.isEmpty else {
completion(nil, WdtWeatherSourceError.insufficientLocationInfo)
log.error("Not enough information about location.") log.error("Not enough information about location.")
return return .failure(.insufficientLocationInfo)
} }
queryParameters["UNITS"] = "all" queryParameters["UNITS"] = "all"
urlComponents.queryItems = queryParameters.map { URLQueryItem(name: $0, value: $1) } urlComponents.queryItems = queryParameters.map { URLQueryItem(name: $0, value: $1) }
guard let url = urlComponents.url else { guard let url = urlComponents.url else {
log.error("Couldn't create URL with params: \(queryParameters)") log.error("Couldn't create URL with params: \(queryParameters)")
completion(nil, WdtWeatherSourceError.badUrl) return .failure(.badUrl)
return
} }
log.debug("Network request (\(location)): \(url)") log.debug("Network request (\(location)): \(url)")
return .success(url)
let urlSession = URLSession.shared }
let dataTask = urlSession.dataTask(with: url) { [weak self] (data, reponse, error) in
guard let self = self else { return } /// This method should only be run from the internalQueue.
guard let data = data else { private func updateWeatherInternal(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
self.log.debug("Network response (\(location)): error \(String(describing: error))") let urlBuildResult = buildURL(for: location, type: type, uvRequest: false)
completion(nil, WdtWeatherSourceError.networkError(error)) switch urlBuildResult {
return case .failure(let error):
} completion(nil, error)
let responseBodyString = String(data: data, encoding: .utf8) ?? "<couldn't show as string" return
self.log.debug("Network response (\(location)): \(responseBodyString)") case .success(let url):
let decoder = XMLDecoder() let urlSession = URLSession.shared
do { let dataTask = urlSession.dataTask(with: url) { [weak self] (data, reponse, error) in
let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data) guard let self = self else { return }
guard let newLocation = try self.applyChangesTo(location: location, from: locationResponse) else { guard let data = data else {
completion(nil, WdtWeatherSourceError.badServerResponse(error)) self.log.debug("Network response (\(location)): error \(String(describing: error))")
completion(nil, WdtWeatherSourceError.networkError(error))
return return
} }
let responseBodyString = String(data: data, encoding: .utf8) ?? "<couldn't show as string"
completion(newLocation, nil) self.log.debug("Network response (\(location)): \(responseBodyString)")
} let decoder = XMLDecoder()
catch { do {
completion(nil, WdtWeatherSourceError.badServerResponse(error)) let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data)
guard let newLocation = try self.applyChangesTo(location: location, from: locationResponse) else {
completion(nil, WdtWeatherSourceError.badServerResponse(error))
return
}
completion(newLocation, nil)
}
catch {
completion(nil, WdtWeatherSourceError.badServerResponse(error))
}
} }
dataTask.resume()
} }
dataTask.resume()
} }
private func applyChangesTo(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? { private func applyChangesTo(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? {
......
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