import 'dart:io'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:extended_image/extended_image.dart'; import 'package:flutter_dmzj/app/dialog_utils.dart'; import 'package:flutter_dmzj/app/log.dart'; import 'package:flutter_dmzj/models/db/novel_download_info.dart'; import 'package:flutter_dmzj/models/db/download_status.dart'; import 'package:flutter_dmzj/requests/novel_request.dart'; import 'package:flutter_dmzj/services/novel_download_service.dart'; import 'package:get/get.dart'; // ignore: depend_on_referenced_packages import 'package:path/path.dart' as p; class NovelDownloader { late Rx info; final Function() onUpdateTask; NovelDownloader(NovelDownloadInfo item, {required this.onUpdateTask}) { info = Rx(item); } final NovelRequest request = NovelRequest(); DownloadStatus get status => info.value.status; CancelToken? cancelToken; Dio dio = Dio(BaseOptions( headers: { 'Referer': "http://www.zaimanhua.com/", }, )); void start() { _startDownload(); } void retry() { _startDownload(); } void pause() { stopTask(); updateStatus(DownloadStatus.pause); } void cancel() async { var result = await DialogUtils.showAlertDialog("确定要取消此任务吗?", title: "取消任务"); if (!result) { return; } cancelToken?.cancel(); cancelToken = null; await _delete(); } void resume() { _startDownload(); } void stopTask() { cancelToken?.cancel(); cancelToken = null; } int retryTime = 0; void _startDownload() async { updateStatus(DownloadStatus.downloading); retryTime = 0; await _downloadContent(); int i = 0; for (var url in info.value.imageUrls) { try { if (status != DownloadStatus.downloading) { break; } retryTime = 0; await _downloadImage(url, i); i++; } catch (e) { break; } } if (status == DownloadStatus.downloading) { updateStatus(DownloadStatus.complete); } } Future _downloadContent() async { try { cancelToken = CancelToken(); var content = await request.novelContent( volumeId: info.value.volumeID, chapterId: info.value.chapterId, cancel: cancelToken, cache: false, ); var fileName = await _saveContent(content); var subStr = content.substring(0, content.length < 200 ? content.length : 200); //检查是否是插画 if (subStr.contains(RegExp(''))) { List imgs = []; for (var item in RegExp(r'') .allMatches(content)) { var src = item.group(1); if (src != null && src.isNotEmpty) { imgs.add(src); } } info.update((val) { val!.fileName = fileName; val.imageUrls = imgs; val.isImage = true; }); } else { info.update((val) { val!.fileName = fileName; val.isImage = false; }); } await _saveInfo(); } catch (e) { Log.logPrint(e); if (e is DioException) { if (e.type == DioExceptionType.cancel) rethrow; if (status == DownloadStatus.waitNetwork || status == DownloadStatus.pauseCellular) rethrow; if (retryTime < 3) { retryTime++; await Future.delayed(const Duration(seconds: 1)); return await _downloadContent(); } } updateStatus(DownloadStatus.error); rethrow; } } Future _downloadImage(String url, int index) async { try { //检查本地是否有缓存,有缓存直接复制本地的 Uint8List bytes; var localFile = await getCachedImageFile(url); if (localFile != null) { bytes = await localFile.readAsBytes(); } else { cancelToken = CancelToken(); var result = await dio.get( url, options: Options( responseType: ResponseType.bytes, ), cancelToken: cancelToken, ); bytes = result.data; } var baseName = Uri.parse(url).path; var fileName = await _saveImage(bytes, index, p.extension(baseName)); info.update((val) { val!.imageFiles.add(fileName); }); await _saveInfo(); } catch (e) { Log.logPrint(e); if (e is DioException) { if (e.type == DioExceptionType.cancel) rethrow; if (status == DownloadStatus.waitNetwork || status == DownloadStatus.pauseCellular) rethrow; if (retryTime < 3) { retryTime++; await Future.delayed(const Duration(seconds: 1)); return await _downloadImage(url, index); } } updateStatus(DownloadStatus.error); rethrow; } } Future _saveContent(String content) async { var dir = info.value.savePath; var fileName = "${info.value.taskId}.txt"; var file = File(p.join(dir, fileName)); if (!await file.exists()) { file = await file.create(recursive: true); } await file.writeAsString(content); return fileName; } Future _saveImage( Uint8List bytes, int index, String extension) async { var dir = info.value.savePath; var fileName = "${(index + 1).toString().padLeft(3, "0")}$extension"; var file = File(p.join(dir, fileName)); if (!await file.exists()) { file = await file.create(recursive: true); } await file.writeAsBytes(bytes); return fileName; } void updateStatus(DownloadStatus e, {bool updateTask = true}) async { info.update((val) { val!.status = e; }); if (updateTask) { onUpdateTask(); } await _saveInfo(); } /// 保存信息 Future _saveInfo() async { await NovelDownloadService.instance.box.put(info.value.taskId, info.value); } Future _delete() async { try { var dir = Directory(info.value.savePath); await dir.delete(recursive: true); } finally { await NovelDownloadService.instance.box.delete(info.value.taskId); updateStatus(DownloadStatus.cancel); } } }