[2.2.19] - 2023-02-11 #38

Merged
olofvndrhr merged 12 commits from dev into master 2023-02-11 15:50:15 +01:00
23 changed files with 314 additions and 158 deletions

1
.gitignore vendored
View file

@ -13,6 +13,7 @@ mangas.txt
.idea/ .idea/
venv venv
test.sh test.sh
.ruff_cache/
### Python template ### Python template
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View file

@ -1,5 +1,5 @@
python 3.9.13 3.10.5 3.8.13 python 3.9.13 3.10.5 3.8.13
shellcheck 0.9.0
shfmt 3.6.0 shfmt 3.6.0
shellcheck 0.8.0 direnv 2.32.2
just 1.13.0 just 1.13.0
direnv 2.32.1

View file

@ -9,6 +9,20 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Add support for more sites - Add support for more sites
## [2.2.19] - 2023-02-11
### Added
- First version of the chapter cache (very basic functionality)
### Fixed
- Fixed all exception re-raises to include the original stack trace
### Changed
- Simplified chapter download loop
## [2.2.18] - 2023-01-21 ## [2.2.18] - 2023-01-21
### Fixed ### Fixed

View file

@ -96,27 +96,27 @@ Script to download mangas from various sites
Options: Options:
--help Show this message and exit. --help Show this message and exit.
--version Show the version and exit. --version Show the version and exit.
source: [mutually_exclusive, required] source: [mutually_exclusive, required]
-u, --url, --uuid TEXT URL or UUID of the manga -u, --url, --uuid TEXT URL or UUID of the manga
--read FILE Path of file with manga links to download. One per line --read FILE Path of file with manga links to download. One per line
verbosity: [mutually_exclusive] verbosity: [mutually_exclusive]
--loglevel INTEGER Custom log level [default: 20] --loglevel INTEGER Custom log level
--warn Only log warnings and higher --warn Only log warnings and higher
--debug Debug logging. Log EVERYTHING --debug Debug logging. Log EVERYTHING
-c, --chapters TEXT Chapters to download -c, --chapters TEXT Chapters to download
-p, --path PATH Download path [default: downloads] -p, --path PATH Download path [default: downloads]
-l, --language TEXT Manga language [default: en] -l, --language TEXT Manga language [default: en]
--list List all available chapters --list List all available chapters
--format TEXT Archive format to create. An empty string means dont archive the folder [default: cbz] --format [cbz|cbr|zip|pdf|] Archive format to create. An empty string means dont archive the folder [default: cbz]
--name-format TEXT Naming format to use when saving chapters. See docs for more infos [default: {default}]
--name-format-none TEXT String to use when the variable of the custom name format is empty
--forcevol Force naming of volumes. For mangas where chapters reset each volume --forcevol Force naming of volumes. For mangas where chapters reset each volume
--wait FLOAT Time to wait for each picture to download in seconds(float) [default: 0.5] --wait FLOAT Time to wait for each picture to download in seconds(float) [default: 0.5]
--hook-manga-pre TEXT Commands to execute before the manga download starts --hook-manga-pre TEXT Commands to execute before the manga download starts
--hook-manga-post TEXT Commands to execute after the manga download finished --hook-manga-post TEXT Commands to execute after the manga download finished
--hook-chapter-pre TEXT Commands to execute before the chapter download starts --hook-chapter-pre TEXT Commands to execute before the chapter download starts
--hook-chapter-post TEXT Commands to execute after the chapter download finished --hook-chapter-post TEXT Commands to execute after the chapter download finished
--cache-path PATH Where to store the cache-db. If no path is given, cache is disabled
``` ```
## Contribution / Bugs ## Contribution / Bugs

View file

@ -1,8 +1,8 @@
FROM cr.44net.ch/baseimages/debian-s6:11.5-linux-amd64 FROM cr.44net.ch/baseimages/debian-s6:11.6-linux-amd64
# set version label # set version label
ARG BUILD_VERSION ARG BUILD_VERSION
ENV MDLP_VERSION=${BUILD_VERSION} ENV IMAGE_VERSION=${BUILD_VERSION}
LABEL version="${BUILD_VERSION}" LABEL version="${BUILD_VERSION}"
LABEL maintainer="Ivan Schaller" LABEL maintainer="Ivan Schaller"
LABEL description="A CLI manga downloader" LABEL description="A CLI manga downloader"

View file

