mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 21:22:53 +00:00
SubscriptionService held one provider per backend and each resolved its instance from settingsManager.subscriptionAccount, falling back to "the first enabled Invidious instance" when that was unset. With several logins possible on one instance that model breaks twice: two accounts would share a single in-memory subscription cache, and the fallback could send an account's request to a server it holds no credential for. Providers are now built per account and cached by account ID, and each resolves its instance and credential from the account it was built for - no fallback. The protocol's accountType becomes a DataContext, which is what actually identifies a provider now. Views that branched on "is the subscription account local" read the context instead, and the settings picker writes both stores. Keeping the legacy setting in step is deliberate for now: several views still read it, and letting the two diverge would show one account in the UI while serving another's data. The Phase 3 switcher removes the last readers. Signing out discards the account's provider so its cached subscriptions cannot be served after the account is gone.
88 lines
3.1 KiB
Swift
88 lines
3.1 KiB
Swift
//
|
|
// SubscriptionProvider.swift
|
|
// Yattee
|
|
//
|
|
// Protocol defining subscription management operations.
|
|
// Implementations handle different subscription sources (local, Invidious, Piped).
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Protocol for subscription management providers.
|
|
/// Each provider implementation handles a specific subscription source.
|
|
@MainActor
|
|
protocol SubscriptionProvider: Sendable {
|
|
/// The data context this provider serves.
|
|
///
|
|
/// A context rather than a backend type: an instance can hold several
|
|
/// accounts, so "which Invidious" is no longer enough to identify a
|
|
/// provider — each account gets its own, with its own cache.
|
|
var context: DataContext { get }
|
|
|
|
/// Fetches all subscriptions from the provider.
|
|
/// - Returns: Array of channels the user is subscribed to.
|
|
func fetchSubscriptions() async throws -> [Channel]
|
|
|
|
/// Subscribes to a channel.
|
|
/// - Parameter channel: The channel to subscribe to.
|
|
func subscribe(to channel: Channel) async throws
|
|
|
|
/// Unsubscribes from a channel.
|
|
/// - Parameter channelID: The channel ID to unsubscribe from.
|
|
func unsubscribe(from channelID: String) async throws
|
|
|
|
/// Checks if subscribed to a channel.
|
|
/// - Parameter channelID: The channel ID to check.
|
|
/// - Returns: `true` if subscribed, `false` otherwise.
|
|
func isSubscribed(to channelID: String) async -> Bool
|
|
|
|
/// The subscription count known without a network fetch.
|
|
/// Returns nil when the provider hasn't populated its cache yet.
|
|
var cachedSubscriptionCount: Int? { get }
|
|
|
|
/// Refreshes the local cache of subscriptions from the remote source.
|
|
/// For local provider, this is a no-op.
|
|
func refreshCache() async throws
|
|
}
|
|
|
|
// MARK: - Default Implementations
|
|
|
|
extension SubscriptionProvider {
|
|
/// Default implementation for providers that don't need cache refresh.
|
|
func refreshCache() async throws {
|
|
// No-op by default
|
|
}
|
|
}
|
|
|
|
// MARK: - Subscription Provider Error
|
|
|
|
/// Errors that can occur during subscription provider operations.
|
|
enum SubscriptionProviderError: Error, LocalizedError, Equatable, Sendable {
|
|
case notAuthenticated
|
|
case networkError(String)
|
|
case channelNotFound
|
|
case alreadySubscribed
|
|
case notSubscribed
|
|
case instanceNotConfigured
|
|
case operationFailed(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .notAuthenticated:
|
|
return String(localized: "subscription.error.notAuthenticated")
|
|
case .networkError(let message):
|
|
return String(localized: "subscription.error.network \(message)")
|
|
case .channelNotFound:
|
|
return String(localized: "subscription.error.channelNotFound")
|
|
case .alreadySubscribed:
|
|
return String(localized: "subscription.error.alreadySubscribed")
|
|
case .notSubscribed:
|
|
return String(localized: "subscription.error.notSubscribed")
|
|
case .instanceNotConfigured:
|
|
return String(localized: "subscription.error.instanceNotConfigured")
|
|
case .operationFailed(let message):
|
|
return String(localized: "subscription.error.operationFailed \(message)")
|
|
}
|
|
}
|
|
}
|