2018-10-11 07:13:19 +00:00
|
|
|
//
|
|
|
|
// WebServices.swift
|
|
|
|
// Passepartout
|
|
|
|
//
|
|
|
|
// Created by Davide De Rosa on 9/14/18.
|
2019-03-09 10:44:44 +00:00
|
|
|
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
|
2018-10-11 07:13:19 +00:00
|
|
|
//
|
2018-11-03 21:33:30 +00:00
|
|
|
// https://github.com/passepartoutvpn
|
2018-10-11 07:13:19 +00:00
|
|
|
//
|
|
|
|
// This file is part of Passepartout.
|
|
|
|
//
|
|
|
|
// Passepartout is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Passepartout is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import SwiftyBeaver
|
|
|
|
|
|
|
|
private let log = SwiftyBeaver.self
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public class WebServices {
|
2019-04-02 07:47:37 +00:00
|
|
|
public enum Group: String {
|
|
|
|
case network = "net"
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public enum Endpoint {
|
2018-10-11 07:13:19 +00:00
|
|
|
case network(Infrastructure.Name)
|
|
|
|
|
|
|
|
var path: String {
|
|
|
|
switch self {
|
|
|
|
case .network(let name):
|
2019-04-02 07:47:37 +00:00
|
|
|
return "\(Group.network.rawValue)/\(name.webName)"
|
2018-10-11 07:13:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public struct Response<T> {
|
|
|
|
public let value: T?
|
2018-10-11 07:13:19 +00:00
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public let lastModifiedString: String?
|
2018-10-11 07:13:19 +00:00
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public var lastModified: Date? {
|
2018-10-11 07:13:19 +00:00
|
|
|
guard let string = lastModifiedString else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return lmFormatter.date(from: string)
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public let isCached: Bool
|
2018-10-11 07:13:19 +00:00
|
|
|
}
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public static let shared = WebServices()
|
2018-10-11 07:13:19 +00:00
|
|
|
|
|
|
|
private static let lmFormatter: DateFormatter = {
|
|
|
|
let fmt = DateFormatter()
|
|
|
|
fmt.timeZone = TimeZone(abbreviation: "GMT")
|
|
|
|
fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz"
|
|
|
|
return fmt
|
|
|
|
}()
|
|
|
|
|
2019-03-18 10:29:28 +00:00
|
|
|
public func network(with name: Infrastructure.Name, ifModifiedSince lastModified: Date?, completionHandler: @escaping (Response<Infrastructure>?, Error?) -> Void) {
|
2018-10-11 07:13:19 +00:00
|
|
|
var request = get(.network(name))
|
|
|
|
if let lastModified = lastModified {
|
|
|
|
request.addValue(WebServices.lmFormatter.string(from: lastModified), forHTTPHeaderField: "If-Modified-Since")
|
|
|
|
}
|
|
|
|
parse(Infrastructure.self, request: request, completionHandler: completionHandler)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func get(_ endpoint: Endpoint) -> URLRequest {
|
2019-01-04 12:15:24 +00:00
|
|
|
let url = AppConstants.Web.url(path: "\(endpoint.path).json")
|
2018-10-11 17:47:04 +00:00
|
|
|
return URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: AppConstants.Web.timeout)
|
2018-10-11 07:13:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private func parse<T: Decodable>(_ type: T.Type, request: URLRequest, completionHandler: @escaping (Response<T>?, Error?) -> Void) {
|
|
|
|
log.debug("GET \(request.url!)")
|
|
|
|
log.debug("Request headers: \(request.allHTTPHeaderFields?.description ?? "")")
|
|
|
|
|
|
|
|
let session = URLSession(configuration: .default)
|
|
|
|
session.dataTask(with: request) { (data, response, error) in
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
|
|
log.error("Error (response): \(error?.localizedDescription ?? "nil")")
|
|
|
|
completionHandler(nil, error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let statusCode = httpResponse.statusCode
|
|
|
|
log.debug("Response status: \(statusCode)")
|
|
|
|
if let responseHeaders = httpResponse.allHeaderFields as? [String: String] {
|
|
|
|
log.debug("Response headers: \(responseHeaders)")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 304: cache hit
|
|
|
|
if statusCode == 304 {
|
|
|
|
log.debug("Response is cached")
|
|
|
|
completionHandler(Response(value: nil, lastModifiedString: nil, isCached: true), nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 200: cache miss
|
|
|
|
let value: T
|
|
|
|
let lastModifiedString: String?
|
|
|
|
guard statusCode == 200, let data = data else {
|
|
|
|
log.error("Error (network): \(error?.localizedDescription ?? "nil")")
|
|
|
|
completionHandler(nil, error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
value = try JSONDecoder().decode(type, from: data)
|
|
|
|
} catch let e {
|
|
|
|
log.error("Error (parsing): \(e)")
|
|
|
|
completionHandler(nil, error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
lastModifiedString = httpResponse.allHeaderFields["Last-Modified"] as? String
|
|
|
|
|
|
|
|
let response = Response(value: value, lastModifiedString: lastModifiedString, isCached: false)
|
|
|
|
completionHandler(response, nil)
|
|
|
|
}.resume()
|
|
|
|
}
|
|
|
|
}
|