From ba3c8445b643a3dbd1c15d6086d7a4a3c19dbf06 Mon Sep 17 00:00:00 2001 From: Zehina Date: Sun, 20 Oct 2024 19:32:35 +0100 Subject: [PATCH] sanitize filename on Windows --- webtoon_downloader/core/exceptions.py | 13 ++++++++++--- webtoon_downloader/core/webtoon/namer.py | 19 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/webtoon_downloader/core/exceptions.py b/webtoon_downloader/core/exceptions.py index 5d1dd79..9611a62 100644 --- a/webtoon_downloader/core/exceptions.py +++ b/webtoon_downloader/core/exceptions.py @@ -11,6 +11,7 @@ class DownloadError(Exception): url: str cause: Exception + base_message: str = "Failed to download from" message: str | None = field(default=None) def __str__(self) -> str: @@ -20,27 +21,33 @@ def __str__(self) -> str: if self.cause: cause_msg = str(self.cause) if cause_msg: - return f"Failed to download from {self.url} => {cause_msg}" + return f"{self.base_message} {self.url} => {cause_msg}" - return f"Failed to download from {self.url} due to: {self.cause.__class__.__name__}" + return f"{self.base_message} {self.url} due to: {self.cause.__class__.__name__}" - return f"Failed to download from {self.url}" + return f"{self.base_message} {self.url}" @dataclass class WebtoonDownloadError(DownloadError): """Exception raised for Webtoon download errors""" + base_message: str = "Failed to download Webtoon" + @dataclass class ImageDownloadError(DownloadError): """Exception raised for image download errors""" + base_message: str = "downloading image" + @dataclass class ChapterDownloadError(DownloadError): """Exception raised for chapter download errors""" + base_message: str = "downloading chapter" + chapter_info: ChapterInfo | None = None diff --git a/webtoon_downloader/core/webtoon/namer.py b/webtoon_downloader/core/webtoon/namer.py index f201363..49c40e5 100644 --- a/webtoon_downloader/core/webtoon/namer.py +++ b/webtoon_downloader/core/webtoon/namer.py @@ -1,3 +1,5 @@ +import os +import re from dataclasses import dataclass from pathlib import Path from typing import Protocol, runtime_checkable @@ -7,6 +9,18 @@ from webtoon_downloader.core.webtoon.models import ChapterInfo, PageInfo +def sanitize_filename(filename: str) -> str: + """ + Sanitizes a filename by replacing all non-alphanumeric characters with underscores on Windows. + """ + if os.name == "nt": + filename = re.sub(r"[^\w\.-]", "_", filename) + if filename[-1] == "_": + filename = filename[:-1] + + return filename + + @runtime_checkable class FileNameGenerator(Protocol): """ @@ -47,7 +61,8 @@ def get_chapter_directory(self, chapter_info: ChapterInfo) -> Path: Returns the directory path for storing the given chapter's data. """ if self.use_chapter_title_directories: - return Path(chapter_info.title) + return Path(sanitize_filename(chapter_info.title)) + return Path(f"{chapter_info.number:0{len(str(chapter_info.total_chapters))}d}") def get_page_filename(self, page_info: PageInfo) -> str: @@ -77,7 +92,7 @@ class NonSeparateFileNameGenerator(FileNameGenerator): are stored in the same directory. """ - def get_chapter_directory(self, chapter_info: ChapterInfo) -> Path: # type ignore + def get_chapter_directory(self, _: ChapterInfo) -> Path: # type ignore """ Returns the root directory for storing pages when they are not separated by chapters. """