Files
app/lib/presentation/providers/download_manager_provider.dart
2026-05-15 09:03:09 +08:00

448 lines
13 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../../core/constants/storage_keys.dart';
import '../../data/models/download_task_model.dart';
import '../../services/download_service.dart';
import '../../services/storage_service.dart';
import '../../core/utils/app_logger.dart';
/// 下载管理Provider
class DownloadManagerProvider extends ChangeNotifier {
final DownloadService _downloadService = DownloadService();
final Map<String, DownloadTaskModel> _tasks = {};
bool _isInitialized = false;
bool _isWifiOnlyEnabled = false;
// 速度追踪:记录每个任务的上次进度更新时间和字节数
final Map<String, DateTime> _lastProgressTime = {};
final Map<String, int> _lastProgressBytes = {};
/// 获取所有下载任务
List<DownloadTaskModel> get tasks => _tasks.values.toList()
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
/// 获取指定状态的任务
List<DownloadTaskModel> getTasksByStatus(DownloadStatus status) {
return tasks.where((task) => task.status == status).toList();
}
/// 下载中的任务数
int get downloadingCount =>
getTasksByStatus(DownloadStatus.downloading).length;
/// 活跃任务数(下载中 + 等待中 + 暂停)
int get activeTaskCount => tasks
.where((t) =>
t.status == DownloadStatus.downloading ||
t.status == DownloadStatus.waiting ||
t.status == DownloadStatus.paused)
.length;
/// WiFi-only 设置
bool get isWifiOnlyEnabled => _isWifiOnlyEnabled;
/// 初始化下载服务
Future<void> initialize() async {
if (_isInitialized) return;
await _downloadService.initialize(callbackHandler: _handleDownloadCallback);
// 加载 WiFi-only 设置
_isWifiOnlyEnabled =
await StorageService.instance.getBool(StorageKeys.downloadWifiOnly) ??
false;
// 从本地存储加载已保存的下载任务
await _loadTasks();
_isInitialized = true;
AppLogger.d('DownloadManagerProvider 初始化完成');
}
/// 更新 WiFi-only 设置,并同步等待中的任务
Future<void> setWifiOnlyEnabled(bool value) async {
_isWifiOnlyEnabled = value;
await StorageService.instance
.setBool(StorageKeys.downloadWifiOnly, value);
// 如果关闭了WiFi-only,需要将等待WiFi的任务重新入队
if (!value) {
for (final task in _tasks.values.toList()) {
if (task.waitingForWifi) {
// 取消当前等待WiFi的任务,重新入队(不需要WiFi)
await _downloadService.cancelDownload(task.id);
_tasks[task.id] = task.copyWith(
status: DownloadStatus.waiting,
waitingForWifi: false,
);
await _saveTasks();
// 重新开始下载
await _downloadService.startDownload(_tasks[task.id]!);
}
}
}
notifyListeners();
}
/// 添加下载任务
Future<DownloadTaskModel?> addDownloadTask({
required String fileName,
required String fileUri,
required int fileSize,
String? savePath,
}) async {
// 如果已存在相同文件的任务,返回null
DownloadTaskModel? existingTask;
for (final task in _tasks.values) {
if (task.fileUri == fileUri) {
existingTask = task;
break;
}
}
if (existingTask != null) {
return null;
}
// 确保下载服务已初始化
await initialize();
// 获取保存路径
if (savePath == null) {
final dir = await _downloadService.getDownloadDirectory();
savePath = '${dir.path}/$fileName';
}
// 创建任务ID
final id = DateTime.now().millisecondsSinceEpoch.toString();
final task = DownloadTaskModel(
id: id,
fileName: fileName,
fileUri: fileUri,
fileSize: fileSize,
savePath: savePath,
status: DownloadStatus.waiting,
);
_tasks[id] = task;
await _saveTasks();
notifyListeners();
// 开始下载
AppLogger.d(
'准备开始下载任务: ${task.id}, 文件: ${task.fileName}, 下载状态: ${task.status}');
final bdTaskId = await _downloadService.startDownload(task);
AppLogger.d('startDownload 返回: bdTaskId=$bdTaskId');
if (bdTaskId == null) {
// 下载失败,更新任务状态
_tasks[id] = task.copyWith(
status: DownloadStatus.failed,
errorMessage: '无法创建下载任务',
);
notifyListeners();
return null;
}
return task;
}
/// 批量添加下载任务
Future<void> addBatchDownloadTasks(List<Map<String, dynamic>> files) async {
await initialize();
final dir = await _downloadService.getDownloadDirectory();
for (final file in files) {
final fileName = file['name'] as String;
final fileUri = file['path'] as String;
final fileSize = file['size'] as int? ?? 0;
await addDownloadTask(
fileName: fileName,
fileUri: fileUri,
fileSize: fileSize,
savePath: '${dir.path}/$fileName',
);
}
}
/// 处理下载回调
void _handleDownloadCallback(
String taskId, DownloadStatus status, int progress) async {
AppLogger.d(
'DownloadManagerProvider._handleDownloadCallback: taskId=$taskId, status=$status, progress=$progress');
// 获取当前任务
final task = _tasks[taskId];
if (task == null) {
AppLogger.d('任务不存在: taskId=$taskId');
return;
}
// 计算下载字节数和速度
final downloadedBytes = (task.fileSize * progress / 100).toInt();
int speed = 0;
final now = DateTime.now();
final lastTime = _lastProgressTime[taskId];
final lastBytes = _lastProgressBytes[taskId] ?? 0;
if (status == DownloadStatus.downloading && lastTime != null) {
final elapsed = now.difference(lastTime).inMilliseconds;
if (elapsed > 0) {
speed = ((downloadedBytes - lastBytes) * 1000 / elapsed).round();
}
}
if (status == DownloadStatus.downloading) {
_lastProgressTime[taskId] = now;
_lastProgressBytes[taskId] = downloadedBytes;
} else {
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
}
// 判断是否在等待WiFi
final waitingForWifi =
status == DownloadStatus.waiting && _isWifiOnlyEnabled;
// 更新任务
final updatedTask = task.copyWith(
status: status,
downloadedBytes: downloadedBytes,
speed: speed,
waitingForWifi: waitingForWifi,
);
// 如果下载完成,设置完成时间
if (status == DownloadStatus.completed) {
_tasks[taskId] = updatedTask.copyWith(
completedAt: DateTime.now(),
speed: 0,
);
} else {
_tasks[taskId] = updatedTask;
}
AppLogger.d('任务已更新: ${_tasks[taskId]!.status}');
await _saveTasks();
notifyListeners();
}
/// 恢复下载
Future<void> resumeDownload(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
await _downloadService.resumeDownload(taskId);
}
}
/// 暂停下载
Future<void> pauseDownload(String taskId) async {
await _downloadService.pauseDownload(taskId);
final task = _tasks[taskId];
if (task != null) {
if (task.status == DownloadStatus.downloading) {
_tasks[taskId] = task.copyWith(
status: DownloadStatus.paused, speed: 0, waitingForWifi: false);
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
await _saveTasks();
notifyListeners();
}
}
}
/// 取消下载
Future<void> cancelDownload(String taskId) async {
await _downloadService.cancelDownload(taskId);
final task = _tasks[taskId];
if (task != null) {
_tasks[taskId] = task.copyWith(
status: DownloadStatus.cancelled, waitingForWifi: false);
await _saveTasks();
notifyListeners();
// 延迟移除任务,同时从存储中删除
Future.delayed(const Duration(seconds: 2), () {
_tasks.remove(taskId);
_downloadService.disposeTask(taskId);
_saveTasks();
notifyListeners();
});
}
}
/// 删除下载任务(包括文件)
Future<void> deleteDownloadTask(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
// 删除已下载的文件
if (task.status == DownloadStatus.completed) {
await _downloadService.deleteDownloadedFile(task.savePath);
}
// 移除任务
_tasks.remove(taskId);
_downloadService.disposeTask(taskId);
await _saveTasks();
notifyListeners();
}
}
/// 重新下载
Future<void> retryDownload(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
// 删除已下载的部分文件
await _downloadService.deleteDownloadedFile(task.savePath);
// 重置任务状态
_tasks[taskId] = task.copyWith(
downloadedBytes: 0,
speed: 0,
status: DownloadStatus.waiting,
errorMessage: null,
completedAt: null,
waitingForWifi: false,
);
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
await _saveTasks();
notifyListeners();
// 重新开始下载
await _downloadService.startDownload(_tasks[taskId]!);
}
}
/// 清空所有已完成的任务
Future<void> clearCompletedTasks() async {
final completedTasks = getTasksByStatus(DownloadStatus.completed);
for (final task in completedTasks) {
await deleteDownloadTask(task.id);
}
}
/// 清空所有失败的任务
Future<void> clearFailedTasks() async {
final failedTasks = getTasksByStatus(DownloadStatus.failed);
for (final task in failedTasks) {
_tasks.remove(task.id);
_downloadService.disposeTask(task.id);
}
await _saveTasks();
notifyListeners();
}
/// 获取任务
DownloadTaskModel? getTask(String taskId) {
return _tasks[taskId];
}
/// 从本地存储加载下载任务
Future<void> _loadTasks() async {
try {
final tasksJson =
await StorageService.instance.getString(StorageKeys.downloadTasks);
if (tasksJson == null || tasksJson.isEmpty) {
AppLogger.d('没有保存的下载任务');
return;
}
final tasksList = jsonDecode(tasksJson) as List<dynamic>;
final loadedTasks = <DownloadTaskModel>[];
final now = DateTime.now();
for (final taskJson in tasksList) {
try {
final task =
DownloadTaskModel.fromJson(taskJson as Map<String, dynamic>);
// 过滤掉已取消的任务(修复4:已取消任务不恢复)
if (task.status == DownloadStatus.cancelled) {
continue;
}
// 如果任务已完成,只保留配置天数内的记录
if (task.status == DownloadStatus.completed) {
if (task.completedAt == null) {
continue;
}
final retentionDays = await StorageService.instance
.getInt(StorageKeys.taskRetentionDays) ??
7;
// retentionDays == -1 表示永不过期
if (retentionDays > 0) {
final daysSinceCompletion =
now.difference(task.completedAt!).inDays;
if (daysSinceCompletion > retentionDays) {
AppLogger.d(
'跳过超过$retentionDays天的已完成任务: ${task.fileName}');
continue;
}
}
}
loadedTasks.add(task);
} catch (e) {
AppLogger.d('解析下载任务失败: $e');
}
}
// 将加载的任务添加到当前任务列表
for (final task in loadedTasks) {
_tasks[task.id] = task;
}
AppLogger.d('从存储加载了 ${loadedTasks.length} 个下载任务');
// 通知 UI 更新
if (loadedTasks.isNotEmpty) {
notifyListeners();
}
// 恢复未完成的任务
for (final task in loadedTasks) {
if (task.status == DownloadStatus.downloading ||
task.status == DownloadStatus.waiting) {
AppLogger.d('恢复下载任务: ${task.fileName}');
// 使用 resumeDownloadAfterRestart 支持断点续传
await _downloadService.resumeDownloadAfterRestart(task);
} else if (task.status == DownloadStatus.paused) {
// 修复5:暂停的任务需要重建 bdTasks 映射,以便继续下载
AppLogger.d('重建暂停任务映射: ${task.fileName}');
await _downloadService.resumeDownloadAfterRestart(task);
// 重建映射后立即暂停,保持任务在暂停状态
await _downloadService.pauseDownload(task.id);
}
}
} catch (e) {
AppLogger.d('加载下载任务失败: $e');
}
}
/// 保存下载任务到本地存储
Future<void> _saveTasks() async {
try {
final tasksList = _tasks.values.map((task) => task.toJson()).toList();
final tasksJson = jsonEncode(tasksList);
await StorageService.instance
.setString(StorageKeys.downloadTasks, tasksJson);
AppLogger.d('已保存 ${_tasks.length} 个下载任务到存储');
} catch (e) {
AppLogger.d('保存下载任务失败: $e');
}
}
@override
void dispose() {
_downloadService.dispose();
super.dispose();
}
}