summaryrefslogtreecommitdiff
path: root/Sora/Data
diff options
context:
space:
mode:
Diffstat (limited to 'Sora/Data')
-rw-r--r--Sora/Data/Booru/BooruManager.swift40
-rw-r--r--Sora/Data/Booru/BooruNetworkImageLoader.swift61
-rw-r--r--Sora/Data/Booru/BooruRequestConfiguration.swift66
-rw-r--r--Sora/Data/ImageCacheManager.swift59
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