You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
4.5 KiB

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:logger/web.dart';
/// OTA下载服务
/// 负责处理固件包的下载、文件管理等功能
class OtaDownloadService {
late final Dio _dio;
/// 构造函数
/// 初始化Dio实例并配置SSL证书处理
OtaDownloadService() {
_dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 30), // 增加连接超时时间
receiveTimeout: Duration.zero,
sendTimeout: const Duration(seconds: 30), // 增加发送超时时间
headers: {'Accept-Encoding': 'gzip'},
));
// 配置SSL证书验证
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
// 处理SSL证书验证
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
// 在生产环境中,应该进行适当的证书验证
// 这里为了解决证书过期问题,暂时跳过验证
// 建议:在生产环境中实现更安全的证书验证逻辑
Logger().w('SSL证书验证跳过: $host:$port');
return true;
};
return client;
};
}
/// 下载固件包
/// [downloadUrl] 下载地址
/// [onProgress] 下载进度回调
/// [onStatusUpdate] 状态更新回调
/// 返回下载文件的本地路径
/// 抛出异常如果下载失败
Future<String> downloadFirmware({
required String downloadUrl,
required Function(double) onProgress,
required Function(String) onStatusUpdate,
}) async {
onStatusUpdate('准备下载固件包...');
final fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1);
const directoryName = "ota_upgrades";
// 获取下载目录
onStatusUpdate('正在获取下载目录...');
final Directory? downloadsDir = await getApplicationSupportDirectory();
if (downloadsDir == null) {
throw FileSystemException('无法访问下载目录');
}
final targetDir = Directory('${downloadsDir.path}/Download/$directoryName');
if (!await targetDir.exists()) {
onStatusUpdate('创建下载目录...');
await targetDir.create(recursive: true);
}
final savePath = '${targetDir.path}/$fileName';
// 检查是否已存在同名文件
onStatusUpdate('检查本地文件...');
if (await _checkExistingFile(targetDir, fileName)) {
onStatusUpdate('文件已存在,准备升级...');
return savePath;
}
// 清理旧文件
onStatusUpdate('清理旧文件...');
await _cleanOldFiles(targetDir);
// 开始下载
onStatusUpdate('开始下载固件包...');
Logger().d('文件保存路径: $savePath');
final options = Options(
receiveTimeout: Duration.zero,
followRedirects: true,
maxRedirects: 5,
);
await _dio.download(
downloadUrl,
savePath,
options: options,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = received / total;
onProgress(progress);
onStatusUpdate('正在下载固件包... ${(progress * 100).toStringAsFixed(1)}%');
}
},
);
// 验证文件是否下载成功
final file = File(savePath);
if (!await file.exists()) {
throw FileSystemException('文件下载失败,请检查写入权限');
}
onStatusUpdate('下载完成,准备升级...');
return savePath;
}
/// 检查是否存在同名文件
/// [targetDir] 目标目录
/// [fileName] 文件名
/// 返回true表示文件已存在
Future<bool> _checkExistingFile(Directory targetDir, String fileName) async {
try {
final files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
final name = basename(entity.path);
if (name == fileName) {
return true;
}
}
}
return false;
} catch (e) {
Logger().d('检查本地文件失败: $e');
return false;
}
}
/// 清理旧文件
/// [targetDir] 目标目录
/// 抛出异常如果清理失败
Future<void> _cleanOldFiles(Directory targetDir) async {
try {
final files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
await entity.delete();
}
}
} catch (e) {
throw Exception('删除旧文件失败: $e');
}
}
}