mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
140 lines
5.0 KiB
Swift
140 lines
5.0 KiB
Swift
//
|
|
// InvidiousSubscriptionProvider.swift
|
|
// Yattee
|
|
//
|
|
// Invidious subscription provider that manages subscriptions on an Invidious instance.
|
|
// Uses in-memory cache only - does NOT persist to SwiftData to avoid corrupting local subscriptions.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Invidious subscription provider that syncs with an Invidious instance account.
|
|
/// Subscriptions are managed on the Invidious server and kept in memory only.
|
|
/// This provider does NOT write to SwiftData to preserve local subscriptions.
|
|
@MainActor
|
|
final class InvidiousSubscriptionProvider: SubscriptionProvider {
|
|
// MARK: - Properties
|
|
|
|
let accountType: SubscriptionAccountType = .invidious
|
|
|
|
private let invidiousAPI: InvidiousAPI
|
|
private let credentialsManager: InvidiousCredentialsManager
|
|
private let instancesManager: InstancesManager
|
|
private let settingsManager: SettingsManager
|
|
|
|
/// In-memory cache of subscribed channel IDs for fast lookup.
|
|
private var cachedChannelIDs: Set<String> = []
|
|
|
|
/// In-memory cache of full channel data for display.
|
|
private var cachedChannels: [Channel] = []
|
|
|
|
/// Whether the cache has been populated from the server.
|
|
private var cachePopulated = false
|
|
|
|
// MARK: - Initialization
|
|
|
|
init(
|
|
invidiousAPI: InvidiousAPI,
|
|
credentialsManager: InvidiousCredentialsManager,
|
|
instancesManager: InstancesManager,
|
|
settingsManager: SettingsManager
|
|
) {
|
|
self.invidiousAPI = invidiousAPI
|
|
self.credentialsManager = credentialsManager
|
|
self.instancesManager = instancesManager
|
|
self.settingsManager = settingsManager
|
|
}
|
|
|
|
// MARK: - SubscriptionProvider
|
|
|
|
func fetchSubscriptions() async throws -> [Channel] {
|
|
let (instance, sid) = try await getAuthenticatedInstance()
|
|
|
|
// Fetch subscriptions from Invidious
|
|
let subscriptions = try await invidiousAPI.subscriptions(instance: instance, sid: sid)
|
|
|
|
// Update in-memory cache
|
|
cachedChannelIDs = Set(subscriptions.map(\.authorId))
|
|
cachedChannels = subscriptions.map { $0.toChannel(baseURL: instance.url) }
|
|
cachePopulated = true
|
|
|
|
LoggingService.shared.info("Fetched \(subscriptions.count) Invidious subscriptions", category: .api)
|
|
|
|
return cachedChannels
|
|
}
|
|
|
|
func subscribe(to channel: Channel) async throws {
|
|
let (instance, sid) = try await getAuthenticatedInstance()
|
|
|
|
// Subscribe on Invidious server
|
|
try await invidiousAPI.subscribe(to: channel.id.channelID, instance: instance, sid: sid)
|
|
|
|
// Update in-memory cache only
|
|
cachedChannelIDs.insert(channel.id.channelID)
|
|
if !cachedChannels.contains(where: { $0.id.channelID == channel.id.channelID }) {
|
|
cachedChannels.append(channel)
|
|
}
|
|
|
|
LoggingService.shared.info("Subscribed to \(channel.name) on Invidious", category: .api)
|
|
}
|
|
|
|
func unsubscribe(from channelID: String) async throws {
|
|
let (instance, sid) = try await getAuthenticatedInstance()
|
|
|
|
// Unsubscribe on Invidious server
|
|
try await invidiousAPI.unsubscribe(from: channelID, instance: instance, sid: sid)
|
|
|
|
// Update in-memory cache only
|
|
cachedChannelIDs.remove(channelID)
|
|
cachedChannels.removeAll { $0.id.channelID == channelID }
|
|
|
|
LoggingService.shared.info("Unsubscribed from \(channelID) on Invidious", category: .api)
|
|
}
|
|
|
|
func isSubscribed(to channelID: String) async -> Bool {
|
|
// If cache isn't populated yet, try to refresh it
|
|
if !cachePopulated {
|
|
do {
|
|
_ = try await fetchSubscriptions()
|
|
} catch {
|
|
// Can't determine subscription status without server connection
|
|
return false
|
|
}
|
|
}
|
|
return cachedChannelIDs.contains(channelID)
|
|
}
|
|
|
|
func refreshCache() async throws {
|
|
// Simply fetch subscriptions again - this updates in-memory cache
|
|
_ = try await fetchSubscriptions()
|
|
}
|
|
|
|
// MARK: - Private Helpers
|
|
|
|
/// Gets the authenticated Invidious instance and session ID from account settings.
|
|
private func getAuthenticatedInstance() async throws -> (Instance, String) {
|
|
// Get the instance ID from subscription account settings
|
|
let account = settingsManager.subscriptionAccount
|
|
|
|
let instance: Instance?
|
|
if let instanceID = account.instanceID {
|
|
// Use the specific instance from account settings
|
|
instance = instancesManager.instances.first { $0.id == instanceID && $0.isEnabled }
|
|
} else {
|
|
// Fallback to first enabled Invidious instance
|
|
instance = instancesManager.instances.first { $0.type == .invidious && $0.isEnabled }
|
|
}
|
|
|
|
guard let instance else {
|
|
throw SubscriptionProviderError.instanceNotConfigured
|
|
}
|
|
|
|
// Get session ID
|
|
guard let sid = credentialsManager.sid(for: instance) else {
|
|
throw SubscriptionProviderError.notAuthenticated
|
|
}
|
|
|
|
return (instance, sid)
|
|
}
|
|
}
|