Initial commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../core/contracts/trakt/trakt_response.dart';
|
||||
import '../../../core/contracts/trakt/trakt_scrobble_target.dart';
|
||||
import '../../../core/contracts/trakt/trakt_sync_job.dart';
|
||||
import '../../../core/domain/ports/authed_trakt_gateway.dart';
|
||||
import '../../../core/infra/trakt_api/trakt_pending_sync_queue.dart';
|
||||
import '../../../shared/utils/app_logger.dart';
|
||||
import 'trakt_scrobble_state_machine.dart';
|
||||
|
||||
const _tag = 'TraktScrobble';
|
||||
|
||||
|
||||
class TraktScrobbleReporter {
|
||||
TraktScrobbleReporter({
|
||||
required this._gateway,
|
||||
required this._queue,
|
||||
required this._target,
|
||||
required this._readProgress,
|
||||
this._appVersion,
|
||||
this._appDate,
|
||||
this._onSuccess,
|
||||
this._onError,
|
||||
});
|
||||
|
||||
final AuthedTraktGateway _gateway;
|
||||
final TraktPendingSyncQueue _queue;
|
||||
final TraktScrobbleTarget? _target;
|
||||
final double Function() _readProgress;
|
||||
final String? _appVersion;
|
||||
final String? _appDate;
|
||||
final void Function()? _onSuccess;
|
||||
final void Function(String error)? _onError;
|
||||
|
||||
final _sm = TraktScrobbleStateMachine();
|
||||
|
||||
bool get _enabled => _target != null;
|
||||
|
||||
Future<void> onPlay() => _dispatch(_sm.onPlay(), _readProgress());
|
||||
|
||||
Future<void> onPause() => _dispatch(_sm.onPause(), _readProgress());
|
||||
|
||||
Future<void> onCompleted() => _dispatch(_sm.onCompleted(), 100);
|
||||
|
||||
|
||||
Future<void> onStop() =>
|
||||
_dispatch(_sm.onStop(progress: _readProgress()), _readProgress());
|
||||
|
||||
Future<void> _dispatch(TraktScrobbleAction action, double progress) async {
|
||||
if (!_enabled) return;
|
||||
final type = _typeFor(action);
|
||||
if (type == null) return;
|
||||
|
||||
final clamped = progress.clamp(0.0, 100.0).toDouble();
|
||||
final body = _target!.toBody(
|
||||
progress: clamped,
|
||||
appVersion: _appVersion,
|
||||
appDate: _appDate,
|
||||
);
|
||||
|
||||
AppLogger.debug(
|
||||
_tag,
|
||||
'scrobble ${type.wire} progress=${clamped.toStringAsFixed(1)} '
|
||||
'(${_target.kind.name})',
|
||||
);
|
||||
|
||||
TraktScrobbleOutcome outcome;
|
||||
try {
|
||||
outcome = await _gateway.scrobble(type, body);
|
||||
} catch (e) {
|
||||
AppLogger.debug(_tag, 'scrobble threw, enqueue', e);
|
||||
await _enqueue(type, body);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (outcome.cls) {
|
||||
case TraktResponseClass.success:
|
||||
case TraktResponseClass.duplicate:
|
||||
AppLogger.debug(_tag, 'scrobble ${type.wire} ok (${outcome.cls.name})');
|
||||
await _queue.discardSupersededBy(type: type, body: body);
|
||||
_onSuccess?.call();
|
||||
case TraktResponseClass.invalid:
|
||||
AppLogger.debug(_tag, 'scrobble ${type.wire} invalid → drop');
|
||||
case TraktResponseClass.rateLimited:
|
||||
case TraktResponseClass.unauthorized:
|
||||
case TraktResponseClass.transient:
|
||||
await _enqueue(type, body);
|
||||
_onError?.call(outcome.cls.name);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _enqueue(
|
||||
TraktSyncJobType type,
|
||||
Map<String, dynamic> body,
|
||||
) async {
|
||||
try {
|
||||
await _queue.enqueue(type: type, body: body);
|
||||
} catch (e) {
|
||||
AppLogger.warn(_tag, 'enqueue failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
static TraktSyncJobType? _typeFor(TraktScrobbleAction action) {
|
||||
switch (action) {
|
||||
case TraktScrobbleAction.none:
|
||||
return null;
|
||||
case TraktScrobbleAction.start:
|
||||
return TraktSyncJobType.scrobbleStart;
|
||||
case TraktScrobbleAction.pause:
|
||||
return TraktSyncJobType.scrobblePause;
|
||||
case TraktScrobbleAction.stop:
|
||||
return TraktSyncJobType.scrobbleStop;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user