@ -1,8 +1,8 @@
FROM cr.44net.ch/baseimages/debian-s6:11.5-linux-arm64 FROM cr.44net.ch/baseimages/debian-s6:11.6-linux-arm64
# set version label # set version label
ARG BUILD_VERSION ARG BUILD_VERSION
ENV MDLP_VERSION=${BUILD_VERSION} ENV IMAGE_VERSION=${BUILD_VERSION}
LABEL version="${BUILD_VERSION}" LABEL version="${BUILD_VERSION}"
LABEL maintainer="Ivan Schaller" LABEL maintainer="Ivan Schaller"
LABEL description="A CLI manga downloader" LABEL description="A CLI manga downloader"

View file

@ -159,3 +159,11 @@ link3
`python3 manga-dlp.py --read mangas.txt --list` `python3 manga-dlp.py --read mangas.txt --list`
This will list all available chapters for link1, link2 and link3. This will list all available chapters for link1, link2 and link3.
## Create basic cache
With the `--cache-path <cache file>` option you can let the script create a very basic json cache. Your downloaded
chapters will be
tracked there, and the script doesn't have to check on disk if you already downloaded it.
If the option is unset (default), then no caching will be done.

View file

@ -94,27 +94,27 @@ Script to download mangas from various sites
Options: Options:
--help Show this message and exit. --help Show this message and exit.
--version Show the version and exit. --version Show the version and exit.
source: [mutually_exclusive, required] source: [mutually_exclusive, required]
-u, --url, --uuid TEXT URL or UUID of the manga -u, --url, --uuid TEXT URL or UUID of the manga
--read FILE Path of file with manga links to download. One per line --read FILE Path of file with manga links to download. One per line
verbosity: [mutually_exclusive] verbosity: [mutually_exclusive]
--loglevel INTEGER Custom log level [default: 20] --loglevel INTEGER Custom log level
--warn Only log warnings and higher --warn Only log warnings and higher
--debug Debug logging. Log EVERYTHING --debug Debug logging. Log EVERYTHING
-c, --chapters TEXT Chapters to download -c, --chapters TEXT Chapters to download
-p, --path PATH Download path [default: downloads] -p, --path PATH Download path [default: downloads]
-l, --language TEXT Manga language [default: en] -l, --language TEXT Manga language [default: en]
--list List all available chapters --list List all available chapters
--format TEXT Archive format to create. An empty string means dont archive the folder [default: cbz] --format [cbz|cbr|zip|pdf|] Archive format to create. An empty string means dont archive the folder [default: cbz]
--name-format TEXT Naming format to use when saving chapters. See docs for more infos [default: {default}]
--name-format-none TEXT String to use when the variable of the custom name format is empty
--forcevol Force naming of volumes. For mangas where chapters reset each volume --forcevol Force naming of volumes. For mangas where chapters reset each volume
--wait FLOAT Time to wait for each picture to download in seconds(float) [default: 0.5] --wait FLOAT Time to wait for each picture to download in seconds(float) [default: 0.5]
--hook-manga-pre TEXT Commands to execute before the manga download starts --hook-manga-pre TEXT Commands to execute before the manga download starts
--hook-manga-post TEXT Commands to execute after the manga download finished --hook-manga-post TEXT Commands to execute after the manga download finished
--hook-chapter-pre TEXT Commands to execute before the chapter download starts --hook-chapter-pre TEXT Commands to execute before the chapter download starts
--hook-chapter-post TEXT Commands to execute after the chapter download finished --hook-chapter-post TEXT Commands to execute after the chapter download finished
--cache-path PATH Where to store the cache-db. If no path is given, cache is disabled
``` ```
## Contribution / Bugs ## Contribution / Bugs

View file

@ -1 +1 @@
__version__ = "2.2.18" __version__ = "2.2.19"

View file

