Channels caching

This commit is contained in:
Arkadiusz Fal
2022-12-14 00:07:32 +01:00
parent d9622cf24c
commit 3b31f21c81
18 changed files with 151 additions and 18 deletions

View File

@@ -519,6 +519,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
return Channel(
app: .invidious,
id: json["authorId"].stringValue,
name: json["author"].stringValue,
bannerURL: json["authorBanners"].arrayValue.first?.dictionaryValue["url"]?.url,
@@ -666,7 +667,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
likeCount: details["likeCount"]?.int ?? 0,
text: details["content"]?.string ?? "",
repliesPage: details["replies"]?.dictionaryValue["continuation"]?.string,
channel: Channel(id: channelId, name: author)
channel: Channel(app: .invidious, id: channelId, name: author)
)
}

View File

@@ -472,6 +472,7 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
func extractChannel(from json: JSON) -> Channel {
Channel(
app: .peerTube,
id: json["id"].stringValue,
name: json["name"].stringValue
)
@@ -572,7 +573,7 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
likeCount: details["likeCount"]?.int ?? 0,
text: details["content"]?.string ?? "",
repliesPage: details["replies"]?.dictionaryValue["continuation"]?.string,
channel: Channel(id: channelId, name: author)
channel: Channel(app: .peerTube, id: channelId, name: author)
)
}

View File

@@ -410,6 +410,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
} ?? [Channel.Tab]()
return Channel(
app: .piped,
id: id,
name: name,
bannerURL: attributes["bannerUrl"]?.url,
@@ -488,7 +489,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
published: published ?? "",
views: details["views"]?.int ?? 0,
description: description,
channel: Channel(id: channelId, name: author, thumbnailURL: authorThumbnailURL, subscriptionsCount: subscriptionsCount),
channel: Channel(app: .piped, id: channelId, name: author, thumbnailURL: authorThumbnailURL, subscriptionsCount: subscriptionsCount),
thumbnails: thumbnails,
live: live,
likes: details["likes"]?.int,
@@ -667,7 +668,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
likeCount: details["likeCount"]?.int ?? 0,
text: extractCommentText(from: details["commentText"]?.stringValue),
repliesPage: details["repliesPage"]?.string,
channel: Channel(id: channelId, name: author)
channel: Channel(app: .piped, id: channelId, name: author)
)
}

View File

@@ -14,6 +14,7 @@ struct BaseCacheModel {
[
FeedCacheModel.shared,
VideosCacheModel.shared,
ChannelsCacheModel.shared,
PlaylistsCacheModel.shared,
ChannelPlaylistsCacheModel.shared,
SubscribedChannelsModel.shared

View File

@@ -0,0 +1,46 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct ChannelsCacheModel: CacheModel {
static let shared = ChannelsCacheModel()
let logger = Logger(label: "stream.yattee.cache.channels")
static let diskConfig = DiskConfig(name: "channels")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
transformer: BaseCacheModel.jsonTransformer
)
func store(_ channel: Channel) {
guard channel.hasExtendedDetails else {
logger.warning("not caching \(channel.cacheKey)")
return
}
logger.info("caching \(channel.cacheKey)")
try? storage?.setObject(channel.json, forKey: channel.cacheKey)
}
func storeIfMissing(_ channel: Channel) {
guard let storage, !storage.objectExists(forKey: channel.cacheKey) else {
return
}
store(channel)
}
func retrieve(_ cacheKey: String) -> Channel? {
logger.info("retrieving cache for \(cacheKey)")
if let json = try? storage?.object(forKey: cacheKey) {
return Channel.from(json)
}
return nil
}
}

View File

@@ -69,6 +69,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
.onSuccess { resource in
if let channels: [Channel] = resource.typedContent() {
self.channels = channels
channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) }
self.storeChannels(account: account, channels: channels)
FeedModel.shared.calculateUnwatchedFeed()
onSuccess()
@@ -93,6 +94,8 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
let date = iso8601DateFormatter.string(from: Date())
logger.info("caching channels \(channelsDateCacheKey(account)) -- \(date)")
channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) }
let dateObject: JSON = ["date": date]
let channelsObject: JSON = ["channels": channels.map(\.json).map(\.object)]
@@ -106,7 +109,16 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
if let json = try? storage?.object(forKey: channelsCacheKey(account)),
let channels = json.dictionaryValue["channels"]
{
return channels.arrayValue.map { Channel.from($0) }
return channels.arrayValue.map { json in
let channel = Channel.from(json)
if !channel.hasExtendedDetails,
let cache = ChannelsCacheModel.shared.retrieve(channel.cacheKey)
{
return cache
}
return channel
}
}
return []

View File

@@ -19,6 +19,8 @@ struct VideosCacheModel: CacheModel {
func storeVideo(_ video: Video) {
logger.info("caching \(video.cacheKey)")
try? storage?.setObject(video.json, forKey: video.cacheKey)
ChannelsCacheModel.shared.storeIfMissing(video.channel)
}
func retrieveVideo(_ cacheKey: String) -> Video? {

View File

@@ -64,6 +64,10 @@ struct Channel: Identifiable, Hashable {
}
}
var app: VideosApp
var instanceID: Instance.ID?
var instanceURL: URL?
var id: String
var name: String
var bannerURL: URL?
@@ -112,14 +116,37 @@ struct Channel: Identifiable, Hashable {
var json: JSON {
[
"app": app.rawValue,
"id": id,
"name": name,
"thumbnailURL": thumbnailURL?.absoluteString ?? ""
]
}
var cacheKey: String {
switch app {
case .local:
return id
case .invidious:
return "youtube-\(id)"
case .piped:
return "youtube-\(id)"
case .peerTube:
return "peertube-\(instanceURL?.absoluteString ?? "unknown-instance")-\(id)"
}
}
var hasExtendedDetails: Bool {
thumbnailURL != nil
}
var thumbnailURLOrCached: URL? {
thumbnailURL ?? ChannelsCacheModel.shared.retrieve(cacheKey)?.thumbnailURL
}
static func from(_ json: JSON) -> Self {
.init(
app: VideosApp(rawValue: json["app"].stringValue) ?? .local,
id: json["id"].stringValue,
name: json["name"].stringValue,
thumbnailURL: json["thumbnailURL"].url

View File

@@ -98,7 +98,7 @@ struct RecentItem: Defaults.Serializable, Identifiable {
return nil
}
return Channel(id: id, name: title)
return Channel(app: .invidious, id: id, name: title)
}
var playlist: ChannelPlaylist? {

View File

@@ -69,7 +69,7 @@ struct Video: Identifiable, Equatable, Hashable {
views: Int = 0,
description: String? = nil,
genre: String? = nil,
channel: Channel = .init(id: "", name: ""),
channel: Channel? = nil,
thumbnails: [Thumbnail] = [],
indexID: String? = nil,
live: Bool = false,
@@ -96,7 +96,7 @@ struct Video: Identifiable, Equatable, Hashable {
self.views = views
self.description = description
self.genre = genre
self.channel = channel
self.channel = channel ?? .init(app: app, id: "", name: "")
self.thumbnails = thumbnails
self.indexID = indexID
self.live = live