Skip to content

Commit

Permalink
增加重新下载画廊的功能
Browse files Browse the repository at this point in the history
修复下载原图时,图片token过期导致下载失败的bug
jiangtian616 committed Aug 14, 2022

Verified

This commit was signed with the committer’s verified signature.
rouault Even Rouault
1 parent 17301b4 commit 4de57ee
Showing 10 changed files with 130 additions and 50 deletions.
1 change: 1 addition & 0 deletions lib/src/l18n/en_US.dart
Original file line number Diff line number Diff line change
@@ -281,6 +281,7 @@ class en_US {
'logList': 'Log List',

/// download page
'reDownload': 'Re-download',
'delete': 'Delete',
'unlocking': 'unlocking',
'waitingForDownloadPageUrl': 'Waiting For Download',
1 change: 1 addition & 0 deletions lib/src/l18n/zh_CN.dart
Original file line number Diff line number Diff line change
@@ -282,6 +282,7 @@ class zh_CN {

/// download page
'delete': '删除',
'reDownload': '重新下载',
'unlocking': '解锁归档中',
'waitingForDownloadPageUrl': '等待下载中',
'waitingForDownloadUrl': '等待下载中',
1 change: 1 addition & 0 deletions lib/src/l18n/zh_TW.dart
Original file line number Diff line number Diff line change
@@ -282,6 +282,7 @@ class zh_TW {

/// download page
'delete': '刪除',
'reDownload': '重新下載',
'unlocking': '解鎖歸檔中',
'waitingForDownloadPageUrl': '等待下載中',
'waitingForDownloadUrl': '等待下載中',
5 changes: 3 additions & 2 deletions lib/src/network/eh_cookie_manager.dart
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ class EHCookieManager extends CookieManager {
json.decode(str);
}
} on Exception catch (e) {
Log.warning('cookieJar init failed, use default setting', false);
Log.warning('cookieJar init failed, use default setting');
Set<String> defaultHostSet = NetworkSetting.currentHost2IP.entries.fold<Set<String>>(
<String>{},
(previousValue, entry) => previousValue..addAll([entry.key, entry.value]),
@@ -54,7 +54,8 @@ class EHCookieManager extends CookieManager {

Get.put<EHCookieManager>(EHCookieManager(_cookieJar));

Log.verbose('init EHCookieManager success', false);
List<Cookie> cookies = await _cookieJar.loadForRequest(Uri.parse(EHConsts.EHIndex));
Log.verbose('init EHCookieManager success, cookies length:${cookies.length}', false);
}

@override
43 changes: 28 additions & 15 deletions lib/src/pages/download/gallery_download_body.dart
Original file line number Diff line number Diff line change
@@ -338,26 +338,18 @@ class _GalleryDownloadBodyState extends State<GalleryDownloadBody> {
);
}

void _handleRemoveItem(BuildContext context, int index) {
downloadService.deleteGallery(downloadService.gallerys[index]);

_listKey.currentState?.removeItem(
index,
(context, Animation<double> animation) => FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
child: _removeItemBuilder(),
),
),
);
}

void _showDeleteBottomSheet(GalleryDownloadedData gallery, int index, BuildContext context) {
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) => CupertinoActionSheet(
actions: <CupertinoActionSheetAction>[
CupertinoActionSheetAction(
child: Text('reDownload'.tr, style: TextStyle(color: Colors.red.shade400)),
onPressed: () {
_handleReDownloadItem(context, index);
backRoute();
},
),
CupertinoActionSheetAction(
child: Text('delete'.tr, style: TextStyle(color: Colors.red.shade400)),
onPressed: () {
@@ -374,6 +366,27 @@ class _GalleryDownloadBodyState extends State<GalleryDownloadBody> {
);
}

void _handleRemoveItem(BuildContext context, int index) {
downloadService.deleteGallery(downloadService.gallerys[index]);

_listKey.currentState?.removeItem(
index,
(context, Animation<double> animation) => FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
child: _removeItemBuilder(),
),
),
);
}

/// delete and then re-download
Future<void> _handleReDownloadItem(BuildContext context, int index) async {
await downloadService.reDownloadGallery(downloadService.gallerys[index]);
setState(() {});
}

void _goToReadPage(GalleryDownloadedData gallery) {
int readIndexRecord = storageService.read('readIndexRecord::${gallery.gid}') ?? 0;

2 changes: 1 addition & 1 deletion lib/src/pages/read/read_page.dart
Original file line number Diff line number Diff line change
@@ -293,7 +293,7 @@ class _ReadPageState extends State<ReadPage> {

/// failed for online mode
Widget _failedWidgetBuilder(BuildContext context, int index, ExtendedImageState state) {
Log.warning(state.lastException, false);
Log.warning(state.lastException);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
8 changes: 8 additions & 0 deletions lib/src/pages/setting/download/setting_download_page.dart
Original file line number Diff line number Diff line change
@@ -218,6 +218,14 @@ class _SettingDownloadPageState extends State<SettingDownloadPage> {
child: Text('30s'),
value: 30,
),
DropdownMenuItem(
child: Text('60s'),
value: 60,
),
DropdownMenuItem(
child: Text('180s'),
value: 180,
),
],
),
),
101 changes: 72 additions & 29 deletions lib/src/service/gallery_download_service.dart
Original file line number Diff line number Diff line change
@@ -3,8 +3,12 @@ import 'dart:core';
import 'dart:io' as io;

import 'package:dio/dio.dart';
import 'package:drift/native.dart';
import 'package:executor/executor.dart';
import 'package:flutter/material.dart';
import 'package:get/get_instance/get_instance.dart';
import 'package:get/get_utils/get_utils.dart';
import 'package:get/state_manager.dart';
import 'package:jhentai/src/database/database.dart';
import 'package:jhentai/src/exception/cancel_exception.dart';
import 'package:jhentai/src/exception/eh_exception.dart';
@@ -16,7 +20,6 @@ import 'package:jhentai/src/setting/site_setting.dart';
import 'package:jhentai/src/utils/speed_computer.dart';
import 'package:jhentai/src/utils/snack_util.dart';
import 'package:retry/retry.dart';
import 'package:get/get.dart';
import 'package:jhentai/src/model/download_progress.dart';
import 'package:jhentai/src/network/eh_request.dart';
import 'package:jhentai/src/setting/path_setting.dart';
@@ -161,8 +164,10 @@ class GalleryDownloadService extends GetxController {
}

/// parse url and then download
_parseGalleryImageUrl(gallery, serialNo).then((_) {
_downloadGalleryImage(gallery, serialNo);
_parseGalleryImageUrl(gallery, serialNo).then((success) {
if (success) {
_downloadGalleryImage(gallery, serialNo);
}
});

/// check if download task has been paused or removed
@@ -260,7 +265,7 @@ class GalleryDownloadService extends GetxController {
Future<void> deleteGallery(GalleryDownloadedData gallery) async {
await pauseDownloadGallery(gallery);
await _clearGalleryDownloadInfoInDatabase(gallery.gid);
await _clearDownloadedImageInDisk(gallery);
_clearDownloadedImageInDisk(gallery);
_clearGalleryDownloadInfoInMemory(gallery);

update(['$galleryDownloadProgressId::${gallery.gid}']);
@@ -285,6 +290,18 @@ class GalleryDownloadService extends GetxController {
});
}

Future<void> reDownloadGallery(GalleryDownloadedData gallery) async {
Log.info('Re-download gallery: ${gallery.gid}');

await pauseDownloadGallery(gallery);
await _clearGalleryDownloadInfoInDatabase(gallery.gid);
_clearDownloadedImageInDisk(gallery);
_clearGalleryDownloadInfoInMemory(gallery, updateId: false);

downloadGallery(gallery.copyWith(downloadStatusIndex: DownloadStatus.downloading.index, insertTime: null));
update(['$galleryDownloadProgressId::${gallery.gid}']);
}

/// use meta in each gallery folder to restore download status, then sync to database.
/// this is used after re-install app, or share download folder to another user.
Future<int> restore() async {
@@ -405,9 +422,9 @@ class GalleryDownloadService extends GetxController {
};
}

Future<void> _parseGalleryImageUrl(GalleryDownloadedData gallery, int serialNo, {bool isNewImage = true}) async {
Future<bool> _parseGalleryImageUrl(GalleryDownloadedData gallery, int serialNo, {bool isNewImage = true}) async {
if (_taskHasBeenPausedOrRemoved(gallery)) {
return;
return false;
}

GalleryImage image;
@@ -421,19 +438,18 @@ class GalleryDownloadService extends GetxController {
maxAttempts: retryTimes,
);
} on CancelException catch (_) {
return;
return false;
} on DioError catch (e) {
if (e.type == DioErrorType.cancel) {
return;
return false;
}
if (e.error is EHException) {
Log.download('Download Error, reason: ${e.error.msg}');
snack('error'.tr, e.error.msg, longDuration: true, closeBefore: true);
await pauseAllDownloadGallery();
return;
return false;
}
await _parseGalleryImageUrl(gallery, serialNo, isNewImage: isNewImage);
return;
return await _parseGalleryImageUrl(gallery, serialNo, isNewImage: isNewImage);
} finally {
gid2Tasks[gallery.gid]?.remove(task);
}
@@ -442,9 +458,13 @@ class GalleryDownloadService extends GetxController {
image.path = _computeImageDownloadRelativePath(gallery, serialNo);
image.downloadStatus = DownloadStatus.downloading;
if (isNewImage) {
await _saveNewImageInfoInDatabase(image, serialNo, gallery.gid);
if (gallery.downloadOriginalImage) {
return await _saveNewImageInfoInDatabase(image.copyWith(url: null), serialNo, gallery.gid);
}
return await _saveNewImageInfoInDatabase(image, serialNo, gallery.gid);
} else {
_updateImageUrl(image, gallery.gid, serialNo);
return true;
}
}

@@ -480,10 +500,11 @@ class GalleryDownloadService extends GetxController {
}
}

AsyncTask<void> task = _downloadGalleryImageTask(gallery, serialNo, image.url);
AsyncTask<Response> task = _downloadGalleryImageTask(gallery, serialNo, image.url);
gid2Tasks[gallery.gid]!.add(task);
Response response;
try {
await retry(
response = await retry(
() => executor.scheduleTask(serialNo, task),
maxAttempts: retryTimes,
retryIf: (e) =>
@@ -516,13 +537,18 @@ class GalleryDownloadService extends GetxController {
gid2Tasks[gallery.gid]?.remove(task);
}

if (_isInvalidToken(gallery, response)) {
Log.warning('Invalid original image token, url: ${image.url}');
return _reParseImageUrlAndDownload(gallery, serialNo);
}

Log.download('download gallery:${gallery.title} image: $serialNo success');

await _updateImageDownloadStatus(image, gallery.gid, serialNo, DownloadStatus.downloaded);
_updateProgressAfterImageDownloaded(gallery.gid, serialNo);
}

AsyncTask<void> _downloadGalleryImageTask(GalleryDownloadedData gallery, int serialNo, String url) {
AsyncTask<Response> _downloadGalleryImageTask(GalleryDownloadedData gallery, int serialNo, String url) {
return () {
if (_taskHasBeenPausedOrRemoved(gallery)) {
throw CancelException();
@@ -594,6 +620,14 @@ class GalleryDownloadService extends GetxController {
);
}

/// We need a token in url to get the original image download url, expired token will leads to a failed request,
bool _isInvalidToken(GalleryDownloadedData gallery, Response response) {
if (!gallery.downloadOriginalImage) {
return false;
}
return !(response.isRedirect ?? true) && (response.headers[Headers.contentTypeHeader]?.contains("text/html; charset=UTF-8") ?? false);
}

Future<void> _tryCopyImageInfo(GalleryDownloadedData newGallery, String oldVersionGalleryUrl, int newImageSerialNo) async {
GalleryDownloadedData? oldGallery = gallerys.firstWhereOrNull((e) => e.galleryUrl == oldVersionGalleryUrl);
if (oldGallery == null) {
@@ -643,7 +677,7 @@ class GalleryDownloadService extends GetxController {
update([downloadGallerysId, '$galleryDownloadProgressId::${gallery.gid}']);
}

void _clearGalleryDownloadInfoInMemory(GalleryDownloadedData gallery) {
void _clearGalleryDownloadInfoInMemory(GalleryDownloadedData gallery, {bool updateId = true}) {
gallerys.remove(gallery);
gid2ThumbnailsCountPerPage.remove(gallery.gid);
gid2Tasks.remove(gallery.gid);
@@ -652,10 +686,12 @@ class GalleryDownloadService extends GetxController {
gid2ImageHrefs.remove(gallery.gid);
gid2SpeedComputer[gallery.gid]!.dispose();
gid2SpeedComputer.remove(gallery.gid);
update([downloadGallerysId]);
if (updateId) {
update([downloadGallerysId]);
}
}

Future<void> _clearDownloadedImageInDisk(GalleryDownloadedData gallery) async {
void _clearDownloadedImageInDisk(GalleryDownloadedData gallery) {
io.Directory directory = io.Directory(_computeGalleryDownloadPath(gallery));
if (!directory.existsSync()) {
return;
@@ -701,17 +737,24 @@ class GalleryDownloadService extends GetxController {
}

/// parse a image's url successfully, need to record its info and with its status beginning at 'downloading'
Future<int> _saveNewImageInfoInDatabase(GalleryImage image, int serialNo, int gid) {
return appDb.insertImage(
image.url,
serialNo,
gid,
image.height,
image.width,
image.path!,
image.imageHash!,
image.downloadStatus.index,
);
Future<bool> _saveNewImageInfoInDatabase(GalleryImage image, int serialNo, int gid) async {
try {
await appDb.insertImage(
image.url,
serialNo,
gid,
image.height,
image.width,
image.path!,
image.imageHash!,
image.downloadStatus.index,
);
return true;
} on SqliteException catch (e) {
Log.error(e);
Log.upload(e, withDownloadLogs: true);
return false;
}
}

Future<void> _updateGalleryDownloadStatus(GalleryDownloadedData gallery) async {
2 changes: 1 addition & 1 deletion lib/src/service/tag_translation_service.dart
Original file line number Diff line number Diff line change
@@ -146,7 +146,7 @@ class TagTranslationService extends GetxService {
});
},
maxAttempts: 5,
onRetry: (error) => Log.warning('download tag translation data failed, retry.', false),
onRetry: (error) => Log.warning('download tag translation data failed, retry.'),
);
} on DioError catch (e) {
Log.error('download tag translation data failed after 3 times', e.message);
16 changes: 14 additions & 2 deletions lib/src/utils/log.dart
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ class Log {
_verboseFileLogger?.i(msg, null, withStack ? null : StackTrace.empty);
}

static void warning(Object? msg, [bool withStack = true]) {
static void warning(Object? msg, [bool withStack = false]) {
_logger?.w(msg, null, withStack ? null : StackTrace.empty);
_verboseFileLogger?.w(msg, null, withStack ? null : StackTrace.empty);
_warningFileLogger?.w(msg, null, withStack ? null : StackTrace.empty);
@@ -93,7 +93,12 @@ class Log {
_downloadFileLogger?.v(msg, null, StackTrace.empty);
}

static Future<void> upload(dynamic throwable, {dynamic stackTrace, Map<String, dynamic>? extraInfos}) async {
static Future<void> upload(
dynamic throwable, {
dynamic stackTrace,
Map<String, dynamic>? extraInfos,
bool withDownloadLogs = false,
}) async {
if (_shouldDismissUpload(throwable)) {
return;
}
@@ -129,6 +134,13 @@ class Log {
scope.addAttachment(SentryAttachment.fromUint8List(verboseAttachment, path.basename(_verboseLogFile.path)));
}
}

if (withDownloadLogs) {
Uint8List downloadAttachment = _downloadLogFile.readAsBytesSync();
if (downloadAttachment.isNotEmpty) {
scope.addAttachment(SentryAttachment.fromUint8List(downloadAttachment, path.basename(_downloadLogFile.path)));
}
}
},
),
);

0 comments on commit 4de57ee

Please sign in to comment.