Connects MDBList through its OAuth device-code grant, registered as a Device Code app so no client secret or redirect URI ships in the binary and TV, mobile and desktop all use the same flow. MDBList omits `verification_uri_complete`, but its device page seeds the code field from a `user_code` query parameter and the sign-in redirect preserves the query string, so the activation link is built locally and the dialog's open button lands on a filled-in form instead of an empty one. A server-supplied complete URL still wins if one ever appears. Poll state is read from the response body rather than the status code: `authorization_pending` and `slow_down` both arrive as HTTP 400, and a missing grant answers 404 `device_not_found`. Writes go out as real-time `/scrobble/*` reports plus `/sync/watched` for the marks that never pass through the player, with ratings on `/sync/ratings`. Matching uses IMDb and TMDb only — MDBList's id block has no `tvdb` field, so a TVDB-only item is skipped rather than written under an empty id block.
55 lines
2.1 KiB
Dart
55 lines
2.1 KiB
Dart
import '../../profiles/profile.dart';
|
|
import '../base_shared_preferences_service.dart';
|
|
import 'tracker_constants.dart';
|
|
import 'tracker_session.dart';
|
|
|
|
/// Per-Plex-profile session persistence for any tracker service.
|
|
///
|
|
/// Keyed by `user_{uuid}_{baseKey}` so each Plex Home profile gets its own
|
|
/// stored session. The storage key remains service-specific, while the payload
|
|
/// is the unified [TrackerSession] JSON shape.
|
|
///
|
|
/// Pass an empty `userUuid` to fall back to a single global slot (used
|
|
/// before a profile has been selected).
|
|
class TrackerAccountStore {
|
|
static final Map<TrackerService, TrackerAccountStore> _stores = {
|
|
TrackerService.mal: TrackerAccountStore._(TrackerService.mal, 'mal_session'),
|
|
TrackerService.anilist: TrackerAccountStore._(TrackerService.anilist, 'anilist_session'),
|
|
TrackerService.simkl: TrackerAccountStore._(TrackerService.simkl, 'simkl_session'),
|
|
TrackerService.trakt: TrackerAccountStore._(TrackerService.trakt, 'trakt_session'),
|
|
TrackerService.mdblist: TrackerAccountStore._(TrackerService.mdblist, 'mdblist_session'),
|
|
};
|
|
|
|
static TrackerAccountStore forService(TrackerService service) => _stores[service]!;
|
|
|
|
final TrackerService service;
|
|
final String _baseKey;
|
|
|
|
TrackerAccountStore._(this.service, this._baseKey);
|
|
|
|
String _scopedKey(String userUuid) => profileScopedPrefsKey(userUuid, _baseKey);
|
|
|
|
Future<TrackerSession?> load(String userUuid) async {
|
|
final prefs = await BaseSharedPreferencesService.sharedCache();
|
|
final raw = readTolerantString(prefs, _scopedKey(userUuid));
|
|
if (raw == null) return null;
|
|
try {
|
|
return TrackerSession.decode(raw, service: service);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> save(String userUuid, TrackerSession session) async {
|
|
final prefs = await BaseSharedPreferencesService.sharedCache();
|
|
await prefs.setString(_scopedKey(userUuid), session.encode());
|
|
}
|
|
|
|
Future<void> clear(String userUuid) async {
|
|
final prefs = await BaseSharedPreferencesService.sharedCache();
|
|
await prefs.remove(_scopedKey(userUuid));
|
|
}
|
|
}
|
|
|
|
TrackerAccountStore trackerAccountStore(TrackerService service) => TrackerAccountStore.forService(service);
|