diff options
| author | Fuwn <[email protected]> | 2025-02-18 23:39:51 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-02-19 00:00:29 -0800 |
| commit | 320ce31337ed60cae24a0374fa2d6d79237a6bfe (patch) | |
| tree | ad1e93799efeb8e7a16521e3ab958c911f73f617 /Sora/Data | |
| parent | feat: Initial commit (diff) | |
| download | sora-testing-320ce31337ed60cae24a0374fa2d6d79237a6bfe.tar.xz sora-testing-320ce31337ed60cae24a0374fa2d6d79237a6bfe.zip | |
feat: Development commit
Diffstat (limited to 'Sora/Data')
| -rw-r--r-- | Sora/Data/Moebooru/MoebooruManager.swift | 114 | ||||
| -rw-r--r-- | Sora/Data/Moebooru/MoebooruPost.swift (renamed from Sora/Data/MoebooruPost.swift) | 0 | ||||
| -rw-r--r-- | Sora/Data/Moebooru/MoebooruPostXMLParser.swift | 42 | ||||
| -rw-r--r-- | Sora/Data/Moebooru/MoebooruTag.swift | 9 | ||||
| -rw-r--r-- | Sora/Data/Moebooru/MoebooruTagXMLParser.swift | 35 | ||||
| -rw-r--r-- | Sora/Data/MoebooruXMLParser.swift | 49 | ||||
| -rw-r--r-- | Sora/Data/Settings.swift | 20 |
7 files changed, 220 insertions, 49 deletions
diff --git a/Sora/Data/Moebooru/MoebooruManager.swift b/Sora/Data/Moebooru/MoebooruManager.swift new file mode 100644 index 0000000..fd8d337 --- /dev/null +++ b/Sora/Data/Moebooru/MoebooruManager.swift @@ -0,0 +1,114 @@ +import SwiftUI + +@MainActor +class MoebooruManager: ObservableObject { + @Published var posts: [MoebooruPost] = [] + @Published var allTags: [MoebooruTag] = [] + @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<Void, Never>? + 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://yande.re/post.xml?page=\(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(MoebooruPostXMLParser().parse(data: data))).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://yande.re/tag.xml?limit=\(limit)") else { return } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + + if Task.isCancelled { return } + + DispatchQueue.main.async { + self.allTags = (MoebooruTagXMLParser().parse(data: data)).sorted { $0.count > $1.count } + } + } catch { + if (error as? URLError)?.code != .cancelled { + #if DEBUG + print(error) + #endif + } + } + } + } +} diff --git a/Sora/Data/MoebooruPost.swift b/Sora/Data/Moebooru/MoebooruPost.swift index df332ec..df332ec 100644 --- a/Sora/Data/MoebooruPost.swift +++ b/Sora/Data/Moebooru/MoebooruPost.swift diff --git a/Sora/Data/Moebooru/MoebooruPostXMLParser.swift b/Sora/Data/Moebooru/MoebooruPostXMLParser.swift new file mode 100644 index 0000000..418a9c6 --- /dev/null +++ b/Sora/Data/Moebooru/MoebooruPostXMLParser.swift @@ -0,0 +1,42 @@ +import Foundation + +class MoebooruPostXMLParser: NSObject, XMLParserDelegate { + private var posts: [MoebooruPost] = [] + + func parse(data: Data) -> [MoebooruPost] { + let parser = XMLParser(data: data) + + parser.delegate = self + parser.parse() + + return posts + } + + func parser(_: XMLParser, didStartElement elementName: String, + namespaceURI _: String?, qualifiedName _: String?, + attributes attributeDict: [String: String]) + { + if elementName == "post", + let id = Int(attributeDict["id"] ?? ""), + let createdAtTimestamp = TimeInterval(attributeDict["created_at"] ?? ""), + let score = Int(attributeDict["score"] ?? ""), + let width = Int(attributeDict["width"] ?? ""), + let height = Int(attributeDict["height"] ?? "") + { + posts.append(MoebooruPost( + id: id, + tags: attributeDict["tags"]?.components(separatedBy: " ") ?? [], + createdAt: Date(timeIntervalSince1970: createdAtTimestamp), + author: attributeDict["author"] ?? "", + source: URL(string: attributeDict["source"] ?? ""), + score: score, + fileURL: URL(string: attributeDict["file_url"] ?? ""), + previewURL: URL(string: attributeDict["preview_url"] ?? ""), + sampleURL: URL(string: attributeDict["sample_url"] ?? ""), + jpegURL: URL(string: attributeDict["jpeg_url"] ?? ""), + width: width, + height: height + )) + } + } +} diff --git a/Sora/Data/Moebooru/MoebooruTag.swift b/Sora/Data/Moebooru/MoebooruTag.swift new file mode 100644 index 0000000..cc783f0 --- /dev/null +++ b/Sora/Data/Moebooru/MoebooruTag.swift @@ -0,0 +1,9 @@ +import Foundation + +struct MoebooruTag: Identifiable, Hashable { + let id: Int + let name: String + let count: Int + let type: Int + let ambiguous: Bool +} diff --git a/Sora/Data/Moebooru/MoebooruTagXMLParser.swift b/Sora/Data/Moebooru/MoebooruTagXMLParser.swift new file mode 100644 index 0000000..7ea2e8e --- /dev/null +++ b/Sora/Data/Moebooru/MoebooruTagXMLParser.swift @@ -0,0 +1,35 @@ +import Foundation + +class MoebooruTagXMLParser: NSObject, XMLParserDelegate { + private var tags: [MoebooruTag] = [] + + func parse(data: Data) -> [MoebooruTag] { + let parser = XMLParser(data: data) + + parser.delegate = self + parser.parse() + + return tags + } + + func parser(_: XMLParser, didStartElement elementName: String, + namespaceURI _: String?, qualifiedName _: String?, + attributes attributeDict: [String: String]) + { + if elementName == "tag" { + if let id = Int(attributeDict["id"] ?? ""), + let count = Int(attributeDict["count"] ?? ""), + let type = Int(attributeDict["type"] ?? ""), + let ambiguous = Bool(attributeDict["ambiguous"] ?? "false") + { + tags.append(MoebooruTag( + id: id, + name: attributeDict["name"] ?? "", + count: count, + type: type, + ambiguous: ambiguous + )) + } + } + } +} diff --git a/Sora/Data/MoebooruXMLParser.swift b/Sora/Data/MoebooruXMLParser.swift deleted file mode 100644 index 1053299..0000000 --- a/Sora/Data/MoebooruXMLParser.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation - -class MoebooruXMLParser: NSObject, XMLParserDelegate { - private var posts: [MoebooruPost] = [] - private var currentAttributes: [String: String] = [:] - private var currentPost: MoebooruPost? - - func parse(data: Data) -> [MoebooruPost] { - let parser = XMLParser(data: data) - - parser.delegate = self - parser.parse() - - return posts - } - - func parser(_: XMLParser, didStartElement elementName: String, - namespaceURI _: String?, qualifiedName _: String?, - attributes attributeDict: [String: String]) - { - if elementName == "post" { - currentAttributes = attributeDict - - if let id = Int(attributeDict["id"] ?? ""), - let createdAtTimestamp = TimeInterval(attributeDict["created_at"] ?? "") - { - if let score = Int(attributeDict["score"] ?? ""), - let width = Int(attributeDict["width"] ?? ""), - let height = Int(attributeDict["height"] ?? "") - { - posts.append(MoebooruPost( - id: id, - tags: attributeDict["tags"]?.components(separatedBy: " ") ?? [], - createdAt: Date(timeIntervalSince1970: createdAtTimestamp), - author: attributeDict["author"] ?? "", - source: URL(string: attributeDict["source"] ?? ""), - score: score, - fileURL: URL(string: attributeDict["file_url"] ?? ""), - previewURL: URL(string: attributeDict["preview_url"] ?? ""), - sampleURL: URL(string: attributeDict["sample_url"] ?? ""), - jpegURL: URL(string: attributeDict["jpeg_url"] ?? ""), - width: width, - height: height - )) - } - } - } - } -} diff --git a/Sora/Data/Settings.swift b/Sora/Data/Settings.swift new file mode 100644 index 0000000..4f1554e --- /dev/null +++ b/Sora/Data/Settings.swift @@ -0,0 +1,20 @@ +import SwiftUI + +class Settings: ObservableObject { + @AppStorage("softLimit") var softLimit: Int = 100 + @AppStorage("largerThumbnails") var largerThumbnails: Bool = false + #if DEBUG + @AppStorage("detailViewType") var detailViewType: PostFileType = .compressed + #else + @AppStorage("detailViewType") var detailViewType: PostFileType = .original + #endif + @AppStorage("thumbnailType") var thumbnailType: PostFileType = .preview + @AppStorage("searchSuggestions") var searchSuggestions: Bool = false + @AppStorage("columns") var columns: Int = 2 + let minSoftLimit: Int = 100 + let maxSoftLimit: Int = 10000 + + func softLimitAsCGFloat() -> CGFloat { + max(CGFloat(softLimit), CGFloat(minSoftLimit)) + } +} |