Commit bc167948 by Demid Merzlyakov

WdtWeatherSource refactoring.

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