mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 00:22:12 +00:00
Please work
This commit is contained in:
parent
56092e2886
commit
7bd4d41f4e
1 changed files with 60 additions and 46 deletions
|
|
@ -18,7 +18,6 @@ struct MediaItem: Identifiable {
|
||||||
let airdate: String
|
let airdate: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct MediaInfoView: View {
|
struct MediaInfoView: View {
|
||||||
let title: String
|
let title: String
|
||||||
@State var imageUrl: String
|
@State var imageUrl: String
|
||||||
|
|
@ -39,7 +38,7 @@ struct MediaInfoView: View {
|
||||||
@State private var jikanFillerSet: Set<Int>? = nil
|
@State private var jikanFillerSet: Set<Int>? = nil
|
||||||
|
|
||||||
// Static/shared Jikan cache & progress guards (one cache for the app to avoid duplicate/expensive fetches)
|
// Static/shared Jikan cache & progress guards (one cache for the app to avoid duplicate/expensive fetches)
|
||||||
private static var jikanCache: [Int: (fetchedAt: Date, fillerEpisodes: Set<Int>)] = [:]
|
private static var jikanCache: [Int: (fetchedAt: Date, episodes: [JikanEpisode])] = [:]
|
||||||
private static let jikanCacheQueue = DispatchQueue(label: "sora.jikan.cache.queue", attributes: .concurrent)
|
private static let jikanCacheQueue = DispatchQueue(label: "sora.jikan.cache.queue", attributes: .concurrent)
|
||||||
private static let jikanCacheTTL: TimeInterval = 60 * 60 * 24 * 7 // 1 week
|
private static let jikanCacheTTL: TimeInterval = 60 * 60 * 24 * 7 // 1 week
|
||||||
private static var inProgressMALIDs: Set<Int> = []
|
private static var inProgressMALIDs: Set<Int> = []
|
||||||
|
|
@ -567,7 +566,7 @@ struct MediaInfoView: View {
|
||||||
let seasons = groupedEpisodes()
|
let seasons = groupedEpisodes()
|
||||||
if seasons.count > 1 {
|
if seasons.count > 1 {
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(0..<seasons.count, id: \..self) { index in
|
ForEach(0..<seasons.count, id: \.self) { index in
|
||||||
Button(action: { selectedSeason = index }) {
|
Button(action: { selectedSeason = index }) {
|
||||||
Text(String(format: NSLocalizedString("Season %d", comment: ""), index + 1))
|
Text(String(format: NSLocalizedString("Season %d", comment: ""), index + 1))
|
||||||
}
|
}
|
||||||
|
|
@ -750,7 +749,7 @@ struct MediaInfoView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyVStack(spacing: 15) {
|
LazyVStack(spacing: 15) {
|
||||||
ForEach(chapters.indices.filter { selectedChapterRange.contains($0) }, id: \..self) { i in
|
ForEach(chapters.indices.filter { selectedChapterRange.contains($0) }, id: \.self) { i in
|
||||||
let chapter = chapters[i]
|
let chapter = chapters[i]
|
||||||
let _ = refreshTrigger
|
let _ = refreshTrigger
|
||||||
if let href = chapter["href"] as? String,
|
if let href = chapter["href"] as? String,
|
||||||
|
|
@ -804,7 +803,7 @@ struct MediaInfoView: View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var chapterRangeSelectorStyled: some View {
|
private var chapterRangeSelectorStyled: some View {
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(generateChapterRanges(), id: \..self) { range in
|
ForEach(generateChapterRanges(), id: \.self) { range in
|
||||||
Button(action: { selectedChapterRange = range }) {
|
Button(action: { selectedChapterRange = range }) {
|
||||||
Text("\(range.lowerBound + 1)-\(range.upperBound)")
|
Text("\(range.lowerBound + 1)-\(range.upperBound)")
|
||||||
}
|
}
|
||||||
|
|
@ -2495,10 +2494,11 @@ struct MediaInfoView: View {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Jikan filler fetching (moved here)
|
// MARK: - Updated Jikan Filler Implementation
|
||||||
private struct JikanResponse: Decodable {
|
private struct JikanResponse: Decodable {
|
||||||
let data: [JikanEpisode]
|
let data: [JikanEpisode]
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct JikanEpisode: Decodable {
|
private struct JikanEpisode: Decodable {
|
||||||
let mal_id: Int
|
let mal_id: Int
|
||||||
let filler: Bool
|
let filler: Bool
|
||||||
|
|
@ -2515,18 +2515,21 @@ struct MediaInfoView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cached: Set<Int>? = nil
|
// Check cache first
|
||||||
|
var cachedEpisodes: [JikanEpisode]? = nil
|
||||||
Self.jikanCacheQueue.sync {
|
Self.jikanCacheQueue.sync {
|
||||||
if let entry = Self.jikanCache[malID],
|
if let entry = Self.jikanCache[malID], Date().timeIntervalSince(entry.fetchedAt) < Self.jikanCacheTTL {
|
||||||
Date().timeIntervalSince(entry.fetchedAt) < Self.jikanCacheTTL {
|
cachedEpisodes = entry.episodes
|
||||||
cached = entry.fillerEpisodes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let cachedSet = cached {
|
|
||||||
DispatchQueue.main.async { self.jikanFillerSet = cachedSet }
|
if let episodes = cachedEpisodes {
|
||||||
|
Logger.shared.log("Using cached filler info for MAL ID: \(malID)", type: "Debug")
|
||||||
|
updateFillerSet(episodes: episodes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent duplicate requests
|
||||||
var shouldFetch = false
|
var shouldFetch = false
|
||||||
Self.inProgressQueue.sync {
|
Self.inProgressQueue.sync {
|
||||||
if !Self.inProgressMALIDs.contains(malID) {
|
if !Self.inProgressMALIDs.contains(malID) {
|
||||||
|
|
@ -2534,61 +2537,72 @@ struct MediaInfoView: View {
|
||||||
shouldFetch = true
|
shouldFetch = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !shouldFetch { return }
|
|
||||||
|
if !shouldFetch {
|
||||||
|
Logger.shared.log("Fetch already in progress for MAL ID: \(malID)", type: "Debug")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.shared.log("Fetching filler info for MAL ID: \(malID)", type: "Debug")
|
||||||
|
|
||||||
|
// Fetch all pages
|
||||||
fetchAllJikanPages(malID: malID) { episodes in
|
fetchAllJikanPages(malID: malID) { episodes in
|
||||||
defer {
|
// Update cache
|
||||||
Self.inProgressQueue.async {
|
if let episodes = episodes {
|
||||||
Self.inProgressMALIDs.remove(malID)
|
Logger.shared.log("Successfully fetched filler info for MAL ID: \(malID)", type: "Debug")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let episodes = episodes else {
|
|
||||||
Self.jikanCacheQueue.async(flags: .barrier) {
|
Self.jikanCacheQueue.async(flags: .barrier) {
|
||||||
Self.jikanCache[malID] = (Date(), Set<Int>())
|
Self.jikanCache[malID] = (Date(), episodes)
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { self.jikanFillerSet = Set<Int>() }
|
|
||||||
return
|
// Update UI
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.updateFillerSet(episodes: episodes)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.shared.log("Failed to fetch filler info for MAL ID: \(malID)", type: "Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
let fillerNumbers = Set(episodes.filter { $0.filler }.map { $0.mal_id })
|
// Remove from in-progress set
|
||||||
|
Self.inProgressQueue.async {
|
||||||
Self.jikanCacheQueue.async(flags: .barrier) {
|
Self.inProgressMALIDs.remove(malID)
|
||||||
Self.jikanCache[malID] = (Date(), fillerNumbers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async { self.jikanFillerSet = fillerNumbers }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchAllJikanPages(malID: Int, completion: @escaping ([JikanEpisode]?) -> Void) {
|
private func fetchAllJikanPages(malID: Int, completion: @escaping ([JikanEpisode]?) -> Void) {
|
||||||
var allEpisodes: [JikanEpisode] = []
|
var allEpisodes: [JikanEpisode] = []
|
||||||
let perPage = 100
|
|
||||||
var currentPage = 1
|
var currentPage = 1
|
||||||
|
let perPage = 100
|
||||||
|
|
||||||
func fetchPage() {
|
func fetchPage() {
|
||||||
guard let url = URL(string: "https://api.jikan.moe/v4/anime/\(malID)/episodes?page=\(currentPage)") else {
|
let url = URL(string: "https://api.jikan.moe/v4/anime/\(malID)/episodes?page=\(currentPage)&limit=\(perPage)")!
|
||||||
completion(nil); return
|
|
||||||
}
|
|
||||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
if let error = error {
|
guard let data = data, error == nil else {
|
||||||
Logger.shared.log("Jikan API request failed (page \(currentPage)): \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Jikan API request failed: \(error?.localizedDescription ?? "Unknown error")", type: "Error")
|
||||||
completion(nil); return
|
completion(nil)
|
||||||
}
|
return
|
||||||
guard let data = data else { completion(nil); return }
|
}
|
||||||
do {
|
do {
|
||||||
let response = try JSONDecoder().decode(JikanResponse.self, from: data)
|
let response = try JSONDecoder().decode(JikanResponse.self, from: data)
|
||||||
allEpisodes.append(contentsOf: response.data)
|
allEpisodes.append(contentsOf: response.data)
|
||||||
if response.data.count == perPage {
|
if response.data.count == perPage {
|
||||||
currentPage += 1; fetchPage()
|
currentPage += 1
|
||||||
} else { completion(allEpisodes) }
|
fetchPage()
|
||||||
|
} else {
|
||||||
|
completion(allEpisodes)
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Logger.shared.log("Failed to parse Jikan response (page \(currentPage)): \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Failed to parse Jikan response: \(error)", type: "Error")
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
fetchPage()
|
fetchPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateFillerSet(episodes: [JikanEpisode]) {
|
||||||
|
let fillerNumbers = Set(episodes.filter { $0.filler }.map { $0.mal_id })
|
||||||
|
self.jikanFillerSet = fillerNumbers
|
||||||
|
Logger.shared.log("Updated filler set with \(fillerNumbers.count) filler episodes", type: "Debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue