diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6c77825..c54a2bf 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.9 +current_version = 2.1.10 commit = True tag = False serialize = {major}.{minor}.{patch} diff --git a/.woodpecker/test_docker.yml b/.woodpecker/test_docker.yml index d39a575..a1416ad 100644 --- a/.woodpecker/test_docker.yml +++ b/.woodpecker/test_docker.yml @@ -30,7 +30,7 @@ pipeline: dockerfile: docker/Dockerfile.amd64 auto_tag: true auto_tag_suffix: linux-amd64-test - build_args: BUILD_VERSION=2.1.9 + build_args: BUILD_VERSION=2.1.10 # build docker image for arm64 test-build-arm64: @@ -46,4 +46,4 @@ pipeline: dockerfile: docker/Dockerfile.arm64 auto_tag: true auto_tag_suffix: linux-arm64-test - build_args: BUILD_VERSION=2.1.9 + build_args: BUILD_VERSION=2.1.10 diff --git a/.woodpecker/test_release.yml b/.woodpecker/test_release.yml index b5d56ae..0ebaa00 100644 --- a/.woodpecker/test_release.yml +++ b/.woodpecker/test_release.yml @@ -34,5 +34,5 @@ pipeline: image: cr.44net.ch/baseimages/debian-base pull: true commands: - - bash get_release_notes.sh 2.1.9 + - bash get_release_notes.sh 2.1.10 - cat RELEASENOTES.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d074d4..196e60a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add support for more sites +## [2.1.10] - 2022-07-14 + +### Fixed + +- Removed some unused files + +### Added + +- `logger.py` for all log related settings and functions + +### Changed + +- Logging of output. The script now uses the `logging` library + ## [2.1.9] - 2022-06-26 ### Fixed diff --git a/README.md b/README.md index 8f37961..d205765 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,9 @@ options: --format FORMAT Archive format to create. An empty string means dont archive the folder. Defaults to 'cbz' --forcevol Force naming of volumes. For mangas where chapters reset each volume --wait WAIT Time to wait for each picture to download in seconds(float). Defaults 0.5 ---verbose Verbose logging. Defaults to false +--lean Lean logging. Minimal log output. Defaults to false +--verbose Verbose logging. More log output. Defaults to false +--debug Debug logging. Most log output. Defaults to false ``` ### Downloads file-structure diff --git a/contrib/api_template.py b/contrib/api_template.py index 9e329ac..094f286 100644 --- a/contrib/api_template.py +++ b/contrib/api_template.py @@ -7,12 +7,11 @@ class YourAPI: img_base_url = "https://uploads.mangadex.org" # get infos to initiate class - def __init__(self, url_uuid, language, forcevol, verbose): + def __init__(self, url_uuid, language, forcevol): # static info self.url_uuid = url_uuid self.language = language self.forcevol = forcevol - self.verbose = verbose # attributes needed by app.py self.manga_uuid = "abc" diff --git a/docker/Dockerfile.amd64 b/docker/Dockerfile.amd64 index aec1066..775673a 100644 --- a/docker/Dockerfile.amd64 +++ b/docker/Dockerfile.amd64 @@ -1,4 +1,4 @@ -FROM cr.44net.ch/baseimages/debian-s6:1.3.5-linux-amd64 +FROM cr.44net.ch/baseimages/debian-s6:1.3.6-linux-amd64 # set version label ARG BUILD_VERSION @@ -10,29 +10,35 @@ LABEL description="A CLI manga downloader" # install packages RUN \ - echo "**** install base packages ****" && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - && \ - echo "**** creating folders ****" && \ - mkdir -p /app && \ - echo "**** cleanup ****" && \ - apt-get purge --auto-remove -y && \ - apt-get clean && \ - rm -rf \ - /tmp/* \ - /var/lib/apt/lists/* \ - /var/tmp/* + echo "**** install base packages ****" \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + python3 \ + python3-pip + +# prepare app +RUN \ + echo "**** creating folders ****" \ + && mkdir -p /app + +# cleanup installation +RUN \ + echo "**** cleanup ****" \ + && apt-get purge --auto-remove -y \ + && apt-get clean \ + && rm -rf \ + /tmp/* \ + /var/lib/apt/lists/* \ + /var/tmp/* # copy files to container COPY docker/rootfs / COPY mangadlp/ /app/mangadlp/ -COPY manga-dlp.py \ - requirements.txt \ - LICENSE \ +COPY \ + manga-dlp.py \ + requirements.txt \ + LICENSE \ /app/ diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64 index 2924467..ce20e2c 100644 --- a/docker/Dockerfile.arm64 +++ b/docker/Dockerfile.arm64 @@ -1,4 +1,4 @@ -FROM cr.44net.ch/baseimages/debian-s6:1.3.5-linux-arm64 +FROM cr.44net.ch/baseimages/debian-s6:1.3.6-linux-arm64 # set version label ARG BUILD_VERSION @@ -10,29 +10,35 @@ LABEL description="A CLI manga downloader" # install packages RUN \ - echo "**** install base packages ****" && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - && \ - echo "**** creating folders ****" && \ - mkdir -p /app && \ - echo "**** cleanup ****" && \ - apt-get purge --auto-remove -y && \ - apt-get clean && \ - rm -rf \ - /tmp/* \ - /var/lib/apt/lists/* \ - /var/tmp/* + echo "**** install base packages ****" \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + python3 \ + python3-pip + +# prepare app +RUN \ + echo "**** creating folders ****" \ + && mkdir -p /app + +# cleanup installation +RUN \ + echo "**** cleanup ****" \ + && apt-get purge --auto-remove -y \ + && apt-get clean \ + && rm -rf \ + /tmp/* \ + /var/lib/apt/lists/* \ + /var/tmp/* # copy files to container COPY docker/rootfs / COPY mangadlp/ /app/mangadlp/ -COPY manga-dlp.py \ - requirements.txt \ - LICENSE \ +COPY \ + manga-dlp.py \ + requirements.txt \ + LICENSE \ /app/ diff --git a/manga-dlp.py b/manga-dlp.py index 9c3d6b9..5bc81ca 100644 --- a/manga-dlp.py +++ b/manga-dlp.py @@ -1,6 +1,6 @@ from mangadlp.input import main -MDLP_VERSION = "2.1.9" +MDLP_VERSION = "2.1.10" if __name__ == "__main__": main() diff --git a/mangadlp/__init__.py b/mangadlp/__init__.py index e69de29..a4db992 100644 --- a/mangadlp/__init__.py +++ b/mangadlp/__init__.py @@ -0,0 +1,22 @@ +import logging + +from mangadlp.logger import logger_lean, logger_verbose + +# prepare logger with default level INFO==20 +logging.basicConfig( + format="%(asctime)s | %(levelname)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + level=20, + handlers=[logging.StreamHandler()], +) + +# create custom log levels +logging.addLevelName(15, "VERBOSE") +logging.VERBOSE = 15 # type: ignore +logging.verbose = logger_verbose # type: ignore +logging.Logger.verbose = logger_verbose # type: ignore + +logging.addLevelName(25, "LEAN") +logging.VERBOSE = 25 # type: ignore +logging.lean = logger_lean # type: ignore +logging.Logger.lean = logger_lean # type: ignore diff --git a/mangadlp/api/mangadex.py b/mangadlp/api/mangadex.py index 5fa6a0e..ff0f3ae 100644 --- a/mangadlp/api/mangadex.py +++ b/mangadlp/api/mangadex.py @@ -1,3 +1,4 @@ +import logging import re import sys from time import sleep @@ -15,12 +16,11 @@ class Mangadex: img_base_url = "https://uploads.mangadex.org" # get infos to initiate class - def __init__(self, url_uuid: str, language: str, forcevol: bool, verbosity: int): + def __init__(self, url_uuid: str, language: str, forcevol: bool): # static info self.url_uuid = url_uuid self.language = language self.forcevol = forcevol - self.verbosity = verbosity # api stuff self.api_content_ratings = "contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic" @@ -36,8 +36,7 @@ class Mangadex: # make initial request def get_manga_data(self) -> requests.Response: - if self.verbosity >= 2: - print(f"INFO: Getting manga data for: {self.manga_uuid}") + logging.verbose(f"Getting manga data for: {self.manga_uuid}") # type: ignore counter = 1 while counter <= 3: try: @@ -46,17 +45,17 @@ class Mangadex: ) except: if counter >= 3: - print("ERR: Maybe the MangaDex API is down?") + logging.error("Maybe the MangaDex API is down?") sys.exit(1) else: - print("ERR: Mangadex API not reachable. Retrying") + logging.error("Mangadex API not reachable. Retrying") sleep(2) counter += 1 else: break # check if manga exists if manga_data.json()["result"] != "ok": - print("ERR: Manga not found") + logging.error("Manga not found") sys.exit(1) return manga_data @@ -69,15 +68,14 @@ class Mangadex: ) # check for new mangadex id if not uuid_regex.search(self.url_uuid): - print("ERR: No valid UUID found") + logging.error("No valid UUID found") sys.exit(1) manga_uuid = uuid_regex.search(self.url_uuid)[0] return manga_uuid # get the title of the manga (and fix the filename) def get_manga_title(self) -> str: - if self.verbosity >= 2: - print(f"INFO: Getting manga title for: {self.manga_uuid}") + logging.verbose(f"Getting manga title for: {self.manga_uuid}") # type: ignore manga_data = self.manga_data.json() try: title = manga_data["data"]["attributes"]["title"][self.language] @@ -89,37 +87,35 @@ class Mangadex: alt_titles.update(title) title = alt_titles[self.language] except: # no title on requested language found - print("ERR: Chapter in requested language not found.") + logging.error("Chapter in requested language not found.") sys.exit(1) return utils.fix_name(title) # check if chapters are available in requested language def check_chapter_lang(self) -> int: - if self.verbosity >= 2: - print( - f"INFO: Checking for chapters in specified language for: {self.manga_uuid}" - ) + logging.verbose( # type: ignore + f"Checking for chapters in specified language for: {self.manga_uuid}" + ) r = requests.get( f"{self.api_base_url}/manga/{self.manga_uuid}/feed?limit=0&{self.api_additions}" ) try: total_chapters = r.json()["total"] except: - print( - "ERR: Error retrieving the chapters list. Did you specify a valid language code?" + logging.error( + "Error retrieving the chapters list. Did you specify a valid language code?" ) return 0 else: if total_chapters == 0: - print("ERR: No chapters available to download!") + logging.error("No chapters available to download!") return 0 return total_chapters # get chapter data like name, uuid etc def get_chapter_data(self) -> dict: - if self.verbosity >= 2: - print(f"INFO: Getting chapter data for: {self.manga_uuid}") + logging.verbose(f"Getting chapter data for: {self.manga_uuid}") # type: ignore api_sorting = "order[chapter]=asc&order[volume]=asc" # check for chapters in specified lang total_chapters = self.check_chapter_lang() @@ -177,8 +173,7 @@ class Mangadex: # get images for the chapter (mangadex@home) def get_chapter_images(self, chapter: str, wait_time: float) -> list: - if self.verbosity >= 2: - print(f"INFO: Getting chapter images for: {self.manga_uuid}") + logging.verbose(f"Getting chapter images for: {self.manga_uuid}") # type: ignore athome_url = f"{self.api_base_url}/at-home/server" chapter_uuid = self.manga_chapter_data[chapter][0] @@ -190,11 +185,11 @@ class Mangadex: r = requests.get(f"{athome_url}/{chapter_uuid}") api_data = r.json() if api_data["result"] != "ok": - print(f"ERR: No chapter with the id {chapter_uuid} found") + logging.error(f"No chapter with the id {chapter_uuid} found") api_error = True raise IndexError elif api_data["chapter"]["data"] is None: - print(f"ERR: No chapter data found for chapter {chapter_uuid}") + logging.error(f"No chapter data found for chapter {chapter_uuid}") api_error = True raise IndexError else: @@ -203,7 +198,7 @@ class Mangadex: except: if counter >= 3: api_error = True - print(f"ERR: Retrying in a few seconds") + logging.error(f"Retrying in a few seconds") counter += 1 sleep(wait_time + 2) # check if result is ok @@ -224,8 +219,7 @@ class Mangadex: # create list of chapters def create_chapter_list(self) -> list: - if self.verbosity >= 2: - print(f"INFO: Creating chapter list for: {self.manga_uuid}") + logging.verbose(f"Creating chapter list for: {self.manga_uuid}") # type: ignore chapter_list = [] for chapter in self.manga_chapter_data.items(): chapter_info = self.get_chapter_infos(chapter[0]) @@ -240,10 +234,9 @@ class Mangadex: # create easy to access chapter infos def get_chapter_infos(self, chapter: str) -> dict: - if self.verbosity >= 3: - print( - f"INFO: Getting chapter infos for: {self.manga_chapter_data[chapter][0]}" - ) + logging.debug( + f"Getting chapter infos for: {self.manga_chapter_data[chapter][0]}" + ) chapter_uuid = self.manga_chapter_data[chapter][0] chapter_vol = self.manga_chapter_data[chapter][1] chapter_num = self.manga_chapter_data[chapter][2] diff --git a/mangadlp/app.py b/mangadlp/app.py index 0803db7..a58ce56 100644 --- a/mangadlp/app.py +++ b/mangadlp/app.py @@ -1,3 +1,4 @@ +import logging import re import shutil import sys @@ -23,7 +24,7 @@ class MangaDLP: :param forcevol: Force naming of volumes. Useful for mangas where chapters reset each volume :param download_path: Download path. Defaults to '/downloads' :param download_wait: Time to wait for each picture to download in seconds - :param verbosity: Verbosity of the output + :param verbosity: Verbosity of the output. Uses the logging library values :return: Nothing. Just the files """ @@ -38,7 +39,7 @@ class MangaDLP: forcevol: bool = False, download_path: str = "downloads", download_wait: float = 0.5, - verbosity: int = 0, + verbosity: int = 20, ) -> None: # init parameters self.url_uuid = url_uuid @@ -54,7 +55,6 @@ class MangaDLP: self._prepare() def _prepare(self) -> None: - # additional stuff # set manga format suffix if self.file_format and "." not in self.file_format: self.file_format = f".{self.file_format}" @@ -62,9 +62,7 @@ class MangaDLP: self.pre_checks() # init api self.api_used = self.check_api(self.url_uuid) - self.api = self.api_used( - self.url_uuid, self.language, self.forcevol, self.verbosity - ) + self.api = self.api_used(self.url_uuid, self.language, self.forcevol) # get manga title and uuid self.manga_uuid = self.api.manga_uuid self.manga_title = self.api.manga_title @@ -76,25 +74,25 @@ class MangaDLP: # prechecks userinput/options # no url and no readin list given if not self.url_uuid: - print( - f'ERR: You need to specify a manga url/uuid with "-u" or a list with "--read"' + logging.error( + 'You need to specify a manga url/uuid with "-u" or a list with "--read"' ) sys.exit(1) # checks if --list is not used if not self.list_chapters: if self.chapters is None: # no chapters to download were given - print( - f'ERR: You need to specify one or more chapters to download. To see all chapters use "--list"' + logging.error( + 'You need to specify one or more chapters to download. To see all chapters use "--list"' ) sys.exit(1) # if forcevol is used, but didn't specify a volume in the chapters selected if self.forcevol and ":" not in self.chapters: - print(f"ERR: You need to specify the volume if you use --forcevol") + logging.error("You need to specify the volume if you use --forcevol") sys.exit(1) # if forcevol is not used, but a volume is specified if not self.forcevol and ":" in self.chapters: - print(f"ERR: Don't specify the volume without --forcevol") + logging.error("Don't specify the volume without --forcevol") sys.exit(1) # check the api which needs to be used @@ -112,11 +110,11 @@ class MangaDLP: # this is only for testing multiple apis if api_test.search(url_uuid): - print("Not supported yet") + logging.critical("Not supported yet") sys.exit(1) # no supported api found - print(f"ERR: No supported api in link/uuid found: {url_uuid}") + logging.error(f"No supported api in link/uuid found: {url_uuid}") raise ValueError # once called per manga @@ -127,18 +125,15 @@ class MangaDLP: print_divider = "=========================================" # show infos - if self.verbosity == 1: - print(f"INFO: Manga Name: {self.manga_title}") - else: - print(f"{print_divider}") - print(f"INFO: Manga Name: {self.manga_title}") - print(f"INFO: Manga UUID: {self.manga_uuid}") - print(f"INFO: Total chapters: {len(self.manga_chapter_list)}") + logging.info(f"{print_divider}") + logging.lean(f"Manga Name: {self.manga_title}") # type: ignore + logging.info(f"Manga UUID: {self.manga_uuid}") + logging.info(f"Total chapters: {len(self.manga_chapter_list)}") # list chapters if list_chapters is true if self.list_chapters: - print(f"INFO: Available Chapters:\n{', '.join(self.manga_chapter_list)}") - print(f"{print_divider}\n") + logging.info(f"Available Chapters: {', '.join(self.manga_chapter_list)}") + logging.info(f"{print_divider}\n") return None # check chapters to download if not all @@ -150,11 +145,8 @@ class MangaDLP: ) # show chapters to download - if self.verbosity == 1: - print(f"INFO: Chapters selected: {', '.join(chapters_to_download)}") - else: - print(f"INFO: Chapters selected:\n{', '.join(chapters_to_download)}") - print(f"{print_divider}") + logging.lean(f"Chapters selected: {', '.join(chapters_to_download)}") # type: ignore + logging.info(f"{print_divider}") # create manga folder self.manga_path.mkdir(parents=True, exist_ok=True) @@ -174,28 +166,21 @@ class MangaDLP: # chapter was not skipped except KeyError: # done with chapter - print("INFO: Done with chapter\n") + logging.info("Done with chapter\n") # done with manga - if self.verbosity != 1: - print(f"{print_divider}") - print(f"INFO: Done with manga: {self.manga_title}") + logging.info(f"{print_divider}") + logging.lean(f"Done with manga: {self.manga_title}") # type: ignore # filter skipped list skipped_chapters = list(filter(None, skipped_chapters)) if len(skipped_chapters) >= 1: - if self.verbosity == 1: - print(f"INFO: Skipped chapters: {', '.join(skipped_chapters)}") - else: - print(f"INFO: Skipped chapters:\n{', '.join(skipped_chapters)}") + logging.lean(f"Skipped chapters: {', '.join(skipped_chapters)}") # type: ignore # filter error list error_chapters = list(filter(None, error_chapters)) if len(error_chapters) >= 1: - if self.verbosity == 1: - print(f"INFO: Chapters with errors: {', '.join(error_chapters)}") - else: - print(f"INFO: Chapters with errors:\n{', '.join(error_chapters)}") - if self.verbosity != 1: - print(f"{print_divider}\n") + logging.lean(f"Chapters with errors: {', '.join(error_chapters)}") # type: ignore + + logging.info(f"{print_divider}\n") # once called per chapter def get_chapter(self, chapter: str) -> dict: @@ -208,13 +193,13 @@ class MangaDLP: chapter, self.download_wait ) except KeyboardInterrupt: - print("ERR: Stopping") + logging.critical("Stopping") sys.exit(1) # check if the image urls are empty. if yes skip this chapter (for mass downloads) if not chapter_image_urls: - print( - f"ERR: No images: Skipping Vol. {chapter_infos['volume']} Ch.{chapter_infos['chapter']}" + logging.error( + f"No images: Skipping Vol. {chapter_infos['volume']} Ch.{chapter_infos['chapter']}" ) # add to skipped chapters list return ( @@ -239,8 +224,8 @@ class MangaDLP: # check if chapter already exists # check for folder, if file format is an empty string if chapter_archive_path.exists(): - if self.verbosity != 1: - print(f"INFO: '{chapter_archive_path}' already exists. Skipping") + if self.verbosity != "lean": + logging.warning(f"'{chapter_archive_path}' already exists. Skipping") # add to skipped chapters list return ( { @@ -255,25 +240,24 @@ class MangaDLP: chapter_path.mkdir(parents=True, exist_ok=True) # verbose log - if self.verbosity >= 2: - print(f"INFO: Chapter UUID: {chapter_infos['uuid']}") - print(f"INFO: Filename: '{chapter_archive_path.name}'\n") - print(f"INFO: File path: '{chapter_archive_path}'\n") - print(f"INFO: Image URLS:\n{chapter_image_urls}\n") + logging.verbose(f"Chapter UUID: {chapter_infos['uuid']}") # type: ignore + logging.verbose(f"Filename: '{chapter_archive_path.name}'") # type: ignore + logging.verbose(f"File path: '{chapter_archive_path}'") # type: ignore + logging.verbose(f"Image URLS:\n{chapter_image_urls}") # type: ignore # log - print(f"INFO: Downloading: '{chapter_filename}'") + logging.lean(f"Downloading: '{chapter_filename}'") # type: ignore # download images try: downloader.download_chapter( - chapter_image_urls, chapter_path, self.download_wait, self.verbosity + chapter_image_urls, chapter_path, self.download_wait ) except KeyboardInterrupt: - print("ERR: Stopping") + logging.critical("Stopping") sys.exit(1) except: - print(f"ERR: Cant download: '{chapter_filename}'. Skipping") + logging.error(f"Cant download: '{chapter_filename}'. Skipping") # add to skipped chapters list return ( { @@ -286,23 +270,23 @@ class MangaDLP: else: # Done with chapter - print(f"INFO: Successfully downloaded: '{chapter_filename}'") + logging.lean(f"INFO: Successfully downloaded: '{chapter_filename}'") # type: ignore return {"chapter_path": chapter_path} # create an archive of the chapter if needed def archive_chapter(self, chapter_path: Path) -> dict: - print(f"INFO: Creating '{self.file_format}' archive") + logging.lean(f"INFO: Creating '{self.file_format}' archive") # type: ignore try: # check if image folder is existing if not chapter_path.exists(): - print(f"ERR: Image folder: {chapter_path} does not exist") + logging.error(f"Image folder: {chapter_path} does not exist") raise IOError if self.file_format == ".pdf": utils.make_pdf(chapter_path) else: utils.make_archive(chapter_path, self.file_format) except: - print(f"ERR: Archive error. Skipping chapter") + logging.error(f"Archive error. Skipping chapter") # add to skipped chapters list return { "error": chapter_path, diff --git a/mangadlp/downloader.py b/mangadlp/downloader.py index a829e8c..d24f056 100644 --- a/mangadlp/downloader.py +++ b/mangadlp/downloader.py @@ -1,3 +1,4 @@ +import logging import shutil import sys from pathlib import Path @@ -14,7 +15,6 @@ def download_chapter( image_urls: list, chapter_path: Union[str, Path], download_wait: float, - verbosity: int, ) -> None: total_img = len(image_urls) for image_num, image in enumerate(image_urls, 1): @@ -22,25 +22,24 @@ def download_chapter( image_suffix = str(Path(image).suffix) or ".png" # set image path image_path = Path(f"{chapter_path}/{image_num:03d}{image_suffix}") - # show progress bar or progress by image for verbose - if verbosity == 0: + # show progress bar for default log level + if logging.root.level == logging.INFO: utils.progress_bar(image_num, total_img) - elif verbosity >= 2: - print(f"INFO: Downloading image {image_num}/{total_img}") + logging.verbose(f"Downloading image {image_num}/{total_img}") # type: ignore counter = 1 while counter <= 3: try: r = requests.get(image, stream=True) if r.status_code != 200: - print(f"ERR: Request for image {image} failed, retrying") + logging.error(f"Request for image {image} failed, retrying") raise ConnectionError except KeyboardInterrupt: - print("ERR: Stopping") + logging.critical("Stopping") sys.exit(1) except: if counter >= 3: - print("ERR: Maybe the MangaDex Servers are down?") + logging.error("Maybe the MangaDex Servers are down?") raise ConnectionError sleep(download_wait) counter += 1 @@ -53,7 +52,7 @@ def download_chapter( r.raw.decode_content = True shutil.copyfileobj(r.raw, file) except: - print("ERR: Can't write file") + logging.error("Can't write file") raise IOError image_num += 1 diff --git a/mangadlp/input.py b/mangadlp/input.py index f337c4e..8e8762d 100644 --- a/mangadlp/input.py +++ b/mangadlp/input.py @@ -1,11 +1,11 @@ import argparse -import subprocess import sys from pathlib import Path import mangadlp.app as app +import mangadlp.logger as logger -MDLP_VERSION = "2.1.9" +MDLP_VERSION = "2.1.10" def check_args(args): @@ -38,6 +38,8 @@ def readin_list(readlist: str) -> list: def call_app(args): + # set logger formatting + logger.format_logger(args.verbosity) # call main function with all input arguments mdlp = app.MangaDLP( args.url_uuid, @@ -182,28 +184,28 @@ def get_args(): "--lean", dest="verbosity", required=False, - help="Lean logging. Defaults to false", + help="Lean logging. Minimal log output. Defaults to false", action="store_const", - const=1, - default=0, + const=25, + default=20, ) verbosity.add_argument( "--verbose", dest="verbosity", required=False, - help="Verbose logging. Defaults to false", + help="Verbose logging. More log output. Defaults to false", action="store_const", - const=2, - default=0, + const=15, + default=20, ) verbosity.add_argument( "--debug", dest="verbosity", required=False, - help="Lean logging. Defaults to false", + help="Debug logging. Most log output. Defaults to false", action="store_const", - const=3, - default=0, + const=10, + default=20, ) # parser.print_help() diff --git a/mangadlp/logger.py b/mangadlp/logger.py new file mode 100644 index 0000000..817355c --- /dev/null +++ b/mangadlp/logger.py @@ -0,0 +1,32 @@ +import logging + + +# set log message format +def format_logger(verbosity: int): + logging.getLogger().setLevel(verbosity) + + # dont show log level name on default/lean logging + if verbosity >= 20: + logging.basicConfig( + format="%(asctime)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + force=True, + ) + else: + logging.basicConfig( + format="%(asctime)s | %(levelname)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + force=True, + ) + + +# create verbose logger with level 15 +def logger_verbose(msg, *args, **kwargs): + if logging.getLogger().isEnabledFor(15): + logging.log(15, msg) + + +# create lean logger with level 25 +def logger_lean(msg, *args, **kwargs): + if logging.getLogger().isEnabledFor(25): + logging.log(25, msg) diff --git a/mangadlp/utils.py b/mangadlp/utils.py index 6027949..fde1056 100644 --- a/mangadlp/utils.py +++ b/mangadlp/utils.py @@ -1,4 +1,6 @@ +import logging import re +from datetime import datetime from pathlib import Path from typing import Any from zipfile import ZipFile @@ -22,7 +24,7 @@ def make_pdf(chapter_path: Path) -> None: try: import img2pdf except: - print("Cant import img2pdf. Please install it first") + logging.error("Cant import img2pdf. Please install it first") raise ImportError pdf_path = Path(f"{chapter_path}.pdf") @@ -32,7 +34,7 @@ def make_pdf(chapter_path: Path) -> None: try: pdf_path.write_bytes(img2pdf.convert(images)) except: - print("ERR: Can't create '.pdf' archive") + logging.error("Can't create '.pdf' archive") raise IOError @@ -115,6 +117,7 @@ def get_filename( def progress_bar(progress: float, total: float) -> None: + time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") percent = int(progress / (int(total) / 100)) bar_length = 50 bar_progress = int(progress / (int(total) / bar_length)) @@ -122,6 +125,6 @@ def progress_bar(progress: float, total: float) -> None: whitespace_texture = " " * (bar_length - bar_progress) if progress == total: full_bar = "■" * bar_length - print(f"\r❙{full_bar}❙ 100%", end="\n") + print(f"\r{time} | ❙{full_bar}❙ 100%", end="\n") else: - print(f"\r❙{bar_texture}{whitespace_texture}❙ {percent}%", end="\r") + print(f"\r{time} | ❙{bar_texture}{whitespace_texture}❙ {percent}%", end="\r") diff --git a/pyproject.toml b/pyproject.toml index 123bc7b..75f2dd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -version = "2.1.9" +version = "2.1.10" name = "manga-dlp" description = "A cli manga downloader" readme = "README.md" @@ -107,3 +107,10 @@ exclude_lines = [ "@(abc.)?abstractmethod", ] ignore_errors = true + +[tool.pylint.main] +py-version = "3.9" + +[tool.pylint.logging] +logging-modules = ["logging"] +logging-format-style = "fstr" diff --git a/release-files.txt b/release-files.txt deleted file mode 100644 index 4cce858..0000000 --- a/release-files.txt +++ /dev/null @@ -1,8 +0,0 @@ -mangadlp/ -manga-dlp.py -pyproject.toml -requirements.txt -MANIFEST.in -README.md -CHANGELOG.md -LICENSE diff --git a/tests/test_02_utils.py b/tests/test_02_utils.py index 305d7e7..4c12d23 100644 --- a/tests/test_02_utils.py +++ b/tests/test_02_utils.py @@ -57,7 +57,7 @@ def test_chapter_list_full(): forcevol=True, download_path="tests", download_wait=2, - verbosity=3, + verbosity=10, ) chap_list = utils.get_chapter_list("1:1,1:2,1:4-1:7,2:", mdlp.manga_chapter_list) assert chap_list == [ diff --git a/tests/test_03_downloader.py b/tests/test_03_downloader.py index ad2d544..2985a59 100644 --- a/tests/test_03_downloader.py +++ b/tests/test_03_downloader.py @@ -18,7 +18,7 @@ def test_downloader(): chapter_path = Path("tests/test_folder1") chapter_path.mkdir(parents=True, exist_ok=True) images = [] - downloader.download_chapter(urls, str(chapter_path), 2, True) + downloader.download_chapter(urls, str(chapter_path), 2) for file in chapter_path.iterdir(): images.append(file.name) @@ -43,7 +43,7 @@ def test_downloader_fail(monkeypatch): chapter_path.mkdir(parents=True, exist_ok=True) monkeypatch.setattr(requests, "get", fail_url) with pytest.raises(ConnectionError) as e: - downloader.download_chapter(images, str(chapter_path), 2, True) + downloader.download_chapter(images, str(chapter_path), 2) assert e.type == ConnectionError # cleanup diff --git a/tests/test_11_api_mangadex.py b/tests/test_11_api_mangadex.py index aa1b70b..b07b272 100644 --- a/tests/test_11_api_mangadex.py +++ b/tests/test_11_api_mangadex.py @@ -8,8 +8,7 @@ def test_uuid_link(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) assert test.manga_uuid == "a96676e5-8ae2-425e-b549-7f15dd34a6d8" @@ -18,8 +17,7 @@ def test_uuid_pure(): url_uuid = "a96676e5-8ae2-425e-b549-7f15dd34a6d8" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) assert test.manga_uuid == "a96676e5-8ae2-425e-b549-7f15dd34a6d8" @@ -28,10 +26,9 @@ def test_uuid_link_false(): url_uuid = "https://mangadex.org/title/a966-76e-5-8a-e2-42-5e-b-549-7f15dd-34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 with pytest.raises(SystemExit) as e: - Mangadex(url_uuid, language, forcevol, verbosity) + Mangadex(url_uuid, language, forcevol) assert e.type == SystemExit assert e.value.code == 1 @@ -40,8 +37,7 @@ def test_title(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) assert test.manga_title == "Komi-san wa Komyushou Desu" @@ -50,8 +46,7 @@ def test_chapter_infos(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) chapter_infos = test.get_chapter_infos("1") chapter_uuid = chapter_infos["uuid"] chapter_name = chapter_infos["name"] @@ -70,10 +65,9 @@ def test_non_existing_manga(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-999999999999/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 with pytest.raises(SystemExit) as e: - Mangadex(url_uuid, language, forcevol, verbosity) + Mangadex(url_uuid, language, forcevol) assert e.type == SystemExit assert e.value.code == 1 @@ -86,10 +80,9 @@ def test_api_failure(monkeypatch): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 with pytest.raises(SystemExit) as e: - Mangadex(url_uuid, language, forcevol, verbosity) + Mangadex(url_uuid, language, forcevol) assert e.type == SystemExit assert e.value.code == 1 @@ -98,8 +91,7 @@ def test_chapter_lang_en(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = True - test = Mangadex(url_uuid, language, forcevol, 3) + test = Mangadex(url_uuid, language, forcevol) assert test.check_chapter_lang() > 0 @@ -108,11 +100,10 @@ def test_empty_chapter_lang(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "ch" forcevol = False - verbosity = 3 with pytest.raises(SystemExit) as e: - Mangadex(url_uuid, language, forcevol, verbosity) - Mangadex(url_uuid, language, forcevol, verbosity).check_chapter_lang() + Mangadex(url_uuid, language, forcevol) + Mangadex(url_uuid, language, forcevol).check_chapter_lang() assert e.type == KeyError or e.type == SystemExit assert e.value.code == 1 @@ -121,10 +112,9 @@ def test_not_existing_lang(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "zz" forcevol = False - verbosity = 3 with pytest.raises(SystemExit) as e: - Mangadex(url_uuid, language, forcevol, verbosity) + Mangadex(url_uuid, language, forcevol) assert e.type == SystemExit assert e.value.code == 1 @@ -135,8 +125,7 @@ def test_create_chapter_list(): ) language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) test_list = [ "1", "2", @@ -170,8 +159,7 @@ def test_create_chapter_list_forcevol(): ) language = "en" forcevol = True - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) test_list = [ "1:1", "1:2", @@ -203,8 +191,7 @@ def test_get_chapter_images(): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) img_base_url = "https://uploads.mangadex.org" chapter_hash = "0752bc5db298beff6b932b9151dd8437" chapter_uuid = "e86ec2c4-c5e4-4710-bfaa-7604f00939c7" @@ -235,8 +222,7 @@ def test_get_chapter_images_error(monkeypatch): url_uuid = "https://mangadex.org/title/a96676e5-8ae2-425e-b549-7f15dd34a6d8/komi-san-wa-komyushou-desu" language = "en" forcevol = False - verbosity = 3 - test = Mangadex(url_uuid, language, forcevol, verbosity) + test = Mangadex(url_uuid, language, forcevol) chapter_num = "1" monkeypatch.setattr(requests, "get", fail_url) diff --git a/tests/test_21_full.py b/tests/test_21_full.py index beb3d66..2ba743f 100644 --- a/tests/test_21_full.py +++ b/tests/test_21_full.py @@ -33,7 +33,7 @@ def test_full_api_mangadex(wait_20s): forcevol=False, download_path="tests", download_wait=2, - verbosity=3, + verbosity=10, ) mdlp.get_manga() @@ -61,6 +61,24 @@ def test_full_with_input_cbz(wait_20s): shutil.rmtree(manga_path, ignore_errors=True) +def test_full_with_input_cbz_info(wait_20s): + url_uuid = "https://mangadex.org/title/0aea9f43-d4a9-4bf7-bebc-550a512f9b95/shikimori-s-not-just-a-cutie" + language = "en" + chapters = "1" + file_format = "cbz" + download_path = "tests" + manga_path = Path("tests/Shikimori's Not Just a Cutie") + chapter_path = Path("tests/Shikimori's Not Just a Cutie/Ch. 1.cbz") + command_args = f"-u {url_uuid} -l {language} -c {chapters} --path {download_path} --format {file_format} --wait 2" + script_path = "manga-dlp.py" + os.system(f"python3 {script_path} {command_args}") + + assert manga_path.exists() and manga_path.is_dir() + assert chapter_path.exists() and chapter_path.is_file() + # cleanup + shutil.rmtree(manga_path, ignore_errors=True) + + def test_full_with_input_pdf(wait_20s): # check if its arm64, if yes skip this step if platform.machine() != "x86_64":