Files
moonwell-launcher/lib/features/downloader/application/download_manager.dart
T
gasaichandesu 58d7808a8d Initial commit
2025-08-30 19:50:39 +04:00

107 lines
3.0 KiB
Dart

import 'dart:async';
import 'package:injectable/injectable.dart';
import 'package:moonwell_launcher/features/downloader/domain/entities/download_exceptions.dart';
import 'package:moonwell_launcher/features/downloader/domain/entities/download_progress.dart';
import 'package:moonwell_launcher/features/downloader/domain/entities/download_request.dart';
import 'package:moonwell_launcher/features/downloader/use_cases/download_object_use_case.dart';
typedef DownloadId = String;
class _Session {
_Session({required this.id, required this.request, required this.useCase})
: progressController = StreamController<DownloadProgress>.broadcast();
final DownloadId id;
final DownloadRequest request;
final DownloadObjectUseCase useCase;
final StreamController<DownloadProgress> progressController;
StreamSubscription<DownloadProgress>? sub;
bool paused = false;
bool cancelled = false;
Stream<DownloadProgress> get stream => progressController.stream;
Future<void> start() async {
cancelled = false;
paused = false;
sub = useCase
.call(
DownloadObjectUseCaseInput(
request: request,
isCancelled: () async => cancelled || paused,
),
)
.listen(
progressController.add,
onError: progressController.addError,
onDone: () => progressController.close(),
);
}
Future<void> pause() async {
paused = true;
await sub?.cancel();
sub = null;
}
Future<void> resume() async {
if (cancelled) return;
paused = false;
await start(); // will resume from file size
}
Future<void> cancel() async {
cancelled = true;
await sub?.cancel();
sub = null;
if (!progressController.isClosed) {
progressController.addError(const CancelledException());
await progressController.close();
}
}
}
@lazySingleton
class DownloadManager {
DownloadManager(this._useCase);
final DownloadObjectUseCase _useCase;
final Map<DownloadId, _Session> _sessions = {};
/// Start or replace a session with [id].
Stream<DownloadProgress> start({
required DownloadId id,
required DownloadRequest request,
}) {
// dispose old session if exists
_sessions[id]?.cancel();
final s = _Session(id: id, request: request, useCase: _useCase);
_sessions[id] = s;
unawaited(s.start());
return s.stream;
}
Stream<DownloadProgress>? progress(DownloadId id) => _sessions[id]?.stream;
Future<void> pause(DownloadId id) => _sessions[id]?.pause() ?? Future.value();
Future<void> resume(DownloadId id) =>
_sessions[id]?.resume() ?? Future.value();
Future<void> cancel(DownloadId id) async {
await _sessions[id]?.cancel();
_sessions.remove(id);
}
bool isActive(DownloadId id) => _sessions.containsKey(id);
Iterable<DownloadId> activeIds() => _sessions.keys;
/// Cancel and clear all sessions (e.g., on sign-out).
Future<void> cancelAll() async {
for (final s in _sessions.values) {
await s.cancel();
}
_sessions.clear();
}
}