@ -45,14 +45,11 @@ class Mangadex:
self.api_additions = f"{self.api_language}&{self.api_content_ratings}" self.api_additions = f"{self.api_language}&{self.api_content_ratings}"
# infos from functions # infos from functions
try: self.manga_uuid = self.get_manga_uuid()
self.manga_uuid = self.get_manga_uuid() self.manga_data = self.get_manga_data()
self.manga_data = self.get_manga_data() self.manga_title = self.get_manga_title()
self.manga_title = self.get_manga_title() self.manga_chapter_data = self.get_chapter_data()
self.manga_chapter_data = self.get_chapter_data() self.chapter_list = self.create_chapter_list()
self.chapter_list = self.create_chapter_list()
except Exception as exc:
raise RuntimeError from exc
# get the uuid for the manga # get the uuid for the manga
def get_manga_uuid(self) -> str: def get_manga_uuid(self) -> str:
@ -65,7 +62,7 @@ class Mangadex:
uuid = uuid_regex.search(self.url_uuid)[0] # type: ignore uuid = uuid_regex.search(self.url_uuid)[0] # type: ignore
except Exception as exc: except Exception as exc:
log.error("No valid UUID found") log.error("No valid UUID found")
raise KeyError("No valid UUID found") from exc raise exc
return uuid return uuid
@ -81,7 +78,7 @@ class Mangadex:
except Exception as exc: except Exception as exc:
if counter >= 3: if counter >= 3:
log.error("Maybe the MangaDex API is down?") log.error("Maybe the MangaDex API is down?")
raise ConnectionError("Maybe the MangaDex API is down?") from exc raise exc
log.error("Mangadex API not reachable. Retrying") log.error("Mangadex API not reachable. Retrying")
sleep(2) sleep(2)
counter += 1 counter += 1
@ -90,7 +87,7 @@ class Mangadex:
# check if manga exists # check if manga exists
if response.json()["result"] != "ok": if response.json()["result"] != "ok":
log.error("Manga not found") log.error("Manga not found")
raise KeyError("Manga not found") raise KeyError
return response.json()["data"] return response.json()["data"]
@ -101,7 +98,7 @@ class Mangadex:
# try to get the title in requested language # try to get the title in requested language
try: try:
title = attributes["title"][self.language] title = attributes["title"][self.language]
except Exception: except KeyError:
log.info("Manga title not found in requested language. Trying alt titles") log.info("Manga title not found in requested language. Trying alt titles")
else: else:
log.debug(f"Language={self.language}, Title='{title}'") log.debug(f"Language={self.language}, Title='{title}'")
@ -115,7 +112,7 @@ class Mangadex:
alt_title = item alt_title = item
break break
title = alt_title[self.language] title = alt_title[self.language]
except Exception: except (KeyError, UnboundLocalError):
log.warning( log.warning(
"Manga title also not found in alt titles. Falling back to english title" "Manga title also not found in alt titles. Falling back to english title"
) )
@ -140,7 +137,7 @@ class Mangadex:
log.error( log.error(
"Error retrieving the chapters list. Did you specify a valid language code?" "Error retrieving the chapters list. Did you specify a valid language code?"
) )
raise KeyError from exc raise exc
else: else:
if total_chapters == 0: if total_chapters == 0:
log.error("No chapters available to download in specified language") log.error("No chapters available to download in specified language")

View file

