summaryrefslogtreecommitdiff
path: root/Sora/Data
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-02-18 23:39:51 -0800
committerFuwn <[email protected]>2025-02-19 00:00:29 -0800
commit320ce31337ed60cae24a0374fa2d6d79237a6bfe (patch)
treead1e93799efeb8e7a16521e3ab958c911f73f617 /Sora/Data
parentfeat: Initial commit (diff)
downloadsora-testing-320ce31337ed60cae24a0374fa2d6d79237a6bfe.tar.xz
sora-testing-320ce31337ed60cae24a0374fa2d6d79237a6bfe.zip
feat: Development commit
Diffstat (limited to 'Sora/Data')
-rw-r--r--Sora/Data/Moebooru/MoebooruManager.swift114
-rw-r--r--Sora/Data/Moebooru/MoebooruPost.swift (renamed from Sora/Data/MoebooruPost.swift)0
-rw-r--r--Sora/Data/Moebooru/MoebooruPostXMLParser.swift42
-rw-r--r--Sora/Data/Moebooru/MoebooruTag.swift9
-rw-r--r--Sora/Data/Moebooru/MoebooruTagXMLParser.swift35
-rw-r--r--Sora/Data/MoebooruXMLParser.swift49
-rw-r--r--Sora/Data/Settings.swift20
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))
+ }
+}