mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-23 18:11:02 +02:00
139 lines
4.0 KiB
Dart
139 lines
4.0 KiB
Dart
import 'dart:async';
|
|
|
|
class DownloadVerificationRetryGuard {
|
|
final Set<String> _grantedRetryKeys = {};
|
|
|
|
bool hasRetriedAfterGrant(String itemId, String service) =>
|
|
_grantedRetryKeys.contains(_key(itemId, service));
|
|
|
|
void recordVerificationResult(
|
|
String itemId,
|
|
String service, {
|
|
required bool granted,
|
|
}) {
|
|
if (granted) {
|
|
_grantedRetryKeys.add(_key(itemId, service));
|
|
}
|
|
}
|
|
|
|
void clearItem(String itemId) {
|
|
_grantedRetryKeys.removeWhere(
|
|
(retryKey) => retryKey == itemId || retryKey.startsWith('$itemId::'),
|
|
);
|
|
}
|
|
|
|
void retainItems(Set<String> itemIds) {
|
|
_grantedRetryKeys.removeWhere((retryKey) {
|
|
final itemId = retryKey.split('::').first;
|
|
return !itemIds.contains(itemId);
|
|
});
|
|
}
|
|
|
|
String _key(String itemId, String service) =>
|
|
'$itemId::${service.trim().toLowerCase()}';
|
|
}
|
|
|
|
/// Shares one verification challenge between downloads for the same service
|
|
/// while still allowing each queue item to stop waiting independently.
|
|
///
|
|
/// When the last waiter leaves, [startFlow]'s cancellation signal completes so
|
|
/// browser timers and grant subscriptions can be released immediately.
|
|
class DownloadVerificationWaitCoordinator {
|
|
final Map<String, _ActiveDownloadVerificationFlow> _flowsByService = {};
|
|
final Map<String, Completer<void>> _cancellationsByItem = {};
|
|
|
|
bool hasActiveFlow(String service) =>
|
|
_flowsByService.containsKey(_serviceKey(service));
|
|
|
|
Future<bool> waitForGrant({
|
|
required String itemId,
|
|
required String service,
|
|
required Future<bool> Function(Future<void> cancellationSignal) startFlow,
|
|
}) {
|
|
final previousCancellation = _cancellationsByItem[itemId];
|
|
if (previousCancellation != null && !previousCancellation.isCompleted) {
|
|
previousCancellation.complete();
|
|
}
|
|
|
|
final itemCancellation = Completer<void>();
|
|
_cancellationsByItem[itemId] = itemCancellation;
|
|
|
|
final serviceKey = _serviceKey(service);
|
|
var activeFlow = _flowsByService[serviceKey];
|
|
if (activeFlow == null) {
|
|
final flowCancellation = Completer<void>();
|
|
final result = Future<bool>.sync(
|
|
() => startFlow(flowCancellation.future),
|
|
);
|
|
activeFlow = _ActiveDownloadVerificationFlow(
|
|
result: result,
|
|
cancellation: flowCancellation,
|
|
);
|
|
_flowsByService[serviceKey] = activeFlow;
|
|
}
|
|
|
|
final waiter = Object();
|
|
activeFlow.waiters.add(waiter);
|
|
return _waitForGrant(
|
|
itemId: itemId,
|
|
serviceKey: serviceKey,
|
|
activeFlow: activeFlow,
|
|
waiter: waiter,
|
|
itemCancellation: itemCancellation,
|
|
);
|
|
}
|
|
|
|
Future<bool> _waitForGrant({
|
|
required String itemId,
|
|
required String serviceKey,
|
|
required _ActiveDownloadVerificationFlow activeFlow,
|
|
required Object waiter,
|
|
required Completer<void> itemCancellation,
|
|
}) async {
|
|
try {
|
|
return await Future.any<bool>([
|
|
activeFlow.result,
|
|
itemCancellation.future.then((_) => false),
|
|
]);
|
|
} finally {
|
|
if (identical(_cancellationsByItem[itemId], itemCancellation)) {
|
|
_cancellationsByItem.remove(itemId);
|
|
}
|
|
activeFlow.waiters.remove(waiter);
|
|
if (activeFlow.waiters.isEmpty &&
|
|
identical(_flowsByService[serviceKey], activeFlow)) {
|
|
_flowsByService.remove(serviceKey);
|
|
if (!activeFlow.cancellation.isCompleted) {
|
|
activeFlow.cancellation.complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cancelItem(String itemId) {
|
|
final cancellation = _cancellationsByItem[itemId];
|
|
if (cancellation != null && !cancellation.isCompleted) {
|
|
cancellation.complete();
|
|
}
|
|
}
|
|
|
|
void cancelAll() {
|
|
for (final cancellation in _cancellationsByItem.values.toList()) {
|
|
if (!cancellation.isCompleted) cancellation.complete();
|
|
}
|
|
}
|
|
|
|
String _serviceKey(String service) => service.trim().toLowerCase();
|
|
}
|
|
|
|
class _ActiveDownloadVerificationFlow {
|
|
_ActiveDownloadVerificationFlow({
|
|
required this.result,
|
|
required this.cancellation,
|
|
});
|
|
|
|
final Future<bool> result;
|
|
final Completer<void> cancellation;
|
|
final Set<Object> waiters = {};
|
|
}
|