diff options
Diffstat (limited to 'Sora/Data')
| -rw-r--r-- | Sora/Data/Booru/BooruManager.swift | 40 | ||||
| -rw-r--r-- | Sora/Data/Booru/BooruNetworkImageLoader.swift | 61 | ||||
| -rw-r--r-- | Sora/Data/Booru/BooruRequestConfiguration.swift | 66 | ||||
| -rw-r--r-- | Sora/Data/ImageCacheManager.swift | 59 |
4 files changed, 181 insertions, 45 deletions
diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift index f5fcf89..6333453 100644 --- a/Sora/Data/Booru/BooruManager.swift +++ b/Sora/Data/Booru/BooruManager.swift @@ -62,8 +62,8 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng pageCache.countLimit = 50 pageCache.totalCostLimit = 50 * 1_024 * 1_024 - self.referer = Self.baseReferer(for: provider.domain) - self.userAgent = Self.resolvedUserAgent( + self.referer = BooruRequestConfiguration.baseReferer(for: provider.domain) + self.userAgent = BooruRequestConfiguration.resolvedUserAgent( sendUserAgent: sendUserAgent, customUserAgent: customUserAgent ) @@ -202,30 +202,6 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng return components.url } - private static func resolvedUserAgent( - sendUserAgent: Bool, - customUserAgent: String - ) -> String? { - guard sendUserAgent else { return nil } - - let trimmedCustomUserAgent = customUserAgent.trimmingCharacters( - in: .whitespacesAndNewlines - ) - - guard trimmedCustomUserAgent.isEmpty else { - return trimmedCustomUserAgent - } - - let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0" - let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1" - - return "Sora/\(version) (Build \(buildNumber))" - } - - private static func baseReferer(for domain: String) -> String { - "https://\(domain)/" - } - func clearCachedPages() { pageCache.removeAllObjects() urlCache.removeAll() @@ -599,13 +575,13 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng } func requestURL(_ url: URL) async throws -> Data { - var headers = HTTPHeaders([HTTPHeader(name: "Referer", value: referer)]) - - if let userAgent { - headers.add(name: "User-Agent", value: userAgent) - } + let request = BooruRequestConfiguration.request( + url: url, + referer: referer, + userAgent: userAgent + ) - return try await AF.request(url, headers: headers) + return try await AF.request(request) .validate(statusCode: 200..<300) .serializingData() .value diff --git a/Sora/Data/Booru/BooruNetworkImageLoader.swift b/Sora/Data/Booru/BooruNetworkImageLoader.swift new file mode 100644 index 0000000..c2a0f8a --- /dev/null +++ b/Sora/Data/Booru/BooruNetworkImageLoader.swift @@ -0,0 +1,61 @@ +import CoreGraphics +import Foundation +import ImageIO +import NetworkImage + +actor BooruNetworkImageLoader: NetworkImageLoader { + private let domain: String + private let sendUserAgent: Bool + private let customUserAgent: String + private var ongoingTasks: [URL: Task<CGImage, Error>] = [:] + + init( + domain: String, + sendUserAgent: Bool, + customUserAgent: String + ) { + self.domain = domain + self.sendUserAgent = sendUserAgent + self.customUserAgent = customUserAgent + } + + func image(from url: URL) async throws -> CGImage { + if let task = ongoingTasks[url] { + return try await task.value + } + + let domain = self.domain + let sendUserAgent = self.sendUserAgent + let customUserAgent = self.customUserAgent + + let task = Task<CGImage, Error> { + guard let data = await ImageCacheManager.shared.loadImageData( + for: url, + domain: domain, + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent + ) else { + throw URLError(.badServerResponse) + } + + guard + let source = CGImageSourceCreateWithData(data as CFData, nil), + let image = CGImageSourceCreateImageAtIndex( + source, + 0, + [kCGImageSourceShouldCache: true] as CFDictionary + ) + else { + throw URLError(.cannotDecodeContentData) + } + + return image + } + + ongoingTasks[url] = task + + defer { ongoingTasks.removeValue(forKey: url) } + + return try await task.value + } +} diff --git a/Sora/Data/Booru/BooruRequestConfiguration.swift b/Sora/Data/Booru/BooruRequestConfiguration.swift new file mode 100644 index 0000000..2b786cf --- /dev/null +++ b/Sora/Data/Booru/BooruRequestConfiguration.swift @@ -0,0 +1,66 @@ +import Foundation + +enum BooruRequestConfiguration { + static func resolvedUserAgent( + sendUserAgent: Bool, + customUserAgent: String + ) -> String? { + guard sendUserAgent else { return nil } + + let trimmedCustomUserAgent = customUserAgent.trimmingCharacters( + in: .whitespacesAndNewlines + ) + + guard trimmedCustomUserAgent.isEmpty else { + return trimmedCustomUserAgent + } + + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0" + let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1" + + return "Sora/\(version) (Build \(buildNumber))" + } + + static func baseReferer(for domain: String) -> String { + "https://\(domain)/" + } + + static func request( + url: URL, + referer: String, + userAgent: String?, + accept: String? = nil + ) -> URLRequest { + var request = URLRequest(url: url) + + request.setValue(referer, forHTTPHeaderField: "Referer") + + if let userAgent { + request.setValue(userAgent, forHTTPHeaderField: "User-Agent") + } + + if let accept { + request.setValue(accept, forHTTPHeaderField: "Accept") + } + + return request + } + + static func request( + url: URL, + domain: String, + sendUserAgent: Bool, + customUserAgent: String, + accept: String? = nil + ) -> URLRequest { + request( + url: url, + referer: baseReferer(for: domain), + userAgent: resolvedUserAgent( + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent + ), + accept: accept + ) + } +} diff --git a/Sora/Data/ImageCacheManager.swift b/Sora/Data/ImageCacheManager.swift index 5561f24..c5a4c6e 100644 --- a/Sora/Data/ImageCacheManager.swift +++ b/Sora/Data/ImageCacheManager.swift @@ -35,9 +35,22 @@ final class ImageCacheManager { } // MARK: - Public Methods - func preloadImages(_ urls: [URL]) { + func preloadImages( + _ urls: [URL], + domain: String, + sendUserAgent: Bool, + customUserAgent: String + ) { let newURLs = urls.filter { url in - !preloadingURLs.contains(url) && cache.cachedResponse(for: URLRequest(url: url)) == nil + let request = BooruRequestConfiguration.request( + url: url, + domain: domain, + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent, + accept: "image/*" + ) + + return !preloadingURLs.contains(url) && cache.cachedResponse(for: request) == nil } for url in newURLs { @@ -47,8 +60,15 @@ final class ImageCacheManager { guard let self else { return } let completionSignal = DispatchSemaphore(value: 0) - - URLSession.shared.dataTask(with: url) { data, response, _ in + let request = BooruRequestConfiguration.request( + url: url, + domain: domain, + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent, + accept: "image/*" + ) + + URLSession.shared.dataTask(with: request) { data, response, _ in Task { @MainActor [weak self] in guard let self else { completionSignal.signal() @@ -65,7 +85,7 @@ final class ImageCacheManager { storeCachedResponse( data: data, response: response, - for: url + for: request ) } } @@ -81,29 +101,42 @@ final class ImageCacheManager { preloadingURLs.removeAll() } - func storeCachedResponse(data: Data, response: URLResponse, for url: URL) { + func storeCachedResponse(data: Data, response: URLResponse, for request: URLRequest) { cache.storeCachedResponse( CachedURLResponse(response: response, data: data), - for: URLRequest(url: url) + for: request ) } - func getCachedResponse(for url: URL) -> CachedURLResponse? { - cache.cachedResponse(for: URLRequest(url: url)) + func getCachedResponse(for request: URLRequest) -> CachedURLResponse? { + cache.cachedResponse(for: request) } - func loadImageData(for url: URL) async -> Data? { - if let cachedResponse = getCachedResponse(for: url) { + func loadImageData( + for url: URL, + domain: String, + sendUserAgent: Bool, + customUserAgent: String + ) async -> Data? { + let request = BooruRequestConfiguration.request( + url: url, + domain: domain, + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent, + accept: "image/*" + ) + + if let cachedResponse = getCachedResponse(for: request) { return cachedResponse.data } do { - let (data, response) = try await URLSession.shared.data(from: url) + let (data, response) = try await URLSession.shared.data(for: request) storeCachedResponse( data: data, response: response, - for: url + for: request ) return data |