import SwiftUI @MainActor class DanbooruManager: ObservableObject { @Published var posts: [DanbooruPost] = [] @Published var allTags: [BooruTag] = [] @Published var isLoading: Bool = false @Published var currentPage: Int = 1 @Published var searchText = "" @Published var endOfData: Bool = false #if os(macOS) @Published var selectedPost: MoebooruPost? #endif private var currentTask: Task? var tags: [String] { if searchText.isEmpty { return [] } return searchText .split(separator: " ") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } } init() { fetchAllTags() } func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async { guard !isLoading else { return } currentTask?.cancel() currentTask = Task { isLoading = true defer { isLoading = false } if replace { self.posts = [] self.currentPage = 1 } guard let url = URL(string: "https://safebooru.org/index.php?page=dapi&s=post&q=index&pid=\(page)&limit=\(limit)&tags=\(tags.joined(separator: "+"))") else { return } do { let (data, _) = try await URLSession.shared.data(from: url) if Task.isCancelled { return } DispatchQueue.main.async { let newPosts = Array(Set(DanbooruPostXMLParser(data: data).parse())).sorted { $0.id > $1.id } if newPosts == [] { self.endOfData = true } else { self.posts += newPosts } } } catch { if (error as? URLError)?.code != .cancelled { #if DEBUG print(error) #endif } } } } func performSearch() { Task { await fetchPosts( page: 1, tags: tags, replace: true ) } } func loadNextPage() { guard !isLoading else { return } Task { await fetchPosts(page: currentPage + 1, tags: tags) DispatchQueue.main.async { self.currentPage += 1 } } } func fetchAllTags(limit: Int = 100_000) { Task { guard let url = URL(string: "https://safebooru.org/index.php?page=dapi&s=tag&q=index&limit=\(limit)") else { return } do { let (data, _) = try await URLSession.shared.data(from: url) if Task.isCancelled { return } DispatchQueue.main.async { self.allTags = (BooruTagXMLParser(data: data).parse()).sorted { $0.count > $1.count } } } catch { if (error as? URLError)?.code != .cancelled { #if DEBUG print(error) #endif } } } } }