summaryrefslogtreecommitdiff
path: root/Sora
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-03-01 20:37:19 -0800
committerFuwn <[email protected]>2025-03-01 20:37:19 -0800
commit6c13c3b884c4cd2ef87b09dd9de7b6d3f161f8f4 (patch)
treeb18857c8ef25a1bcb09ecb133319415d31bde364 /Sora
parentfeat: Development commit (diff)
downloadsora-testing-6c13c3b884c4cd2ef87b09dd9de7b6d3f161f8f4.tar.xz
sora-testing-6c13c3b884c4cd2ef87b09dd9de7b6d3f161f8f4.zip
feat: Development commit
Diffstat (limited to 'Sora')
-rw-r--r--Sora/Data/Booru/BooruManager.swift253
-rw-r--r--Sora/Data/Booru/BooruProvider.swift22
2 files changed, 113 insertions, 162 deletions
diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift
index 2b52b4b..d7adc5b 100644
--- a/Sora/Data/Booru/BooruManager.swift
+++ b/Sora/Data/Booru/BooruManager.swift
@@ -2,6 +2,7 @@ import SwiftUI
@MainActor
class BooruManager: ObservableObject {
+ // MARK: - Published Properties
@Published var posts: [BooruPost] = []
@Published var allTags: [BooruTag] = []
@Published var isLoading = false
@@ -12,56 +13,34 @@ class BooruManager: ObservableObject {
@Published var selectedPost: BooruPost?
@Published var flavor: BooruProviderFlavor
@Published var domain: String
- @Published var postIndexMap: [String: Int] = [:]
- private var currentTask: Task<Void, Never>?
- let provider: BooruProvider
- var tags: [String] {
- if searchText.isEmpty {
- return []
- }
+ @Published private(set) var postIndexMap: [String: Int] = [:]
+ @Published var provider: BooruProvider
- return
- searchText
- .split(separator: " ")
- .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
- .filter { !$0.isEmpty }
- }
- private var tagsCacheFileURL: URL? {
- guard let directory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
- else {
- return nil
- }
+ // MARK: - Private Properties
+ private var currentTask: Task<Void, Never>?
+ private let tagsCacheFileURL: URL? = FileManager.default.urls(
+ for: .cachesDirectory, in: .userDomainMask
+ ).first?
+ .appendingPathComponent("\(BooruProvider.safebooru.asFileNameComponent())_tags.json")
- return
- directory
- .appendingPathComponent("\(provider.asFileNameComponent())_tags.json")
+ // MARK: - Computed Properties
+ var tags: [String] {
+ searchText.isEmpty
+ ? []
+ : searchText
+ .split(separator: " ")
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
+ .filter { !$0.isEmpty }
}
+ // MARK: - Initialization
init(_ provider: BooruProvider) {
self.provider = provider
self.flavor = BooruProviderFlavor(provider: provider)
-
- switch provider {
- case .yandere:
- domain = "yande.re"
-
- case .konachan:
- domain = "konachan.com"
-
- case .sakugabooru:
- domain = "sakugabooru.com"
-
- case .safebooru:
- domain = "safebooru.org"
-
- case .gelbooru:
- domain = "gelbooru.com"
-
- case .danbooru:
- domain = "danbooru.donmai.us"
- }
+ self.domain = provider.domain
}
+ // MARK: - Public Methods
func initializeTags() {
loadCachedTags()
fetchAllTags()
@@ -70,82 +49,38 @@ class BooruManager: ObservableObject {
func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async
{
- guard !isLoading else { return }
+ guard !isLoading,
+ let url = urlForPosts(page: flavor == .gelbooru ? page - 1 : page, limit: limit, tags: tags)
+ else { return }
+
+ isLoading = true
currentTask?.cancel()
currentTask = Task {
- isLoading = true
-
defer { isLoading = false }
- if replace {
- self.posts = []
- self.currentPage = 1
- }
-
- guard
- let url = urlForPosts(
- page: self.flavor == .gelbooru ? page - 1 : page, limit: limit, tags: tags
- )
- else {
- return
- }
-
do {
let (data, _) = try await URLSession.shared.data(from: url)
- if Task.isCancelled { return }
-
- DispatchQueue.main.async {
- let newPosts = Array(
- Set(
- self.flavor == .danbooru
- ? DanbooruPostParser(data: data).parse()
- : BooruPostXMLParser(data: data, provider: self.provider).parse()
- )
- )
- .sorted { lhs, rhs in
- lhs.id > rhs.id
- }
-
- if newPosts.isEmpty {
- self.endOfData = true
- } else {
- self.posts += Array(Set(newPosts))
-
- self.posts = self.posts.sorted { $0.id > $1.id }
- self.postIndexMap.merge(
- zip(newPosts.indices, newPosts.map(\.id)).reduce(into: [:]) { result, element in
- result[element.1] = element.0
- }
- ) { _, new in new }
- }
- }
+ guard !Task.isCancelled else { return }
+
+ let newPosts = parsePosts(from: data).sorted { $0.id > $1.id }
+
+ updatePosts(newPosts, replace: replace)
} catch {
- if (error as? URLError)?.code != .cancelled {
- debugPrint("BooruManager.fetchPosts: \(error)")
- }
+ if (error as? URLError)?.code != .cancelled { debugPrint("fetchPosts: \(error)") }
}
}
}
func performSearch(settings: SettingsManager? = nil) {
- if let settings {
- settings.appendToSearchHistory(
- BooruSearchQuery(
- provider: settings.preferredBooru,
- tags: tags,
- searchedAt: Date()
- )
- )
- }
-
+ settings?.appendToSearchHistory(
+ BooruSearchQuery(provider: settings!.preferredBooru, tags: tags, searchedAt: Date())
+ )
currentTask?.cancel()
- currentTask = Task {
- await fetchPosts(page: 1, tags: tags, replace: true)
- }
+ Task { await fetchPosts(page: 1, tags: tags, replace: true) }
}
func loadNextPage() {
@@ -154,42 +89,42 @@ class BooruManager: ObservableObject {
Task {
await fetchPosts(page: currentPage + 1, tags: tags)
- DispatchQueue.main.async {
- self.currentPage += 1
- }
+ currentPage += 1
}
}
func fetchAllTags(limit: Int = 0) {
- Task {
- guard let url = urlForTags(limit: limit) else { return }
+ guard let url = urlForTags(limit: limit) else { return }
+ Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
- if Task.isCancelled { return }
+ guard !Task.isCancelled else { return }
+
+ allTags = BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count }
- DispatchQueue.main.async {
- self.allTags = BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count }
- self.saveTagsToCache()
- self.updateTagsCacheSize()
- }
+ saveTagsToCache()
} catch {
- if (error as? URLError)?.code != .cancelled {
- debugPrint("BooruManager.fetchAllTags: \(error)")
- }
+ if (error as? URLError)?.code != .cancelled { debugPrint("fetchAllTags: \(error)") }
}
}
}
+ func clearCachedTags() {
+ try? tagsCacheFileURL.map { url in
+ try FileManager.default.removeItem(at: url)
+ updateTagsCacheSize()
+ }
+ }
+
+ // MARK: - Private Helpers
private func urlForPosts(page: Int, limit: Int, tags: [String]) -> URL? {
let tagString = tags.joined(separator: "+")
switch flavor {
case .danbooru:
- return URL(
- string: "https://\(domain)/posts.json?page=\(page)&tags=\(tagString)"
- )
+ return URL(string: "https://\(domain)/posts.json?page=\(page)&tags=\(tagString)")
case .moebooru:
return URL(string: "https://\(domain)/post.xml?page=\(page)&limit=\(limit)&tags=\(tagString)")
@@ -205,78 +140,72 @@ class BooruManager: ObservableObject {
private func urlForTags(limit: Int) -> URL? {
switch flavor {
case .moebooru:
- URL(string: "https://\(domain)/tag.xml?limit=\(limit)")
+ return URL(string: "https://\(domain)/tag.xml?limit=\(limit)")
case .gelbooru:
- URL(string: "https://\(domain)/index.php?page=dapi&s=tag&q=index&limit=\(limit)")
+ return URL(string: "https://\(domain)/index.php?page=dapi&s=tag&q=index&limit=\(limit)")
case .danbooru:
- nil
+ return nil
}
}
- private func saveTagsToCache() {
- guard let url = tagsCacheFileURL else { return }
-
- do {
- let data = try JSONEncoder().encode(allTags)
+ private func parsePosts(from data: Data) -> [BooruPost] {
+ Array(
+ Set(
+ flavor == .danbooru
+ ? DanbooruPostParser(data: data).parse()
+ : BooruPostXMLParser(data: data, provider: provider).parse()
+ )
+ )
+ }
- try data.write(to: url)
- updateTagsCacheSize()
- } catch {
- debugPrint("BooruManager.saveTagsToCache: \(error)")
+ private func updatePosts(_ newPosts: [BooruPost], replace: Bool) {
+ if replace {
+ posts = []
+ currentPage = 1
}
- }
- private func loadCachedTags() {
- guard let url = tagsCacheFileURL else { return }
+ endOfData = newPosts.isEmpty
- do {
- let data = try Data(contentsOf: url)
- let cachedTags = try JSONDecoder().decode([BooruTag].self, from: data)
+ if !endOfData {
+ posts = (posts + newPosts).sorted { $0.id > $1.id }
- DispatchQueue.main.async {
- self.allTags = cachedTags
- self.updateTagsCacheSize()
- }
- } catch {
- debugPrint("BooruManager.loadCachedTags: \(error)")
+ postIndexMap.merge(
+ Dictionary(uniqueKeysWithValues: newPosts.enumerated().map { ($0.element.id, $0.offset) })
+ ) { _, new in new }
}
}
- func clearCachedTags() {
- guard let url = tagsCacheFileURL else { return }
-
- do {
- try FileManager.default.removeItem(at: url)
+ private func saveTagsToCache() {
+ try? tagsCacheFileURL.map { url in
+ try JSONEncoder().encode(allTags).write(to: url)
updateTagsCacheSize()
- } catch {
- debugPrint("BooruManager.clearCachedTags: \(error)")
}
}
- func updateTagsCacheSize() {
- guard let url = tagsCacheFileURL else {
- cacheSize = nil
+ private func loadCachedTags() {
+ guard let url = tagsCacheFileURL else { return }
+
+ if let data = try? Data(contentsOf: url),
+ let tags = try? JSONDecoder().decode([BooruTag].self, from: data)
+ {
+ allTags = tags
- return
+ updateTagsCacheSize()
}
+ }
- do {
- cacheSize = ByteCountFormatter.string(
+ func updateTagsCacheSize() {
+ cacheSize = tagsCacheFileURL.flatMap { url in
+ ByteCountFormatter.string(
fromByteCount: Int64(
- (try FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int) ?? 0
+ (try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int) ?? 0
),
countStyle: .file
)
- } catch {
- cacheSize = nil
-
- debugPrint("BooruManager.updateCacheSize: \(error)")
}
}
- deinit {
- currentTask?.cancel()
- }
+ deinit { currentTask?.cancel() }
}
diff --git a/Sora/Data/Booru/BooruProvider.swift b/Sora/Data/Booru/BooruProvider.swift
index 5458223..aa3acf3 100644
--- a/Sora/Data/Booru/BooruProvider.swift
+++ b/Sora/Data/Booru/BooruProvider.swift
@@ -6,6 +6,28 @@ enum BooruProvider: String, CaseIterable, Decodable, Encodable {
case sakugabooru = "sakugabooru"
case yandere = "yande.re"
+ var domain: String {
+ switch self {
+ case .yandere:
+ return "yande.re"
+
+ case .konachan:
+ return "konachan.com"
+
+ case .sakugabooru:
+ return "sakugabooru.com"
+
+ case .safebooru:
+ return "safebooru.org"
+
+ case .gelbooru:
+ return "gelbooru.com"
+
+ case .danbooru:
+ return "danbooru.donmai.us"
+ }
+ }
+
func asFileNameComponent() -> String {
rawValue.lowercased().replacingOccurrences(of: ".", with: "_")
}