コード例 #1
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def delete_associations(
    id_or_url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    path_service: BasePathService = Provide[Application.services.path_service],
    asset_service: BaseAssetService = Provide[
        Application.services.asset_service],
):
    """Removes all except vital information related to novel, this includes chapters, metadata, and assets."""
    try:
        novel = cli_helpers.get_novel(id_or_url)
    except ValueError:
        sys.exit(1)

    logger.info(f"Removing associated data from '{novel.title}' ({novel.id})…")

    novel_service.delete_volumes(novel)
    logger.info("Deleted volumes and chapters of novel.")

    novel_service.delete_metadata(novel)
    logger.info("Deleted metadata of novel.")

    asset_service.delete_assets_of_novel(novel)
    logger.info("Deleted asset entries of novel.")

    novel_dir = path_service.novel_data_path(novel)
    if novel_dir.exists():
        shutil.rmtree(novel_dir)
    logger.info(
        f"Deleted saved file data of novel: {{data.dir}}/{path_service.relative_to_data_dir(novel_dir)}."
    )
コード例 #2
0
ファイル: novel.py プロジェクト: mHaisham/novelsave
def get_novel(
    id_or_url: str,
    silent: bool = False,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
) -> Novel:
    """retrieve novel is it exists in the database otherwise return none

    :raises ValueError: if novel does not exist
    """

    is_url = url_helper.is_url(id_or_url)
    if is_url:
        novel = novel_service.get_novel_by_url(id_or_url)
    else:
        try:
            novel = novel_service.get_novel_by_id(int(id_or_url))
        except ValueError:
            logger.error(
                f"Value provided is neither a url or an id: {id_or_url}.")
            sys.exit(1)

    if not novel:
        quote = "'" if is_url else ""
        msg = f"Novel not found: {quote}{id_or_url}{quote}."

        logger.info(msg)
        raise ValueError(msg)

    if not silent:
        logger.info(f"Acquired '{novel.title}' ({novel.id}) from database.")

    return novel