@ -2,12 +2,13 @@ import re
import shutil import shutil
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Union
from loguru import logger as log from loguru import logger as log
from mangadlp import downloader, utils from mangadlp import downloader, utils
from mangadlp.api.mangadex import Mangadex from mangadlp.api.mangadex import Mangadex
from mangadlp.cache import CacheDB
from mangadlp.hooks import run_hook from mangadlp.hooks import run_hook
@ -22,7 +23,7 @@ class MangaDLP:
list_chapters (bool): List all available chapters and exit list_chapters (bool): List all available chapters and exit
file_format (str): Archive format to create. An empty string means don't archive the folder file_format (str): Archive format to create. An empty string means don't archive the folder
forcevol (bool): Force naming of volumes. Useful for mangas where chapters reset each volume forcevol (bool): Force naming of volumes. Useful for mangas where chapters reset each volume
download_path (str): Download path. Defaults to '<script_dir>/downloads' download_path (str/Path): Download path. Defaults to '<script_dir>/downloads'
download_wait (float): Time to wait for each picture to download in seconds download_wait (float): Time to wait for each picture to download in seconds
""" """
@ -37,29 +38,31 @@ class MangaDLP:
name_format: str = "{default}", name_format: str = "{default}",
name_format_none: str = "", name_format_none: str = "",
forcevol: bool = False, forcevol: bool = False,
download_path: str = "downloads", download_path: Union[str, Path] = "downloads",
download_wait: float = 0.5, download_wait: float = 0.5,
manga_pre_hook_cmd: str = "", manga_pre_hook_cmd: str = "",
manga_post_hook_cmd: str = "", manga_post_hook_cmd: str = "",
chapter_pre_hook_cmd: str = "", chapter_pre_hook_cmd: str = "",
chapter_post_hook_cmd: str = "", chapter_post_hook_cmd: str = "",
cache_path: str = "",
) -> None: ) -> None:
# init parameters # init parameters
self.url_uuid: str = url_uuid self.url_uuid = url_uuid
self.language: str = language self.language = language
self.chapters: str = chapters self.chapters = chapters
self.list_chapters: bool = list_chapters self.list_chapters = list_chapters
self.file_format: str = file_format self.file_format = file_format
self.name_format: str = name_format self.name_format = name_format
self.name_format_none: str = name_format_none self.name_format_none = name_format_none
self.forcevol: bool = forcevol self.forcevol = forcevol
self.download_path: str = download_path self.download_path: Path = Path(download_path)
self.download_wait: float = download_wait self.download_wait = download_wait
self.manga_pre_hook_cmd: str = manga_pre_hook_cmd self.manga_pre_hook_cmd = manga_pre_hook_cmd
self.manga_post_hook_cmd: str = manga_post_hook_cmd self.manga_post_hook_cmd = manga_post_hook_cmd
self.chapter_pre_hook_cmd: str = chapter_pre_hook_cmd self.chapter_pre_hook_cmd = chapter_pre_hook_cmd
self.chapter_post_hook_cmd: str = chapter_post_hook_cmd self.chapter_post_hook_cmd = chapter_post_hook_cmd
self.hook_infos: dict = {} self.hook_infos: dict = {}
self.cache_path = cache_path
# prepare everything # prepare everything
self._prepare() self._prepare()
@ -134,10 +137,6 @@ class MangaDLP:
# once called per manga # once called per manga
def get_manga(self) -> None: def get_manga(self) -> None:
# create empty skipped chapters list
skipped_chapters: list[Any] = []
error_chapters: list[Any] = []
print_divider = "=========================================" print_divider = "========================================="
# show infos # show infos
log.info(f"{print_divider}") log.info(f"{print_divider}")
@ -166,6 +165,12 @@ class MangaDLP:
# create manga folder # create manga folder
self.manga_path.mkdir(parents=True, exist_ok=True) self.manga_path.mkdir(parents=True, exist_ok=True)
# prepare cache if specified
if self.cache_path:
cache = CacheDB(self.cache_path, self.manga_uuid, self.language)
cached_chapters = cache.db_uuid_chapters
log.info(f"Cached chapters: {cached_chapters}")
# create dict with all variables for the hooks # create dict with all variables for the hooks
self.hook_infos.update( self.hook_infos.update(
{ {
@ -178,7 +183,7 @@ class MangaDLP:
"chapters_to_download": chapters_to_download, "chapters_to_download": chapters_to_download,
"file_format": self.file_format, "file_format": self.file_format,
"forcevol": self.forcevol, "forcevol": self.forcevol,
"download_path": self.download_path, "download_path": str(self.download_path),
"manga_path": self.manga_path, "manga_path": self.manga_path,
} }
) )
@ -192,31 +197,46 @@ class MangaDLP:
) )
# get chapters # get chapters
skipped_chapters: list[Any] = []
error_chapters: list[Any] = []
for chapter in chapters_to_download: for chapter in chapters_to_download:
return_infos = self.get_chapter(chapter) if self.cache_path and chapter in cached_chapters:
error_chapters.append(return_infos.get("error")) log.info("Chapter is in cache. Skipping download")
skipped_chapters.append(return_infos.get("skipped")) continue
if self.file_format and return_infos["chapter_path"]:
return_infos = self.archive_chapter(return_infos["chapter_path"])
error_chapters.append(return_infos.get("error"))
skipped_chapters.append(return_infos.get("skipped"))
# check if chapter was skipped
try: try:
return_infos["skipped"] chapter_path = self.get_chapter(chapter)
# chapter was not skipped except FileExistsError:
except KeyError: skipped_chapters.append(chapter)
# done with chapter # update cache
log.info(f"Done with chapter '{chapter}'\n") if self.cache_path:
cache.add_chapter(chapter)
continue
except Exception:
error_chapters.append(chapter)
continue
# start chapter post hook if self.file_format:
run_hook( try:
command=self.chapter_post_hook_cmd, self.archive_chapter(chapter_path)
hook_type="chapter_post", except Exception:
status="successful", error_chapters.append(chapter)
**self.hook_infos, continue
)
# done with chapter
log.info(f"Done with chapter '{chapter}'")
# update cache
if self.cache_path:
cache.add_chapter(chapter)
# start chapter post hook
run_hook(
command=self.chapter_post_hook_cmd,
hook_type="chapter_post",
status="successful",
**self.hook_infos,
)
# done with manga # done with manga
log.info(f"{print_divider}") log.info(f"{print_divider}")
@ -243,7 +263,7 @@ class MangaDLP:
log.info(f"{print_divider}\n") log.info(f"{print_divider}\n")
# once called per chapter # once called per chapter
def get_chapter(self, chapter: str) -> dict: def get_chapter(self, chapter: str) -> Path:
# get chapter infos # get chapter infos
chapter_infos = self.api.get_chapter_infos(chapter) chapter_infos = self.api.get_chapter_infos(chapter)
log.debug(f"Chapter infos: {chapter_infos}") log.debug(f"Chapter infos: {chapter_infos}")
@ -271,15 +291,8 @@ class MangaDLP:
**self.hook_infos, **self.hook_infos,
) )
# add to skipped chapters list # error
return ( raise SystemError
{
"error": f"{chapter_infos['volume']}:{chapter_infos['chapter']}",
"chapter_path": None,
}
if self.forcevol
else {"error": f"{chapter_infos['chapter']}", "chapter_path": None}
)
# get filename for chapter (without suffix) # get filename for chapter (without suffix)
chapter_filename = utils.get_filename( chapter_filename = utils.get_filename(
@ -311,15 +324,8 @@ class MangaDLP:
**self.hook_infos, **self.hook_infos,
) )
# add to skipped chapters list # skipped
return ( raise FileExistsError
{
"skipped": f"{chapter_infos['volume']}:{chapter_infos['chapter']}",
"chapter_path": None,
}
if self.forcevol
else {"skipped": f"{chapter_infos['chapter']}", "chapter_path": None}
)
# create chapter folder (skips it if it already exists) # create chapter folder (skips it if it already exists)
chapter_path.mkdir(parents=True, exist_ok=True) chapter_path.mkdir(parents=True, exist_ok=True)
@ -361,7 +367,7 @@ class MangaDLP:
except KeyboardInterrupt: except KeyboardInterrupt:
log.critical("Stopping") log.critical("Stopping")
sys.exit(1) sys.exit(1)
except Exception: except Exception as exc:
log.error(f"Cant download: '{chapter_filename}'. Skipping") log.error(f"Cant download: '{chapter_filename}'. Skipping")
# run chapter post hook # run chapter post hook
@ -373,24 +379,17 @@ class MangaDLP:
**self.hook_infos, **self.hook_infos,
) )
# add to skipped chapters list # chapter error
return ( raise exc
{
"error": f"{chapter_infos['volume']}:{chapter_infos['chapter']}",
"chapter_path": None,
}
if self.forcevol
else {"error": f"{chapter_infos['chapter']}", "chapter_path": None}
)
else: # Done with chapter
# Done with chapter log.info(f"Successfully downloaded: '{chapter_filename}'")
log.info(f"Successfully downloaded: '{chapter_filename}'")
return {"chapter_path": chapter_path} # ok
return chapter_path
# create an archive of the chapter if needed # create an archive of the chapter if needed
def archive_chapter(self, chapter_path: Path) -> dict: def archive_chapter(self, chapter_path: Path) -> None:
log.info(f"Creating archive '{chapter_path}{self.file_format}'") log.info(f"Creating archive '{chapter_path}{self.file_format}'")
try: try:
# check if image folder is existing # check if image folder is existing
@ -401,14 +400,9 @@ class MangaDLP:
utils.make_pdf(chapter_path) utils.make_pdf(chapter_path)
else: else:
utils.make_archive(chapter_path, self.file_format) utils.make_archive(chapter_path, self.file_format)
except Exception: except Exception as exc:
log.error("Archive error. Skipping chapter") log.error("Archive error. Skipping chapter")
# add to skipped chapters list raise exc
return {
"error": chapter_path,
}
else:
# remove image folder
shutil.rmtree(chapter_path)
return {} # remove image folder
shutil.rmtree(chapter_path)

