mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Fix lint issues.
This commit is contained in:
@@ -9,11 +9,11 @@ let package = Package(
|
||||
.library(
|
||||
name: "RSWeb",
|
||||
type: .dynamic,
|
||||
targets: ["RSWeb"]),
|
||||
targets: ["RSWeb"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../Parser"),
|
||||
.package(path: "../RSCore"),
|
||||
.package(path: "../RSCore")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -26,6 +26,6 @@ let package = Package(
|
||||
),
|
||||
.testTarget(
|
||||
name: "RSWebTests",
|
||||
dependencies: ["RSWeb"]),
|
||||
dependencies: ["RSWeb"])
|
||||
]
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ public struct CacheControlInfo: Codable, Equatable {
|
||||
public var canResume: Bool {
|
||||
Date() >= resumeDate
|
||||
}
|
||||
|
||||
|
||||
public init?(urlResponse: HTTPURLResponse) {
|
||||
guard let cacheControlValue = urlResponse.valueForHTTPHeaderField(HTTPResponseHeader.cacheControl) else {
|
||||
return nil
|
||||
@@ -35,7 +35,7 @@ public struct CacheControlInfo: Codable, Equatable {
|
||||
guard let maxAge = Self.parseMaxAge(value) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let d = Date()
|
||||
self.dateCreated = d
|
||||
self.maxAge = maxAge
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension Dictionary where Key == String, Value == String {
|
||||
public extension Dictionary where Key == String, Value == String {
|
||||
|
||||
/// Translates a dictionary into a string like `foo=bar¶m2=some%20thing`.
|
||||
var urlQueryString: String? {
|
||||
|
||||
@@ -11,12 +11,12 @@ import Foundation
|
||||
// Main thread only.
|
||||
|
||||
public extension Notification.Name {
|
||||
|
||||
|
||||
static let DownloadProgressDidChange = Notification.Name(rawValue: "DownloadProgressDidChange")
|
||||
}
|
||||
|
||||
public final class DownloadProgress {
|
||||
|
||||
|
||||
public var numberOfTasks = 0 {
|
||||
didSet {
|
||||
if numberOfTasks == 0 && numberRemaining != 0 {
|
||||
@@ -27,7 +27,7 @@ public final class DownloadProgress {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var numberRemaining = 0 {
|
||||
didSet {
|
||||
if numberRemaining != oldValue {
|
||||
@@ -46,22 +46,22 @@ public final class DownloadProgress {
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
||||
public var isComplete: Bool {
|
||||
assert(Thread.isMainThread)
|
||||
return numberRemaining < 1
|
||||
}
|
||||
|
||||
|
||||
public init(numberOfTasks: Int) {
|
||||
assert(Thread.isMainThread)
|
||||
self.numberOfTasks = numberOfTasks
|
||||
}
|
||||
|
||||
|
||||
public func addToNumberOfTasks(_ n: Int) {
|
||||
assert(Thread.isMainThread)
|
||||
numberOfTasks = numberOfTasks + n
|
||||
}
|
||||
|
||||
|
||||
public func addToNumberOfTasksAndRemaining(_ n: Int) {
|
||||
assert(Thread.isMainThread)
|
||||
numberOfTasks = numberOfTasks + n
|
||||
@@ -74,14 +74,14 @@ public final class DownloadProgress {
|
||||
numberRemaining = numberRemaining - 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func completeTasks(_ tasks: Int) {
|
||||
assert(Thread.isMainThread)
|
||||
if numberRemaining >= tasks {
|
||||
numberRemaining = numberRemaining - tasks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func reset() {
|
||||
assert(Thread.isMainThread)
|
||||
numberRemaining = 0
|
||||
@@ -92,7 +92,7 @@ public final class DownloadProgress {
|
||||
// MARK: - Private
|
||||
|
||||
private extension DownloadProgress {
|
||||
|
||||
|
||||
func postDidChangeNotification() {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .DownloadProgressDidChange, object: self)
|
||||
|
||||
@@ -40,13 +40,12 @@ public protocol DownloadSessionDelegate {
|
||||
/// These URLs are skipped for the rest of the session.
|
||||
private var urlsWith400s = Set<URL>()
|
||||
|
||||
|
||||
public init(delegate: DownloadSessionDelegate) {
|
||||
|
||||
self.delegate = delegate
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
let sessionConfiguration = URLSessionConfiguration.ephemeral
|
||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
sessionConfiguration.timeoutIntervalForRequest = 15.0
|
||||
@@ -60,9 +59,9 @@ public protocol DownloadSessionDelegate {
|
||||
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
||||
}
|
||||
|
||||
urlSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: OperationQueue.main)
|
||||
urlSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: OperationQueue.main)
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
urlSession.invalidateAndCancel()
|
||||
}
|
||||
@@ -170,7 +169,7 @@ extension DownloadSession: URLSessionDataDelegate {
|
||||
addDataTaskFromQueueIfNecessary()
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
|
||||
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
|
||||
guard let info = infoForTask(dataTask) else {
|
||||
@@ -279,7 +278,7 @@ private extension DownloadSession {
|
||||
|
||||
var currentURL = url
|
||||
|
||||
while(true) {
|
||||
while true {
|
||||
|
||||
if let oneRedirectURL = redirectCache[currentURL] {
|
||||
|
||||
@@ -289,9 +288,7 @@ private extension DownloadSession {
|
||||
}
|
||||
urls.insert(oneRedirectURL)
|
||||
currentURL = oneRedirectURL
|
||||
}
|
||||
|
||||
else {
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -449,7 +446,7 @@ extension URLSessionTask {
|
||||
// MARK: - DownloadInfo
|
||||
|
||||
private final class DownloadInfo {
|
||||
|
||||
|
||||
let url: URL
|
||||
let data = NSMutableData()
|
||||
var urlResponse: URLResponse?
|
||||
@@ -458,9 +455,9 @@ private final class DownloadInfo {
|
||||
|
||||
self.url = url
|
||||
}
|
||||
|
||||
|
||||
func addData(_ d: Data) {
|
||||
|
||||
|
||||
data.append(d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class Downloader {
|
||||
sessionConfiguration.httpCookieAcceptPolicy = .never
|
||||
sessionConfiguration.httpMaximumConnectionsPerHost = 1
|
||||
sessionConfiguration.httpCookieStorage = nil
|
||||
|
||||
|
||||
if let userAgentHeaders = UserAgent.headers() {
|
||||
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public final class Downloader {
|
||||
urlRequestToUse.addSpecialCaseUserAgentIfNeeded()
|
||||
|
||||
let task = urlSession.dataTask(with: urlRequestToUse) { (data, response, error) in
|
||||
DispatchQueue.main.async() {
|
||||
DispatchQueue.main.async {
|
||||
completion?(data, response, error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public extension Notification.Name {
|
||||
public final class HTMLMetadataCache: Sendable {
|
||||
|
||||
static let shared = HTMLMetadataCache()
|
||||
|
||||
|
||||
// Sent along with .htmlMetadataAvailable notification
|
||||
public struct UserInfoKey {
|
||||
public static let htmlMetadata = "htmlMetadata"
|
||||
|
||||
@@ -19,7 +19,7 @@ public final class HTMLMetadataDownloader: Sendable {
|
||||
|
||||
private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "HTMLMetadataDownloader")
|
||||
private static let debugLoggingEnabled = false
|
||||
|
||||
|
||||
private let cache = HTMLMetadataCache()
|
||||
private let attemptDatesLock = OSAllocatedUnfairLock(initialState: [String: Date]())
|
||||
private let urlsReturning4xxsLock = OSAllocatedUnfairLock(initialState: Set<String>())
|
||||
@@ -88,7 +88,7 @@ private extension HTMLMetadataDownloader {
|
||||
Self.logger.debug("HTMLMetadataDownloader downloading for \(url)")
|
||||
}
|
||||
|
||||
Downloader.shared.download(actualURL) { data, response, error in
|
||||
Downloader.shared.download(actualURL) { data, response, _ in
|
||||
if let data, !data.isEmpty, let response, response.statusIsOK {
|
||||
let urlToUse = response.url ?? actualURL
|
||||
let parserData = ParserData(url: urlToUse.absoluteString, data: data)
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
import Foundation
|
||||
|
||||
public struct HTTPConditionalGetInfo: Codable, Equatable {
|
||||
|
||||
|
||||
public let lastModified: String?
|
||||
public let etag: String?
|
||||
|
||||
|
||||
public init?(lastModified: String?, etag: String?) {
|
||||
if lastModified == nil && etag == nil {
|
||||
return nil
|
||||
@@ -20,19 +20,19 @@ public struct HTTPConditionalGetInfo: Codable, Equatable {
|
||||
self.lastModified = lastModified
|
||||
self.etag = etag
|
||||
}
|
||||
|
||||
|
||||
public init?(urlResponse: HTTPURLResponse) {
|
||||
let lastModified = urlResponse.valueForHTTPHeaderField(HTTPResponseHeader.lastModified)
|
||||
let etag = urlResponse.valueForHTTPHeaderField(HTTPResponseHeader.etag)
|
||||
self.init(lastModified: lastModified, etag: etag)
|
||||
}
|
||||
|
||||
public init?(headers: [AnyHashable : Any]) {
|
||||
public init?(headers: [AnyHashable: Any]) {
|
||||
let lastModified = headers[HTTPResponseHeader.lastModified] as? String
|
||||
let etag = headers[HTTPResponseHeader.etag] as? String
|
||||
self.init(lastModified: lastModified, etag: etag)
|
||||
}
|
||||
|
||||
|
||||
public func addRequestHeadersToURLRequest(_ urlRequest: inout URLRequest) {
|
||||
// Bug seen in the wild: lastModified with last possible 32-bit date, which is in 2038. Ignore those.
|
||||
// TODO: drop this check in late 2037.
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
import Foundation
|
||||
|
||||
public struct HTTPDateInfo: Codable, Equatable {
|
||||
|
||||
|
||||
private static let formatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEEE, dd LLL yyyy HH:mm:ss zzz"
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
|
||||
public let date: Date?
|
||||
|
||||
|
||||
public init?(urlResponse: HTTPURLResponse) {
|
||||
if let headerDate = urlResponse.valueForHTTPHeaderField(HTTPResponseHeader.date) {
|
||||
date = HTTPDateInfo.formatter.date(from: headerDate)
|
||||
@@ -25,5 +25,5 @@ public struct HTTPDateInfo: Codable, Equatable {
|
||||
date = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,33 +9,33 @@
|
||||
import Foundation
|
||||
|
||||
public struct HTTPLinkPagingInfo {
|
||||
|
||||
|
||||
public let nextPage: String?
|
||||
public let lastPage: String?
|
||||
|
||||
|
||||
public init(nextPage: String?, lastPage: String?) {
|
||||
self.nextPage = nextPage
|
||||
self.lastPage = lastPage
|
||||
}
|
||||
|
||||
public init(urlResponse: HTTPURLResponse) {
|
||||
|
||||
|
||||
guard let linkHeader = urlResponse.valueForHTTPHeaderField(HTTPResponseHeader.link) else {
|
||||
self.init(nextPage: nil, lastPage: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let links = linkHeader.components(separatedBy: ",")
|
||||
|
||||
|
||||
var dict: [String: String] = [:]
|
||||
for link in links {
|
||||
let components = link.components(separatedBy:"; ")
|
||||
let components = link.components(separatedBy: "; ")
|
||||
let page = components[0].trimmingCharacters(in: CharacterSet(charactersIn: " <>"))
|
||||
dict[components[1]] = page
|
||||
}
|
||||
|
||||
|
||||
self.init(nextPage: dict["rel=\"next\""], lastPage: dict["rel=\"last\""])
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ public struct HTTPRequestHeader {
|
||||
public static let userAgent = "User-Agent"
|
||||
public static let authorization = "Authorization"
|
||||
public static let contentType = "Content-Type"
|
||||
|
||||
|
||||
// Conditional GET
|
||||
|
||||
|
||||
public static let ifModifiedSince = "If-Modified-Since"
|
||||
public static let ifNoneMatch = "If-None-Match" //Etag
|
||||
public static let ifNoneMatch = "If-None-Match" // Etag
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ public struct HTTPResponseCode {
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
// Not an enum because the main interest is the actual values.
|
||||
|
||||
public static let responseContinue = 100 //"continue" is a language keyword, hence the weird name
|
||||
|
||||
public static let responseContinue = 100 // "continue" is a language keyword, hence the weird name
|
||||
public static let switchingProtocols = 101
|
||||
|
||||
|
||||
public static let OK = 200
|
||||
public static let created = 201
|
||||
public static let accepted = 202
|
||||
@@ -23,7 +23,7 @@ public struct HTTPResponseCode {
|
||||
public static let noContent = 204
|
||||
public static let resetContent = 205
|
||||
public static let partialContent = 206
|
||||
|
||||
|
||||
public static let redirectMultipleChoices = 300
|
||||
public static let redirectPermanent = 301
|
||||
public static let redirectTemporary = 302
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct HTTPResponseHeader {
|
||||
|
||||
// Conditional GET. See:
|
||||
// http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers/
|
||||
|
||||
|
||||
public static let lastModified = "Last-Modified"
|
||||
// Changed to the canonical case for lookups against a case sensitive dictionary
|
||||
// https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
|
||||
|
||||
@@ -21,7 +21,7 @@ public class MacWebBrowser {
|
||||
return false
|
||||
}
|
||||
|
||||
if (inBackground) {
|
||||
if inBackground {
|
||||
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
configuration.activates = false
|
||||
@@ -124,23 +124,23 @@ public class MacWebBrowser {
|
||||
/// - url: The URL to open.
|
||||
/// - inBackground: If `true`, attempt to load the URL without bringing the browser to the foreground.
|
||||
@discardableResult public func openURL(_ url: URL, inBackground: Bool = false) -> Bool {
|
||||
|
||||
|
||||
// TODO: make this function async.
|
||||
|
||||
|
||||
guard let preparedURL = url.preparedForOpeningInBrowser() else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Task { @MainActor in
|
||||
|
||||
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
if inBackground {
|
||||
configuration.activates = false
|
||||
}
|
||||
|
||||
|
||||
NSWorkspace.shared.open([preparedURL], withApplicationAt: self.url, configuration: configuration, completionHandler: nil)
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class MacWebBrowser {
|
||||
extension MacWebBrowser: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
if let name = name, let bundleIdentifier = bundleIdentifier{
|
||||
if let name = name, let bundleIdentifier = bundleIdentifier {
|
||||
return "MacWebBrowser: \(name) (\(bundleIdentifier))"
|
||||
} else {
|
||||
return "MacWebBrowser"
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
import Foundation
|
||||
|
||||
public struct MimeType {
|
||||
|
||||
|
||||
// This could certainly use expansion.
|
||||
|
||||
|
||||
public static let png = "image/png"
|
||||
public static let jpeg = "image/jpeg"
|
||||
public static let jpg = "image/jpg"
|
||||
@@ -20,29 +20,29 @@ public struct MimeType {
|
||||
}
|
||||
|
||||
public extension String {
|
||||
|
||||
|
||||
func isMimeTypeImage() -> Bool {
|
||||
|
||||
|
||||
return self.isOfGeneralMimeType("image")
|
||||
}
|
||||
|
||||
|
||||
func isMimeTypeAudio() -> Bool {
|
||||
|
||||
|
||||
return self.isOfGeneralMimeType("audio")
|
||||
}
|
||||
|
||||
|
||||
func isMimeTypeVideo() -> Bool {
|
||||
|
||||
|
||||
return self.isOfGeneralMimeType("video")
|
||||
}
|
||||
|
||||
|
||||
func isMimeTypeTimeBasedMedia() -> Bool {
|
||||
|
||||
|
||||
return self.isMimeTypeAudio() || self.isMimeTypeVideo()
|
||||
}
|
||||
|
||||
|
||||
private func isOfGeneralMimeType(_ type: String) -> Bool {
|
||||
|
||||
|
||||
let lower = self.lowercased()
|
||||
if lower.hasPrefix(type) {
|
||||
return true
|
||||
|
||||
@@ -48,8 +48,8 @@ public class Reachability {
|
||||
return reachability.connection != .unavailable
|
||||
}
|
||||
|
||||
public typealias NetworkReachable = (Reachability) -> ()
|
||||
public typealias NetworkUnreachable = (Reachability) -> ()
|
||||
public typealias NetworkReachable = (Reachability) -> Void
|
||||
public typealias NetworkUnreachable = (Reachability) -> Void
|
||||
|
||||
public enum Connection: CustomStringConvertible {
|
||||
@available(*, deprecated, renamed: "unavailable")
|
||||
@@ -72,7 +72,7 @@ public class Reachability {
|
||||
if flags == nil {
|
||||
try? setReachabilityFlags()
|
||||
}
|
||||
|
||||
|
||||
switch flags?.connection {
|
||||
case .unavailable?, nil: return .unavailable
|
||||
case .none?: return .unavailable
|
||||
|
||||
@@ -22,8 +22,7 @@ extension URL {
|
||||
let result: Bool
|
||||
if let host = host(), host.contains("openrss.org") {
|
||||
result = true
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result = false
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ extension Set where Element == URL {
|
||||
extension URLRequest {
|
||||
|
||||
mutating func addSpecialCaseUserAgentIfNeeded() {
|
||||
|
||||
|
||||
if let url, url.isOpenRSSOrgURL {
|
||||
setValue(UserAgent.openRSSOrgUserAgent, forHTTPHeaderField: HTTPRequestHeader.userAgent)
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@ public extension String {
|
||||
|
||||
return escaped
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ public extension URL {
|
||||
|
||||
if isHTTPSURL() {
|
||||
return absoluteString.stringByRemovingCaseInsensitivePrefix(URLConstants.prefixHTTPS)
|
||||
}
|
||||
else if isHTTPURL() {
|
||||
} else if isHTTPURL() {
|
||||
return absoluteString.stringByRemovingCaseInsensitivePrefix(URLConstants.prefixHTTP)
|
||||
}
|
||||
|
||||
@@ -76,7 +75,7 @@ private extension String {
|
||||
let lowerPrefix = prefix.lowercased()
|
||||
let lowerSelf = self.lowercased()
|
||||
|
||||
if (lowerSelf == lowerPrefix) {
|
||||
if lowerSelf == lowerPrefix {
|
||||
return ""
|
||||
}
|
||||
if !lowerSelf.hasPrefix(lowerPrefix) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
public extension URLComponents {
|
||||
|
||||
|
||||
// `+` is a valid character in query component as per RFC 3986 (https://developer.apple.com/documentation/foundation/nsurlcomponents/1407752-queryitems)
|
||||
// workaround:
|
||||
// - http://www.openradar.me/24076063
|
||||
@@ -17,18 +17,18 @@ public extension URLComponents {
|
||||
guard !(queryItems?.isEmpty ?? true) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var allowedCharacters = CharacterSet.urlQueryAllowed
|
||||
allowedCharacters.remove(charactersIn: "!*'();:@&=+$,/?%#[]")
|
||||
|
||||
|
||||
var queries = [String]()
|
||||
for queryItem in queryItems! {
|
||||
if let value = queryItem.value?.addingPercentEncoding(withAllowedCharacters: allowedCharacters)?.replacingOccurrences(of: "%20", with: "+") {
|
||||
queries.append("\(queryItem.name)=\(value)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return queries.joined(separator: "&")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ import Foundation
|
||||
public extension URLRequest {
|
||||
|
||||
@discardableResult mutating func addBasicAuthorization(username: String, password: String) -> Bool {
|
||||
|
||||
|
||||
// Do this *only* with https. And not even then if you can help it.
|
||||
|
||||
|
||||
let s = "\(username):\(password)"
|
||||
guard let d = s.data(using: .utf8, allowLossyConversion: false) else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
let base64EncodedString = d.base64EncodedString()
|
||||
let authorization = "Basic \(base64EncodedString)"
|
||||
setValue(authorization, forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
import Foundation
|
||||
|
||||
public extension URLResponse {
|
||||
|
||||
|
||||
var statusIsOK: Bool {
|
||||
return forcedStatusCode >= 200 && forcedStatusCode <= 299
|
||||
}
|
||||
|
||||
|
||||
var forcedStatusCode: Int {
|
||||
|
||||
|
||||
// Return actual statusCode or 0 if there isn’t one.
|
||||
|
||||
|
||||
if let response = self as? HTTPURLResponse {
|
||||
return response.statusCode
|
||||
}
|
||||
@@ -26,20 +26,20 @@ public extension URLResponse {
|
||||
}
|
||||
|
||||
public extension HTTPURLResponse {
|
||||
|
||||
|
||||
func valueForHTTPHeaderField(_ headerField: String) -> String? {
|
||||
|
||||
|
||||
// Case-insensitive. HTTP headers may not be in the case you expect.
|
||||
|
||||
|
||||
let lowerHeaderField = headerField.lowercased()
|
||||
|
||||
|
||||
for (key, value) in allHeaderFields {
|
||||
|
||||
|
||||
if lowerHeaderField == (key as? String)?.lowercased() {
|
||||
return value as? String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
public struct UserAgent {
|
||||
|
||||
|
||||
public static func fromInfoPlist() -> String? {
|
||||
|
||||
return Bundle.main.object(forInfoDictionaryKey: "UserAgent") as? String
|
||||
|
||||
@@ -14,7 +14,7 @@ public enum TransportError: LocalizedError {
|
||||
case noURL
|
||||
case suspended
|
||||
case httpError(status: Int)
|
||||
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .httpError(let status):
|
||||
@@ -111,27 +111,27 @@ public enum TransportError: LocalizedError {
|
||||
return NSLocalizedString("An unknown network error occurred.", comment: "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public protocol Transport {
|
||||
|
||||
|
||||
/// Cancels all pending requests
|
||||
func cancelAll()
|
||||
|
||||
|
||||
/// Sends URLRequest and returns the HTTP headers and the data payload.
|
||||
func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void)
|
||||
|
||||
|
||||
/// Sends URLRequest that doesn't require any result information.
|
||||
func send(request: URLRequest, method: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
|
||||
/// Sends URLRequest with a data payload and returns the HTTP headers and the data payload.
|
||||
func send(request: URLRequest, method: String, payload: Data, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void)
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension URLSession: Transport {
|
||||
|
||||
|
||||
public func cancelAll() {
|
||||
getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
|
||||
for dataTask in dataTasks {
|
||||
@@ -145,7 +145,7 @@ extension URLSession: Transport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
||||
let task = self.dataTask(with: request) { (data, response, error) in
|
||||
DispatchQueue.main.async {
|
||||
@@ -169,11 +169,11 @@ extension URLSession: Transport {
|
||||
}
|
||||
|
||||
public func send(request: URLRequest, method: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
|
||||
var sendRequest = request
|
||||
sendRequest.httpMethod = method
|
||||
|
||||
let task = self.dataTask(with: sendRequest) { (data, response, error) in
|
||||
|
||||
let task = self.dataTask(with: sendRequest) { (_, response, error) in
|
||||
DispatchQueue.main.async {
|
||||
if let error = error {
|
||||
return completion(.failure(error))
|
||||
@@ -193,12 +193,12 @@ extension URLSession: Transport {
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
|
||||
public func send(request: URLRequest, method: String, payload: Data, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
||||
|
||||
|
||||
var sendRequest = request
|
||||
sendRequest.httpMethod = method
|
||||
|
||||
|
||||
let task = self.uploadTask(with: sendRequest, from: payload) { (data, response, error) in
|
||||
DispatchQueue.main.async {
|
||||
if let error = error {
|
||||
@@ -215,14 +215,14 @@ extension URLSession: Transport {
|
||||
default:
|
||||
completion(.failure(TransportError.httpError(status: response.forcedStatusCode)))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
|
||||
public static func webserviceTransport() -> Transport {
|
||||
|
||||
|
||||
let sessionConfiguration = URLSessionConfiguration.default
|
||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
sessionConfiguration.timeoutIntervalForRequest = 60.0
|
||||
@@ -231,11 +231,11 @@ extension URLSession: Transport {
|
||||
sessionConfiguration.httpMaximumConnectionsPerHost = 2
|
||||
sessionConfiguration.httpCookieStorage = nil
|
||||
sessionConfiguration.urlCache = nil
|
||||
|
||||
|
||||
if let userAgentHeaders = UserAgent.headers() {
|
||||
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
||||
}
|
||||
|
||||
|
||||
return URLSession(configuration: sessionConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
import Foundation
|
||||
|
||||
extension Transport {
|
||||
|
||||
|
||||
/**
|
||||
Sends an HTTP get and returns JSON object(s)
|
||||
*/
|
||||
public func send<R: Decodable>(request: URLRequest, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void) {
|
||||
|
||||
|
||||
send(request: request) { result in
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -33,15 +33,13 @@ extension Transport {
|
||||
DispatchQueue.main.async {
|
||||
completion(.success((response, decoded)))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
completion(.success((response, nil)))
|
||||
}
|
||||
|
||||
@@ -51,12 +49,12 @@ extension Transport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Sends the specified HTTP method with a JSON payload.
|
||||
*/
|
||||
public func send<P: Encodable>(request: URLRequest, method: String, payload: P, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
|
||||
var postRequest = request
|
||||
postRequest.addValue("application/json; charset=utf-8", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||
|
||||
@@ -79,12 +77,12 @@ extension Transport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Sends the specified HTTP method with a JSON payload and returns JSON object(s).
|
||||
*/
|
||||
public func send<P: Encodable, R: Decodable>(request: URLRequest, method: String, payload: P, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void) {
|
||||
|
||||
|
||||
var postRequest = request
|
||||
postRequest.addValue("application/json; charset=utf-8", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||
|
||||
@@ -95,7 +93,7 @@ extension Transport {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
send(request: postRequest, method: method, payload: data) { result in
|
||||
DispatchQueue.main.async {
|
||||
|
||||
|
||||
@@ -10,22 +10,12 @@ import XCTest
|
||||
@testable import RSWeb
|
||||
|
||||
class RSWebTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
@@ -36,7 +26,7 @@ class RSWebTests: XCTestCase {
|
||||
func testAllBrowsers() {
|
||||
let browsers = MacWebBrowser.sortedBrowsers()
|
||||
|
||||
XCTAssertNotNil(browsers);
|
||||
XCTAssertNotNil(browsers)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user