Fix lint issues.

This commit is contained in:
Brent Simmons
2025-01-22 22:13:20 -08:00
parent d210692d7d
commit 72a5e46dcc
115 changed files with 584 additions and 711 deletions

View File

@@ -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"])
]
)

View File

@@ -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

View File

@@ -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&param2=some%20thing`.
var urlQueryString: String? {

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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.

View File

@@ -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
}
}
}

View File

@@ -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\""])
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -35,5 +35,5 @@ public extension String {
return escaped
}
}

View File

@@ -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) {

View File

@@ -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: "&")
}
}

View File

@@ -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
}
}

View File

@@ -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 isnt 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
}
}

View File

@@ -9,7 +9,7 @@
import Foundation
public struct UserAgent {
public static func fromInfoPlist() -> String? {
return Bundle.main.object(forInfoDictionaryKey: "UserAgent") as? String

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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)
}
}