56
mangadlp/cache.py Normal file
View file

@ -0,0 +1,56 @@
import json
from pathlib import Path
from typing import Union
from loguru import logger as log
class CacheDB:
def __init__(self, db_path: Union[str, Path], uuid: str, lang: str) -> None:
self.db_path = Path(db_path)
self.uuid = uuid
self.lang = lang
self.db_key = f"{uuid}__{lang}"
self._prepare()
self.db_data = self.read_db()
# create db key entry if not found
if not self.db_data.get(self.db_key):
self.db_data[self.db_key] = {}
self.db_uuid_data: dict = self.db_data[self.db_key]
self.db_uuid_chapters: list = self.db_uuid_data.get("chapters") or []
def _prepare(self):
if self.db_path.exists():
return
# create empty cache
try:
self.db_path.touch()
self.db_path.write_text(json.dumps({}), encoding="utf8")
except Exception as exc:
log.error("Can't create db-file")
raise exc
def read_db(self) -> dict:
log.info(f"Reading cache-db: {self.db_path}")
try:
db_txt = self.db_path.read_text(encoding="utf8")
db_dict: dict = json.loads(db_txt)
except Exception as exc:
log.error("Can't load cache-db")
raise exc
return db_dict
def add_chapter(self, chapter: str) -> None:
log.info(f"Adding chapter to cache-db: {chapter}")
self.db_uuid_chapters.append(chapter)
# dedup entries
updated_chapters = list({*self.db_uuid_chapters})
try:
self.db_data[self.db_key]["chapters"] = sorted(updated_chapters)
self.db_path.write_text(json.dumps(self.db_data, indent=4), encoding="utf8")
except Exception as exc:
log.error("Can't write cache-db")
raise exc

