二次开发源码提交
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user