コード例 #3
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def delete_downloaded_content(
    id_or_url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    """deletes all the downloaded content from chapters of novel"""
    try:
        novel = cli_helpers.get_novel(id_or_url)
    except ValueError:
        sys.exit(1)

    novel_service.delete_content(novel)
    logger.info(f"Deleted chapter content from '{novel.title}' ({novel.id}).")
コード例 #4
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def import_metadata(
    id_or_url: str,
    metadata_url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    """import metadata from a metadata supplied into an existing novel"""
    try:
        novel = cli_helpers.get_novel(id_or_url)
    except ValueError:
        sys.exit(1)

    meta_source_gateway = cli_helpers.get_meta_source_gateway(metadata_url)
    metadata_dtos = meta_source_gateway.metadata_by_url(metadata_url)

    novel_service.update_metadata(novel, metadata_dtos)
コード例 #5
0
ファイル: _url.py プロジェクト: mHaisham/novelsave
def remove_url(
    url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    """Removes the selected url from the database"""
    try:
        novel = get_novel(url)
    except ValueError:
        sys.exit(1)

    try:
        novel_service.remove_url(novel, url)
    except ValueError as e:
        logger.error(e)
        sys.exit(1)
    else:
        logger.info(f"Removed '{url}' from '{novel.title}' ({novel.id}).")
コード例 #6
0
ファイル: _url.py プロジェクト: mHaisham/novelsave
def add_url(
    id_or_url: str,
    new_url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    """Deletes the novel and all its data"""
    try:
        novel = get_novel(id_or_url)
    except ValueError:
        sys.exit(1)

    try:
        novel_service.add_url(novel, new_url)
    except ValueError as e:
        logger.error(e)
        sys.exit(1)
    else:
        logger.info(f"Added '{new_url}' to '{novel.title}' ({novel.id}).")
コード例 #7
0
ファイル: novel.py プロジェクト: mHaisham/novelsave
def download_thumbnail(
    novel: Novel,
    force: bool = False,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    file_service: BaseFileService = Provide[Application.services.file_service],
    path_service: BasePathService = Provide[Application.services.path_service],
):
    thumbnail_path = path_service.thumbnail_path(novel)
    novel_service.set_thumbnail_asset(
        novel, path_service.relative_to_data_dir(thumbnail_path))

    if not force and thumbnail_path.exists() and thumbnail_path.is_file():
        logger.info("Skipped thumbnail download since file already exists.")
        return

    logger.debug(
        f"Attempting to download thumbnail from {novel.thumbnail_url}.")
    try:
        response = requests.get(novel.thumbnail_url)
    except requests.ConnectionError:
        raise NSError(
            "Connection terminated unexpectedly; Make sure you are connected to the internet."
        )

    if not response.ok:
        logger.error(
            f"Encountered an error during thumbnail download: {response.status_code} {response.reason}."
        )
        return

    thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
    file_service.write_bytes(thumbnail_path, response.content)

    size = string_helper.format_bytes(len(response.content))
    logger.info(
        f"Downloaded and saved thumbnail image to {novel.thumbnail_path} ({size})."
    )
コード例 #8
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def list_novels(
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    source_service: BaseSourceService = Provide[
        Application.services.source_service],
):
    novels = novel_service.get_all_novels()

    table = [["Id", "Title", "Source", "Last updated"]]

    for novel in novels:
        url = novel_service.get_primary_url(novel)

        try:
            source = source_service.source_from_url(url).name
        except SourceNotFoundException:
            source = None

        table.append([novel.id, novel.title, source, novel.last_updated])

    for line in tabulate(table, headers="firstrow",
                         tablefmt="github").splitlines():
        logger.info(line)
コード例 #9
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def delete_novel(
    id_or_url: str,
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    path_service: BasePathService = Provide[Application.services.path_service],
):
    """delete all records of novel. this includes chapters, and assets"""
    try:
        novel = cli_helpers.get_novel(id_or_url)
    except ValueError:
        sys.exit(1)

    logger.info(f"Deleting '{novel.title}' ({novel.id})…")

    novel_dir = path_service.novel_data_path(novel)
    if novel_dir.exists():
        shutil.rmtree(novel_dir)
    logger.info(
        f"Deleted data of novel: {{data.dir}}/{path_service.relative_to_data_dir(novel_dir)}."
    )

    novel_service.delete_novel(novel)
    logger.info("Deleted novel entry.")
コード例 #10
0
ファイル: novel.py プロジェクト: mHaisham/novelsave
def update_novel(
    novel: Novel,
    browser: Optional[str],
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    url = novel_service.get_primary_url(novel)
    logger.debug(f"Acquired primary novel url: {url}.")

    source_gateway = get_source_gateway(url)
    novel_dto = retrieve_novel_info(source_gateway, url, browser)

    novel_service.update_novel(novel, novel_dto)
    novel_service.update_chapters(novel, novel_dto.volumes)
    novel_service.update_metadata(novel, novel_dto.metadata)

    chapters = [c for v in novel_dto.volumes for c in v.chapters]
    logger.info(
        f"Novel updated using new values: id={novel.id} title='{novel.title}' chapters={len(chapters)}"
    )
    return novel
コード例 #11
0
ファイル: novel.py プロジェクト: mHaisham/novelsave
def create_novel(
    url: str,
    browser: Optional[str],
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    path_service: BasePathService = Provide[Application.services.path_service],
) -> Novel:
    """
    retrieve information about the novel from webpage and insert novel into database.
    this includes chapter list and metadata.
    """
    source_gateway = get_source_gateway(url)
    novel_dto = retrieve_novel_info(source_gateway, url, browser)

    novel = novel_service.insert_novel(novel_dto)
    try:
        novel_service.add_url(novel, url)
    except ValueError as e:
        logger.debug("(ERROR) " + str(e))

    novel_service.insert_chapters(novel, novel_dto.volumes)
    novel_service.insert_metadata(novel, novel_dto.metadata)

    chapters = [c for v in novel_dto.volumes for c in v.chapters]
    logger.info(
        f"Added new novel with values: id={novel.id} title='{novel.title}' chapters={len(chapters)}."
    )

    data_dir = path_service.novel_data_path(novel)
    if data_dir.exists():
        logger.debug(
            f"Removing existing data in novel data dir: {{data.dir}}/{path_service.relative_to_data_dir(data_dir)}."
        )
        shutil.rmtree(data_dir)

    return novel
コード例 #12
0
ファイル: novel.py プロジェクト: mHaisham/novelsave
def download_chapters(
    novel: Novel,
    limit: Optional[int],
    threads: Optional[int],
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
    asset_service: BaseAssetService = Provide[
        Application.services.asset_service],
    dto_adapter: DTOAdapter = Provide[Application.adapters.dto_adapter],
):
    chapters = novel_service.get_pending_chapters(novel, limit)
    if not chapters:
        logger.info("Skipped chapter download as none are pending.")
        return

    url = novel_service.get_primary_url(novel)
    logger.debug(f"Acquired primary novel url: {url}.")

    source_gateway = get_source_gateway(url)
    thread_count = (min(threads, os.cpu_count())
                    if threads is not None else os.cpu_count())

    def download(dto: ChapterDTO):
        try:
            return source_gateway.update_chapter_content(dto)
        except Exception as exc:
            raise ContentUpdateFailedException(dto, exc)

    logger.info(
        f"Downloading {len(chapters)} pending chapters with {thread_count} threads…"
    )
    successes = 0
    with tqdm(total=len(chapters), **TQDM_CONFIG) as pbar:
        with futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
            download_futures = [
                executor.submit(download, dto_adapter.chapter_to_dto(c))
                for c in chapters
            ]

            for future in futures.as_completed(download_futures):
                try:
                    chapter_dto = future.result()
                    chapter_dto.content = asset_service.collect_assets(
                        novel, chapter_dto)
                    novel_service.update_content(chapter_dto)

                    logger.debug(
                        f"Chapter content downloaded: '{chapter_dto.title}' ({chapter_dto.index})"
                    )
                    successes += 1
                except ContentUpdateFailedException as e:
                    logger.error(
                        f"An error occurred during content download: {type(e.exception)}."
                    )
                    logger.debug(
                        "An error occurred during content download: {}",
                        type(e.exception),
                    )

                pbar.update(1)

    logger.info(
        f"Chapters download complete, {successes} succeeded, with {len(chapters) - successes} errors."
    )
コード例 #13
0
ファイル: _novel.py プロジェクト: mHaisham/novelsave
def show_info(
    id_or_url: str,
    fmt: Literal["default", "json"] = "default",
    novel_service: BaseNovelService = Provide[
        Application.services.novel_service],
):
    """print current information of novel"""
    try:
        novel = cli_helpers.get_novel(id_or_url, silent=True)
    except ValueError:
        sys.exit(1)

    chapters = novel_service.get_chapters(novel)

    data = {
        "novel": {
            "id": novel.id,
            "title": novel.title,
            "author": novel.author,
            "lang": novel.lang,
            "thumbnail": novel.thumbnail_url,
            "synopsis": novel.synopsis.splitlines(),
            "urls": [o.url for o in novel_service.get_urls(novel)],
        },
        "chapters": {
            "total": len(chapters),
            "downloaded": len([c for c in chapters if c.content]),
        },
    }

    if fmt is None or fmt == "default":
        endl = "\n"
        text = "[novel]" + endl

        def format_keyvalue(key, value):
            text = f"{key} = "

            if type(value) == str:
                text += f"'{value}'"
            else:
                text += str(value)

            text += endl
            return text

        for key, value in data["novel"].items():
            text += format_keyvalue(key, value)

        text += endl
        text += "[chapters]" + endl

        for key, value in data["chapters"].items():
            text += format_keyvalue(key, value)

    elif fmt == "json":
        text = json.dumps(data, indent=4)
    else:
        raise NSError(
            f"Provided novel information formatter is not supported: {fmt}.")

    for line in text.splitlines():
        logger.info(line)