View file

@ -99,7 +99,7 @@ def readin_list(_ctx, _param, value) -> list:
"-p", "-p",
"--path", "--path",
"path", "path",
type=click.Path(exists=False), type=click.Path(exists=False, writable=True, path_type=Path),
default="downloads", default="downloads",
required=False, required=False,
show_default=True, show_default=True,
@ -207,6 +207,15 @@ def readin_list(_ctx, _param, value) -> list:
show_default=True, show_default=True,
help="Commands to execute after the chapter download finished", help="Commands to execute after the chapter download finished",
) )
@click.option(
"--cache-path",
"cache_path",
type=click.Path(exists=False, writable=True, path_type=str),
default=None,
required=False,
show_default=True,
help="Where to store the cache-db. If no path is given, cache is disabled",
)
@click.pass_context @click.pass_context
def main( def main(
ctx: click.Context, ctx: click.Context,
@ -214,7 +223,7 @@ def main(
read_mangas: list, read_mangas: list,
verbosity: int, verbosity: int,
chapters: str, chapters: str,
path: str, path: Path,
lang: str, lang: str,
list_chapters: bool, list_chapters: bool,
chapter_format: str, chapter_format: str,
@ -226,8 +235,8 @@ def main(
hook_manga_post: str, hook_manga_post: str,
hook_chapter_pre: str, hook_chapter_pre: str,
hook_chapter_post: str, hook_chapter_post: str,
cache_path: str,
): # pylint: disable=too-many-locals ): # pylint: disable=too-many-locals
""" """
Script to download mangas from various sites Script to download mangas from various sites
@ -262,6 +271,7 @@ def main(
manga_post_hook_cmd=hook_manga_post, manga_post_hook_cmd=hook_manga_post,
chapter_pre_hook_cmd=hook_chapter_pre, chapter_pre_hook_cmd=hook_chapter_pre,
chapter_post_hook_cmd=hook_chapter_post, chapter_post_hook_cmd=hook_chapter_post,
cache_path=cache_path,
) )
mdlp.get_manga() mdlp.get_manga()

View file

@ -41,7 +41,7 @@ def download_chapter(
except Exception as exc: except Exception as exc:
if counter >= 3: if counter >= 3:
log.error("Maybe the MangaDex Servers are down?") log.error("Maybe the MangaDex Servers are down?")
raise ConnectionError from exc raise exc
sleep(download_wait) sleep(download_wait)
counter += 1 counter += 1
else: else:
@ -54,7 +54,7 @@ def download_chapter(
shutil.copyfileobj(r.raw, file) shutil.copyfileobj(r.raw, file)
except Exception as exc: except Exception as exc:
log.error("Can't write file") log.error("Can't write file")
raise IOError from exc raise exc
image_num += 1 image_num += 1
sleep(download_wait) sleep(download_wait)

View file

@ -32,7 +32,6 @@ class InterceptHandler(logging.Handler):
# init logger with format and log level # init logger with format and log level
def prepare_logger(loglevel: int = 20) -> None: def prepare_logger(loglevel: int = 20) -> None:
config: dict = { config: dict = {
"handlers": [ "handlers": [
{ {

View file

@ -16,9 +16,10 @@ def make_archive(chapter_path: Path, file_format: str) -> None:
for file in chapter_path.iterdir(): for file in chapter_path.iterdir():
zipfile.write(file, file.name) zipfile.write(file, file.name)
# rename zip to file format requested # rename zip to file format requested
zip_path.rename(zip_path.with_suffix(file_format)) zip_path.replace(zip_path.with_suffix(file_format))
except Exception as exc: except Exception as exc:
raise IOError from exc log.error(f"Can't create '{file_format}' archive")
raise exc
def make_pdf(chapter_path: Path) -> None: def make_pdf(chapter_path: Path) -> None:
@ -26,7 +27,7 @@ def make_pdf(chapter_path: Path) -> None:
import img2pdf # pylint: disable=import-outside-toplevel import img2pdf # pylint: disable=import-outside-toplevel
except Exception as exc: except Exception as exc:
log.error("Cant import img2pdf. Please install it first") log.error("Cant import img2pdf. Please install it first")
raise ImportError from exc raise exc
pdf_path: Path = Path(f"{chapter_path}.pdf") pdf_path: Path = Path(f"{chapter_path}.pdf")
images: list[str] = [] images: list[str] = []
@ -36,7 +37,7 @@ def make_pdf(chapter_path: Path) -> None:
pdf_path.write_bytes(img2pdf.convert(images)) pdf_path.write_bytes(img2pdf.convert(images))
except Exception as exc: except Exception as exc:
log.error("Can't create '.pdf' archive") log.error("Can't create '.pdf' archive")
raise IOError from exc raise exc
# create a list of chapters # create a list of chapters

View file

@ -94,6 +94,7 @@ show_error_context = true
show_column_numbers = true show_column_numbers = true
show_error_codes = true show_error_codes = true
pretty = true pretty = true
no_implicit_optional = false
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = [ pythonpath = [

View file

@ -1,12 +1,12 @@
import pytest import pytest
import mangadlp.app as app
from mangadlp.api.mangadex import Mangadex from mangadlp.api.mangadex import Mangadex
from mangadlp.app import MangaDLP
def test_check_api_mangadex(): def test_check_api_mangadex():
url = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" url = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu"
test = app.MangaDLP(url_uuid=url, list_chapters=True, download_wait=2) test = MangaDLP(url_uuid=url, list_chapters=True, download_wait=2)
assert test.api_used == Mangadex assert test.api_used == Mangadex
@ -14,6 +14,6 @@ def test_check_api_mangadex():
def test_check_api_none(): def test_check_api_none():
url = "https://abc.defghjk/title/abc/def" url = "https://abc.defghjk/title/abc/def"
with pytest.raises(SystemExit) as e: with pytest.raises(SystemExit) as e:
app.MangaDLP(url_uuid=url, list_chapters=True, download_wait=2) MangaDLP(url_uuid=url, list_chapters=True, download_wait=2)
assert e.type == SystemExit assert e.type == SystemExit
assert e.value.code == 1 assert e.value.code == 1

View file

@ -27,9 +27,9 @@ def test_make_archive_false():
archive_path = Path("tests/test_dir2.cbz") archive_path = Path("tests/test_dir2.cbz")
img_path = Path("tests/test_dir2") img_path = Path("tests/test_dir2")
file_format = "cbz" file_format = "cbz"
with pytest.raises(IOError) as e: with pytest.raises(Exception) as e:
utils.make_archive(img_path, file_format) utils.make_archive(img_path, file_format)
assert e.type == IOError assert e.type == FileNotFoundError
assert not archive_path.exists() assert not archive_path.exists()
# cleanup # cleanup
Path("tests/test_dir2.zip").unlink() Path("tests/test_dir2.zip").unlink()

View file

@ -42,9 +42,9 @@ def test_downloader_fail(monkeypatch):
chapter_path = Path("tests/test_folder1") chapter_path = Path("tests/test_folder1")
chapter_path.mkdir(parents=True, exist_ok=True) chapter_path.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(requests, "get", fail_url) monkeypatch.setattr(requests, "get", fail_url)
with pytest.raises(ConnectionError) as e: with pytest.raises(TypeError) as e:
downloader.download_chapter(images, str(chapter_path), 2) downloader.download_chapter(images, str(chapter_path), 2)
assert e.type == ConnectionError assert e.type == TypeError
# cleanup # cleanup
shutil.rmtree(chapter_path, ignore_errors=True) shutil.rmtree(chapter_path, ignore_errors=True)

77
tests/test_06_cache.py Normal file
View file

@ -0,0 +1,77 @@
import json
from pathlib import Path
from mangadlp.cache import CacheDB
def test_cache_creation():
cache_file = Path("cache.json")
cache = CacheDB(cache_file, "abc", "en")
assert cache_file.exists() and cache_file.read_text(encoding="utf8") == "{}"
cache_file.unlink()
def test_cache_insert():
cache_file = Path("cache.json")
cache = CacheDB(cache_file, "abc", "en")
cache.add_chapter("1")
cache.add_chapter("2")
cache_data = json.loads(cache_file.read_text(encoding="utf8"))
assert cache_data["abc__en"]["chapters"] == ["1", "2"]
cache_file.unlink()
def test_cache_update():
cache_file = Path("cache.json")
cache = CacheDB(cache_file, "abc", "en")
cache.add_chapter("1")
cache.add_chapter("2")
cache_data = json.loads(cache_file.read_text(encoding="utf8"))
assert cache_data["abc__en"]["chapters"] == ["1", "2"]
cache.add_chapter("3")
cache_data = json.loads(cache_file.read_text(encoding="utf8"))
assert cache_data["abc__en"]["chapters"] == ["1", "2", "3"]
cache_file.unlink()
def test_cache_multiple():
cache_file = Path("cache.json")
cache1 = CacheDB(cache_file, "abc", "en")
cache1.add_chapter("1")
cache1.add_chapter("2")
cache2 = CacheDB(cache_file, "def", "en")
cache2.add_chapter("8")
cache2.add_chapter("9")
cache_data = json.loads(cache_file.read_text(encoding="utf8"))
assert cache_data["abc__en"]["chapters"] == ["1", "2"]
assert cache_data["def__en"]["chapters"] == ["8", "9"]
cache_file.unlink()
def test_cache_lang():
cache_file = Path("cache.json")
cache1 = CacheDB(cache_file, "abc", "en")
cache1.add_chapter("1")
cache1.add_chapter("2")
cache2 = CacheDB(cache_file, "abc", "de")
cache2.add_chapter("8")
cache2.add_chapter("9")
cache_data = json.loads(cache_file.read_text(encoding="utf8"))
assert cache_data["abc__en"]["chapters"] == ["1", "2"]
assert cache_data["abc__de"]["chapters"] == ["8", "9"]
cache_file.unlink()

View file

@ -27,9 +27,9 @@ def test_uuid_link_false():
language = "en" language = "en"
forcevol = False forcevol = False
with pytest.raises(RuntimeError) as e: with pytest.raises(Exception) as e:
Mangadex(url_uuid, language, forcevol) Mangadex(url_uuid, language, forcevol)
assert e.type == RuntimeError assert e.type == TypeError
def test_title(): def test_title():
@ -83,9 +83,9 @@ def test_non_existing_manga():
language = "en" language = "en"
forcevol = False forcevol = False
with pytest.raises(RuntimeError) as e: with pytest.raises(Exception) as e:
Mangadex(url_uuid, language, forcevol) Mangadex(url_uuid, language, forcevol)
assert e.type == RuntimeError assert e.type == KeyError
def test_api_failure(monkeypatch): def test_api_failure(monkeypatch):
@ -97,9 +97,9 @@ def test_api_failure(monkeypatch):
language = "en" language = "en"
forcevol = False forcevol = False
with pytest.raises(RuntimeError) as e: with pytest.raises(Exception) as e:
Mangadex(url_uuid, language, forcevol) Mangadex(url_uuid, language, forcevol)
assert e.type == RuntimeError assert e.type == TypeError
def test_chapter_lang_en(): def test_chapter_lang_en():
@ -116,10 +116,9 @@ def test_empty_chapter_lang():
language = "ch" language = "ch"
forcevol = False forcevol = False
with pytest.raises(RuntimeError) as e: with pytest.raises(Exception) as e:
Mangadex(url_uuid, language, forcevol) Mangadex(url_uuid, language, forcevol)
Mangadex(url_uuid, language, forcevol).check_chapter_lang() assert e.type == KeyError
assert e.type == KeyError or e.type == RuntimeError
def test_not_existing_lang(): def test_not_existing_lang():
@ -127,9 +126,9 @@ def test_not_existing_lang():
language = "zz" language = "zz"
forcevol = False forcevol = False
with pytest.raises(RuntimeError) as e: with pytest.raises(Exception) as e:
Mangadex(url_uuid, language, forcevol) Mangadex(url_uuid, language, forcevol)
assert e.type == RuntimeError assert e.type == KeyError
def test_create_chapter_list(): def test_create_chapter_list():

View file

@ -78,11 +78,10 @@ def test_full_with_input_cbz_info(wait_20s):
shutil.rmtree(manga_path, ignore_errors=True) shutil.rmtree(manga_path, ignore_errors=True)
@pytest.mark.skipif(
platform.machine() != "x86_64", reason="pdf only supported on amd64"
)
def test_full_with_input_pdf(wait_20s): def test_full_with_input_pdf(wait_20s):
# check if its arm64, if yes skip this step
if platform.machine() != "x86_64":
return True
url_uuid = "https://mangadex.org/title/0aea9f43-d4a9-4bf7-bebc-550a512f9b95/shikimori-s-not-just-a-cutie" url_uuid = "https://mangadex.org/title/0aea9f43-d4a9-4bf7-bebc-550a512f9b95/shikimori-s-not-just-a-cutie"
language = "en" language = "en"
chapters = "1" chapters = "1"