mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Feed cache
This commit is contained in:
@@ -84,4 +84,8 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(username)
|
||||
}
|
||||
|
||||
var feedCacheKey: String {
|
||||
"feed-\(id)"
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
static let basePath = "/api/v1"
|
||||
|
||||
@Published var account: Account!
|
||||
@Published var validInstance = true
|
||||
|
||||
static func withAnonymousAccountForInstanceURL(_ url: URL) -> InvidiousAPI {
|
||||
.init(account: Instance(app: .invidious, apiURLString: url.absoluteString).anonymousAccount)
|
||||
@@ -35,8 +34,6 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
func setAccount(_ account: Account) {
|
||||
self.account = account
|
||||
|
||||
validInstance = account.anonymous
|
||||
|
||||
configure()
|
||||
|
||||
if !account.anonymous {
|
||||
@@ -45,31 +42,15 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
}
|
||||
|
||||
func validate() {
|
||||
validateInstance()
|
||||
validateSID()
|
||||
}
|
||||
|
||||
func validateInstance() {
|
||||
guard !validInstance else {
|
||||
return
|
||||
}
|
||||
|
||||
home?
|
||||
.load()
|
||||
.onSuccess { _ in
|
||||
self.validInstance = true
|
||||
}
|
||||
.onFailure { _ in
|
||||
self.validInstance = false
|
||||
}
|
||||
}
|
||||
|
||||
func validateSID() {
|
||||
guard signedIn, !(account.token?.isEmpty ?? true) else {
|
||||
return
|
||||
}
|
||||
|
||||
feed?
|
||||
notifications?
|
||||
.load()
|
||||
.onFailure { _ in
|
||||
self.updateToken(force: true)
|
||||
@@ -273,8 +254,17 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "/feed/subscriptions")
|
||||
}
|
||||
|
||||
var feed: Resource? {
|
||||
func feed(_ page: Int?) -> Resource? {
|
||||
resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
|
||||
.withParam("page", String(page ?? 1))
|
||||
}
|
||||
|
||||
var feed: Resource? {
|
||||
resource(baseURL: account.url, path: basePathAppending("auth/feed"))
|
||||
}
|
||||
|
||||
var notifications: Resource? {
|
||||
resource(baseURL: account.url, path: basePathAppending("auth/notifications"))
|
||||
}
|
||||
|
||||
var subscriptions: Resource? {
|
||||
|
@@ -70,7 +70,7 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
|
||||
return
|
||||
}
|
||||
|
||||
feed?
|
||||
feed(1)?
|
||||
.load()
|
||||
.onFailure { _ in
|
||||
self.updateToken(force: true)
|
||||
@@ -262,8 +262,9 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "/feed/subscriptions")
|
||||
}
|
||||
|
||||
var feed: Resource? {
|
||||
func feed(_ page: Int?) -> Resource? {
|
||||
resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
|
||||
.withParam("page", String(page ?? 1))
|
||||
}
|
||||
|
||||
var subscriptions: Resource? {
|
||||
|
@@ -220,7 +220,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.instance.apiURL, path: "subscriptions")
|
||||
}
|
||||
|
||||
var feed: Resource? {
|
||||
func feed(_: Int?) -> Resource? {
|
||||
resource(baseURL: account.instance.apiURL, path: "feed")
|
||||
.withParam("authToken", account.token)
|
||||
}
|
||||
|
@@ -18,8 +18,8 @@ protocol VideosAPI {
|
||||
|
||||
func video(_ id: Video.ID) -> Resource
|
||||
|
||||
func feed(_ page: Int?) -> Resource?
|
||||
var subscriptions: Resource? { get }
|
||||
var feed: Resource? { get }
|
||||
var home: Resource? { get }
|
||||
var popular: Resource? { get }
|
||||
var playlists: Resource? { get }
|
||||
|
@@ -30,6 +30,10 @@ enum VideosApp: String, CaseIterable {
|
||||
supportsAccounts
|
||||
}
|
||||
|
||||
var paginatesSubscriptions: Bool {
|
||||
self == .invidious
|
||||
}
|
||||
|
||||
var supportsTrendingCategories: Bool {
|
||||
self == .invidious
|
||||
}
|
||||
|
@@ -1,16 +1,40 @@
|
||||
import Cache
|
||||
import Foundation
|
||||
import Logging
|
||||
import SwiftyJSON
|
||||
|
||||
struct CacheModel {
|
||||
static var shared = CacheModel()
|
||||
|
||||
static let jsonToDataTransformer: (JSON) -> Data = { try! $0.rawData() }
|
||||
static let jsonFromDataTransformer: (Data) -> JSON = { try! JSON(data: $0) }
|
||||
static let jsonTransformer = Transformer(toData: jsonToDataTransformer, fromData: jsonFromDataTransformer)
|
||||
|
||||
let logger = Logger(label: "stream.yattee.cache")
|
||||
|
||||
static let bookmarksGroup = "group.stream.yattee.app.bookmarks"
|
||||
let bookmarksDefaults = UserDefaults(suiteName: Self.bookmarksGroup)
|
||||
|
||||
func removeAll() {
|
||||
func clearBookmarks() {
|
||||
guard let bookmarksDefaults else { return }
|
||||
bookmarksDefaults.dictionaryRepresentation().keys.forEach(bookmarksDefaults.removeObject(forKey:))
|
||||
}
|
||||
|
||||
func clear() {
|
||||
FeedCacheModel.shared.clear()
|
||||
VideosCacheModel.shared.clear()
|
||||
}
|
||||
|
||||
var totalSize: Int {
|
||||
(FeedCacheModel.shared.storage.totalDiskStorageSize ?? 0) +
|
||||
(VideosCacheModel.shared.storage.totalDiskStorageSize ?? 0)
|
||||
}
|
||||
|
||||
var totalSizeFormatted: String {
|
||||
totalSizeFormatter.string(fromByteCount: Int64(totalSize))
|
||||
}
|
||||
|
||||
private var totalSizeFormatter: ByteCountFormatter {
|
||||
.init()
|
||||
}
|
||||
}
|
||||
|
62
Model/Cache/FeedCacheModel.swift
Normal file
62
Model/Cache/FeedCacheModel.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
import Cache
|
||||
import Foundation
|
||||
import Logging
|
||||
import SwiftyJSON
|
||||
|
||||
struct FeedCacheModel {
|
||||
static let shared = FeedCacheModel()
|
||||
let logger = Logger(label: "stream.yattee.cache.feed")
|
||||
|
||||
static let diskConfig = DiskConfig(name: "feed")
|
||||
static let memoryConfig = MemoryConfig()
|
||||
|
||||
let storage = try! Storage<String, JSON>(
|
||||
diskConfig: Self.diskConfig,
|
||||
memoryConfig: Self.memoryConfig,
|
||||
transformer: CacheModel.jsonTransformer
|
||||
)
|
||||
|
||||
func storeFeed(account: Account, videos: [Video]) {
|
||||
let date = dateFormatter.string(from: Date())
|
||||
logger.info("caching feed \(account.feedCacheKey) -- \(date)")
|
||||
let feedTimeObject: JSON = ["date": date]
|
||||
let videosObject: JSON = ["videos": videos.map(\.json).map(\.object)]
|
||||
try? storage.setObject(feedTimeObject, forKey: feedTimeCacheKey(account.feedCacheKey))
|
||||
try? storage.setObject(videosObject, forKey: account.feedCacheKey)
|
||||
}
|
||||
|
||||
func retrieveFeed(account: Account) -> [Video] {
|
||||
logger.info("retrieving cache for \(account.feedCacheKey)")
|
||||
|
||||
if let json = try? storage.object(forKey: account.feedCacheKey),
|
||||
let videos = json.dictionaryValue["videos"]
|
||||
{
|
||||
return videos.arrayValue.map { Video.from($0) }
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
func getFeedTime(account: Account) -> Date? {
|
||||
if let json = try? storage.object(forKey: feedTimeCacheKey(account.feedCacheKey)),
|
||||
let string = json.dictionaryValue["date"]?.string,
|
||||
let date = dateFormatter.date(from: string)
|
||||
{
|
||||
return date
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clear() {
|
||||
try? storage.removeAll()
|
||||
}
|
||||
|
||||
private var dateFormatter: ISO8601DateFormatter {
|
||||
.init()
|
||||
}
|
||||
|
||||
private func feedTimeCacheKey(_ feedCacheKey: String) -> String {
|
||||
"\(feedCacheKey)-feedTime"
|
||||
}
|
||||
}
|
@@ -7,31 +7,31 @@ struct VideosCacheModel {
|
||||
static let shared = VideosCacheModel()
|
||||
let logger = Logger(label: "stream.yattee.cache.videos")
|
||||
|
||||
static let jsonToDataTransformer: (JSON) -> Data = { try! $0.rawData() }
|
||||
static let jsonFromDataTransformer: (Data) -> JSON = { try! JSON(data: $0) }
|
||||
static let jsonTransformer = Transformer(toData: jsonToDataTransformer, fromData: jsonFromDataTransformer)
|
||||
static let diskConfig = DiskConfig(name: "videos")
|
||||
static let memoryConfig = MemoryConfig()
|
||||
|
||||
static let videosStorageDiskConfig = DiskConfig(name: "videos")
|
||||
static let vidoesStorageMemoryConfig = MemoryConfig()
|
||||
|
||||
let videosStorage = try! Storage<String, JSON>(
|
||||
diskConfig: Self.videosStorageDiskConfig,
|
||||
memoryConfig: Self.vidoesStorageMemoryConfig,
|
||||
transformer: Self.jsonTransformer
|
||||
let storage = try! Storage<String, JSON>(
|
||||
diskConfig: Self.diskConfig,
|
||||
memoryConfig: Self.memoryConfig,
|
||||
transformer: CacheModel.jsonTransformer
|
||||
)
|
||||
|
||||
func storeVideo(_ video: Video) {
|
||||
logger.info("caching \(video.cacheKey)")
|
||||
try? videosStorage.setObject(video.json, forKey: video.cacheKey)
|
||||
try? storage.setObject(video.json, forKey: video.cacheKey)
|
||||
}
|
||||
|
||||
func retrieveVideo(_ cacheKey: String) -> Video? {
|
||||
logger.info("retrieving cache for \(cacheKey)")
|
||||
|
||||
if let json = try? videosStorage.object(forKey: cacheKey) {
|
||||
if let json = try? storage.object(forKey: cacheKey) {
|
||||
return Video.from(json)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clear() {
|
||||
try? storage.removeAll()
|
||||
}
|
||||
}
|
||||
|
@@ -103,7 +103,7 @@ extension PlayerModel {
|
||||
|
||||
func removeHistory() {
|
||||
removeAllWatches()
|
||||
CacheModel.shared.removeAll()
|
||||
CacheModel.shared.clearBookmarks()
|
||||
}
|
||||
|
||||
func removeWatch(_ watch: Watch) {
|
||||
|
@@ -279,7 +279,7 @@ extension PlayerModel {
|
||||
}
|
||||
|
||||
func loadQueueVideoDetails(_ item: PlayerQueueItem) {
|
||||
guard !accounts.current.isNil, !item.hasDetailsLoaded else { return }
|
||||
guard !accounts.current.isNil, !item.hasDetailsLoaded, let video = item.video else { return }
|
||||
|
||||
let videoID = item.video?.videoID ?? item.videoID
|
||||
|
||||
@@ -292,7 +292,7 @@ extension PlayerModel {
|
||||
return
|
||||
}
|
||||
|
||||
playerAPI(item.video).loadDetails(item, completionHandler: { [weak self] newItem in
|
||||
playerAPI(video)?.loadDetails(item, completionHandler: { [weak self] newItem in
|
||||
guard let self else { return }
|
||||
|
||||
self.queue.filter { $0.videoID == item.videoID }.forEach { item in
|
||||
|
Reference in New Issue
Block a user