Add podcast index: JSON mapping of article URLs to cached MP3 files
All checks were successful
CI / build-and-deploy (push) Successful in 25s
All checks were successful
CI / build-and-deploy (push) Successful in 25s
PodcastIndex.swift stores {articleUrl, filename, title, createdAt} in
App Support/podcasts/index.json. Updated on every successful download.
Enables building a podcast library view later.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,12 +67,13 @@ struct ClaudeService: Sendable {
|
|||||||
return try JSONDecoder().decode(PodcastStatus.self, from: data)
|
return try JSONDecoder().decode(PodcastStatus.self, from: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadPodcastAudio(jobId: String, articleUrl: String) async throws -> URL {
|
func downloadPodcastAudio(jobId: String, articleUrl: String, title: String? = nil) async throws -> URL {
|
||||||
let dest = ClaudeService.cachedPodcastURL(for: articleUrl)
|
let dest = ClaudeService.cachedPodcastURL(for: articleUrl)
|
||||||
try FileManager.default.createDirectory(at: dest.deletingLastPathComponent(),
|
try FileManager.default.createDirectory(at: dest.deletingLastPathComponent(),
|
||||||
withIntermediateDirectories: true)
|
withIntermediateDirectories: true)
|
||||||
let data = try await get("/v1/podcast/audio/\(jobId)")
|
let data = try await get("/v1/podcast/audio/\(jobId)")
|
||||||
try data.write(to: dest, options: .atomic)
|
try data.write(to: dest, options: .atomic)
|
||||||
|
PodcastIndex.upsert(articleUrl: articleUrl, filename: dest.lastPathComponent, title: title)
|
||||||
return dest
|
return dest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
56
Marks/Services/PodcastIndex.swift
Normal file
56
Marks/Services/PodcastIndex.swift
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PodcastEntry: Codable, Identifiable {
|
||||||
|
var id: String { articleUrl }
|
||||||
|
let articleUrl: String
|
||||||
|
let filename: String
|
||||||
|
var title: String?
|
||||||
|
let createdAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PodcastIndex {
|
||||||
|
private static let indexURL: URL = {
|
||||||
|
let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||||
|
.appendingPathComponent("podcasts")
|
||||||
|
return dir.appendingPathComponent("index.json")
|
||||||
|
}()
|
||||||
|
|
||||||
|
private static let encoder: JSONEncoder = {
|
||||||
|
let e = JSONEncoder()
|
||||||
|
e.dateEncodingStrategy = .iso8601
|
||||||
|
e.outputFormatting = .prettyPrinted
|
||||||
|
return e
|
||||||
|
}()
|
||||||
|
|
||||||
|
private static let decoder: JSONDecoder = {
|
||||||
|
let d = JSONDecoder()
|
||||||
|
d.dateDecodingStrategy = .iso8601
|
||||||
|
return d
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func all() -> [PodcastEntry] {
|
||||||
|
guard let data = try? Data(contentsOf: indexURL),
|
||||||
|
let entries = try? decoder.decode([PodcastEntry].self, from: data)
|
||||||
|
else { return [] }
|
||||||
|
return entries.sorted { $0.createdAt > $1.createdAt }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func upsert(articleUrl: String, filename: String, title: String?) {
|
||||||
|
var entries = all()
|
||||||
|
entries.removeAll { $0.articleUrl == articleUrl }
|
||||||
|
entries.insert(PodcastEntry(articleUrl: articleUrl, filename: filename,
|
||||||
|
title: title, createdAt: Date()), at: 0)
|
||||||
|
save(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func remove(articleUrl: String) {
|
||||||
|
var entries = all()
|
||||||
|
entries.removeAll { $0.articleUrl == articleUrl }
|
||||||
|
save(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func save(_ entries: [PodcastEntry]) {
|
||||||
|
guard let data = try? encoder.encode(entries) else { return }
|
||||||
|
try? data.write(to: indexURL, options: .atomic)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ final class PodcastPlayerViewModel {
|
|||||||
)
|
)
|
||||||
if job.isDone {
|
if job.isDone {
|
||||||
phase = .downloading
|
phase = .downloading
|
||||||
let audioUrl = try await claude.downloadPodcastAudio(jobId: jobId, articleUrl: articleUrl)
|
let audioUrl = try await claude.downloadPodcastAudio(jobId: jobId, articleUrl: articleUrl, title: job.title)
|
||||||
setupPlayer(url: audioUrl, title: job.title ?? "Podcast")
|
setupPlayer(url: audioUrl, title: job.title ?? "Podcast")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user