Skip to content
Merged

Dev #412

Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/jmcomic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# 被依赖方 <--- 使用方
# config <--- entity <--- toolkit <--- client <--- option <--- downloader

__version__ = '2.5.34'
__version__ = '2.5.35'

from .api import *
from .jm_plugin import *
Expand Down
5 changes: 4 additions & 1 deletion src/jmcomic/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def download_album(jm_album_id,
option=None,
downloader=None,
callback=None,
check_exception=True,
) -> Union[__DOWNLOAD_API_RET, Set[__DOWNLOAD_API_RET]]:
"""
下载一个本子(album),包含其所有的章节(photo)
Expand All @@ -58,6 +59,7 @@ def download_album(jm_album_id,
:param option: 下载选项
:param downloader: 下载器类
:param callback: 返回值回调函数,可以拿到 album 和 downloader
:param check_exception: 是否检查异常, 如果为True,会检查downloader是否有下载异常,并上抛PartialDownloadFailedException
:return: 对于的本子实体类,下载器(如果是上述的批量情况,返回值为download_batch的返回值)
"""

Expand All @@ -69,7 +71,8 @@ def download_album(jm_album_id,

if callback is not None:
callback(album, dler)

if check_exception:
dler.raise_if_has_exception()
return album, dler


Expand Down
2 changes: 1 addition & 1 deletion src/jmcomic/jm_client_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ def check_special_text(cls, resp):

cls.raise_request_error(
resp,
f'{reason}'
f'{reason}({content})'
+ (f': {url}' if url is not None else '')
)

Expand Down
118 changes: 72 additions & 46 deletions src/jmcomic/jm_downloader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
from .jm_option import *
Comment thread
hect0x7 marked this conversation as resolved.


def catch_exception(func):
def wrapper(self, *args, **kwargs):
self: JmDownloader
try:
return func(self, *args, **kwargs)
except Exception as e:
detail: JmBaseEntity = args[0]
if detail.is_image():
detail: JmImageDetail
jm_log('image.failed', f'图片下载失败: [{detail.download_url}], 异常: [{e}]')
self.download_failed_image.append((detail, e))

elif detail.is_photo():
detail: JmPhotoDetail
jm_log('photo.failed', f'章节下载失败: [{detail.id}], 异常: [{e}]')
self.download_failed_photo.append((detail, e))

raise e

wrapper.__name__ = func.__name__
return wrapper


# noinspection PyMethodMayBeStatic
class DownloadCallback:

Expand Down Expand Up @@ -50,48 +73,50 @@ class JmDownloader(DownloadCallback):

def __init__(self, option: JmOption) -> None:
self.option = option
self.client = option.build_jm_client()
# 下载成功的记录dict
self.download_success_dict: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
# 下载失败的记录list
self.download_failed_list: List[Tuple[JmImageDetail, BaseException]] = []
self.download_failed_image: List[Tuple[JmImageDetail, BaseException]] = []
self.download_failed_photo: List[Tuple[JmPhotoDetail, BaseException]] = []

def download_album(self, album_id):
client = self.client_for_album(album_id)
album = client.get_album_detail(album_id)
self.download_by_album_detail(album, client)
album = self.client.get_album_detail(album_id)
self.download_by_album_detail(album)
return album

def download_by_album_detail(self, album: JmAlbumDetail, client: JmcomicClient):
def download_by_album_detail(self, album: JmAlbumDetail):
self.before_album(album)
if album.skip:
return
self.execute_by_condition(
self.execute_on_condition(
iter_objs=album,
apply=lambda photo: self.download_by_photo_detail(photo, client),
apply=self.download_by_photo_detail,
count_batch=self.option.decide_photo_batch_count(album)
)
self.after_album(album)

def download_photo(self, photo_id):
client = self.client_for_photo(photo_id)
photo = client.get_photo_detail(photo_id)
self.download_by_photo_detail(photo, client)
photo = self.client.get_photo_detail(photo_id)
self.download_by_photo_detail(photo)
return photo

def download_by_photo_detail(self, photo: JmPhotoDetail, client: JmcomicClient):
client.check_photo(photo)
@catch_exception
def download_by_photo_detail(self, photo: JmPhotoDetail):
self.client.check_photo(photo)

self.before_photo(photo)
if photo.skip:
return
self.execute_by_condition(
self.execute_on_condition(
iter_objs=photo,
apply=lambda image: self.download_by_image_detail(image, client),
apply=self.download_by_image_detail,
count_batch=self.option.decide_image_batch_count(photo)
)
self.after_photo(photo)

def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
@catch_exception
def download_by_image_detail(self, image: JmImageDetail):
Comment thread
hect0x7 marked this conversation as resolved.
img_save_path = self.option.decide_image_filepath(image)

image.save_path = img_save_path
Expand All @@ -110,22 +135,15 @@ def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
if use_cache is True and image.exists:
return

try:
client.download_by_image_detail(
image,
img_save_path,
decode_image=decode_image,
)
except BaseException as e:
jm_log('image.failed', f'图片下载失败: [{image.download_url}], 异常: {e}')
# 保存失败记录
self.download_failed_list.append((image, e))
raise
self.client.download_by_image_detail(
image,
img_save_path,
decode_image=decode_image,
)

self.after_image(image, img_save_path)

# noinspection PyMethodMayBeStatic
def execute_by_condition(self,
def execute_on_condition(self,
iter_objs: DetailEntity,
apply: Callable,
count_batch: int,
Expand Down Expand Up @@ -166,20 +184,6 @@ def do_filter(self, detail: DetailEntity):
"""
return detail

# noinspection PyUnusedLocal
def client_for_album(self, jm_album_id) -> JmcomicClient:
"""
默认情况下,所有的JmDownloader共用一个JmcomicClient
"""
return self.option.build_jm_client()

# noinspection PyUnusedLocal
def client_for_photo(self, jm_photo_id) -> JmcomicClient:
"""
默认情况下,所有的JmDownloader共用一个JmcomicClient
"""
return self.option.build_jm_client()

@property
def all_success(self) -> bool:
"""
Expand All @@ -189,7 +193,7 @@ def all_success(self) -> bool:

注意!如果使用了filter机制,例如通过filter只下载3张图片,那么all_success也会为False
"""
if len(self.download_failed_list) != 0:
if not self.has_no_download_failed_exception:
return False

for album, photo_dict in self.download_success_dict.items():
Expand All @@ -202,6 +206,10 @@ def all_success(self) -> bool:

return True

@property
def has_no_download_failed_exception(self):
return len(self.download_failed_image) == 0 and len(self.download_failed_photo) == 0

# 下面是回调方法

def before_album(self, album: JmAlbumDetail):
Expand Down Expand Up @@ -259,6 +267,23 @@ def after_image(self, image: JmImageDetail, img_save_path):
downloader=self,
)

def raise_if_has_exception(self):
if self.has_no_download_failed_exception:
return
msg_ls = ['部分下载失败', '', '']

if len(self.download_failed_photo) != 0:
msg_ls[1] = f'共{len(self.download_failed_photo)}个章节下载失败: {self.download_failed_photo}'

if len(self.download_failed_image) != 0:
msg_ls[2] = f'共{len(self.download_failed_image)}个图片下载失败: {self.download_failed_image}'

ExceptionTool.raises(
'\n'.join(msg_ls),
{'downloader': self},
PartialDownloadFailedException,
)

# 下面是对with语法的支持

def __enter__(self):
Expand All @@ -283,7 +308,7 @@ class DoNotDownloadImage(JmDownloader):
不会下载任何图片的Downloader,用作测试
"""

def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
def download_by_image_detail(self, image: JmImageDetail):
# ensure make dir
self.option.decide_image_filepath(image)

Expand All @@ -297,12 +322,13 @@ class JustDownloadSpecificCountImage(JmDownloader):
count_lock = Lock()
count = 0

def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
@catch_exception
def download_by_image_detail(self, image: JmImageDetail):
# ensure make dir
self.option.decide_image_filepath(image)

if self.try_countdown():
return super().download_by_image_detail(image, client)
return super().download_by_image_detail(image)

def try_countdown(self):
if self.count < 0:
Expand Down
24 changes: 14 additions & 10 deletions src/jmcomic/jm_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ def is_album(cls):
def is_page(cls):
return False

@classmethod
def __alias__(cls):
# "JmAlbumDetail" -> "album" (本子)
# "JmPhotoDetail" -> "photo" (章节)
cls_name = cls.__name__
return cls_name[cls_name.index("m") + 1: cls_name.rfind("Detail")].lower()


class IndexedEntity:
def getindex(self, index: int):
Expand Down Expand Up @@ -125,17 +132,9 @@ def idoname(self):
return f'[{self.id}] {self.oname}'

def __str__(self):
return f'{self.__class__.__name__}' \
'{' \
f'{self.id}: {self.title}' \
'}'
return f'''{self.__class__.__name__}({self.__alias__()}-{self.id}: "{self.title}")'''

@classmethod
def __alias__(cls):
# "JmAlbumDetail" -> "album" (本子)
# "JmPhotoDetail" -> "photo" (章节)
cls_name = cls.__name__
return cls_name[cls_name.index("m") + 1: cls_name.rfind("Detail")].lower()
__repr__ = __str__

@classmethod
def get_dirname(cls, detail: 'DetailEntity', ref: str) -> str:
Expand Down Expand Up @@ -258,6 +257,11 @@ def tag(self) -> str:
def is_image(cls):
return True

def __str__(self):
return f'''{self.__class__.__name__}({self.__alias__()}-[{self.download_url}])'''

__repr__ = __str__


class JmPhotoDetail(DetailEntity, Downloadable):

Expand Down
7 changes: 5 additions & 2 deletions src/jmcomic/jm_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def from_context(self, key):
def __str__(self):
return self.msg


class ResponseUnexpectedException(JmcomicException):
description = '响应不符合预期异常'

Expand Down Expand Up @@ -44,7 +45,6 @@ def pattern(self):

class JsonResolveFailException(ResponseUnexpectedException):
description = 'Json解析异常'
pass


class MissingAlbumPhotoException(ResponseUnexpectedException):
Expand All @@ -57,7 +57,10 @@ def error_jmid(self) -> str:

class RequestRetryAllFailException(JmcomicException):
description = '请求重试全部失败异常'
pass


class PartialDownloadFailedException(JmcomicException):
description = '部分章节或图片下载失败异常'


class ExceptionTool:
Expand Down