diff --git a/.gitignore b/.gitignore index e2a43594..f6222a65 100644 --- a/.gitignore +++ b/.gitignore @@ -173,7 +173,7 @@ cython_debug/ test.* .run -/dev.sh /src/templates/ /src/config/ /src/debuger.py +/src/dist.zip diff --git a/dev.sh b/dev.sh new file mode 100644 index 00000000..323fc3c2 --- /dev/null +++ b/dev.sh @@ -0,0 +1,24 @@ +#!/bin/bash + + +# This script is used to run the development environment. + +python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple install -r requirements.txt + +cd src || exit + +CONFIG_DIR="config" + +if [ ! -d "$CONFIG_DIR" ]; then + echo "The directory '$CONFIG_DIR' is missing." + mkdir config +fi + +VERSION_FILE="module/__version__.py" + +if [ ! -f "$VERSION_FILE" ]; then + echo "The file '$VERSION_FILE' is missing." + echo "VERSION='DEV_VERSION'" >> "$VERSION_FILE" +fi + +python3 main.py \ No newline at end of file diff --git a/src/module/api/bangumi.py b/src/module/api/bangumi.py index e2c700e1..8091890a 100644 --- a/src/module/api/bangumi.py +++ b/src/module/api/bangumi.py @@ -1,9 +1,9 @@ from fastapi import Depends, HTTPException, status +from fastapi.responses import JSONResponse from .log import router from module.models import BangumiData -from module.database import BangumiDatabase from module.manager import TorrentManager from module.security import get_current_user @@ -33,7 +33,7 @@ async def get_data(bangumi_id: str, current_user=Depends(get_current_user)): @router.post("/api/v1/bangumi/updateRule", tags=["bangumi"]) -async def update_data(data: BangumiData, current_user=Depends(get_current_user)): +async def update_rule(data: BangumiData, current_user=Depends(get_current_user)): if not current_user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" @@ -43,7 +43,7 @@ async def update_data(data: BangumiData, current_user=Depends(get_current_user)) @router.delete("/api/v1/bangumi/deleteRule/{bangumi_id}", tags=["bangumi"]) -async def delete_data(bangumi_id: str, file:bool = False, current_user=Depends(get_current_user)): +async def delete_rule(bangumi_id: str, file: bool = False, current_user=Depends(get_current_user)): if not current_user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" @@ -53,7 +53,7 @@ async def delete_data(bangumi_id: str, file:bool = False, current_user=Depends(g @router.delete("/api/v1/bangumi/disableRule/{bangumi_id}", tags=["bangumi"]) -async def delete_rule( +async def disable_rule( bangumi_id: str, file: bool = False, current_user=Depends(get_current_user) ): if not current_user: @@ -80,6 +80,6 @@ async def reset_all(current_user=Depends(get_current_user)): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" ) - with BangumiDatabase() as database: - database.delete_all() - return {"status": "ok"} + with TorrentManager() as torrent: + torrent.delete_all() + return JSONResponse(status_code=200, content={"message": "OK"}) diff --git a/src/module/api/proxy.py b/src/module/api/proxy.py index d06052fc..c9b43b8a 100644 --- a/src/module/api/proxy.py +++ b/src/module/api/proxy.py @@ -54,8 +54,8 @@ async def get_search_result(searchstr: str): @router.get("/RSS/Bangumi", tags=["proxy"]) -async def get_bangumi(bangumiId: str, groupid: str): - full_path = "Bangumi?bangumiId=" + bangumiId + "&groupid=" + groupid +async def get_bangumi(bangumiId: str, subgroupid: str): + full_path = "Bangumi?bangumiId=" + bangumiId + "&subgroupid=" + subgroupid content = get_rss_content(full_path) return Response(content, media_type="application/xml") diff --git a/src/module/checker/checker.py b/src/module/checker/checker.py index aba46030..fc2c46d8 100644 --- a/src/module/checker/checker.py +++ b/src/module/checker/checker.py @@ -9,14 +9,16 @@ class Checker: def __init__(self): pass - def check_renamer(self) -> bool: - if self.check_downloader() and settings.bangumi_manage.enable: + @staticmethod + def check_renamer() -> bool: + if settings.bangumi_manage.enable: return True else: return False - def check_analyser(self) -> bool: - if self.check_downloader() and settings.rss_parser.enable: + @staticmethod + def check_analyser() -> bool: + if settings.rss_parser.enable: return True else: return False diff --git a/src/module/core/program.py b/src/module/core/program.py index 21e37bf4..466eed90 100644 --- a/src/module/core/program.py +++ b/src/module/core/program.py @@ -31,7 +31,6 @@ class Program(RenameThread, RSSThread): "Legacy data detected, starting data migration, please wait patiently." ) data_migration() - add_rss_feed() self.start() def start(self): @@ -39,12 +38,16 @@ class Program(RenameThread, RSSThread): return {"status": "Not ready to start."} self.stop_event.clear() settings.load() - if self.enable_renamer: - self.rename_start() - if self.enable_rss: - self.rss_start() - logger.info("Program running.") - return {"status": "Program started."} + if self.downloader_status: + if self.enable_renamer: + self.rename_start() + if self.enable_rss: + add_rss_feed() + self.rss_start() + logger.info("Program running.") + return {"status": "Program started."} + else: + return {"status": "Can't connect to downloader. Program not paused."} def stop(self): if self.is_running: diff --git a/src/module/downloader/client/qb_downloader.py b/src/module/downloader/client/qb_downloader.py index b10c2386..d3563185 100644 --- a/src/module/downloader/client/qb_downloader.py +++ b/src/module/downloader/client/qb_downloader.py @@ -9,7 +9,6 @@ from qbittorrentapi.exceptions import ( ) from module.ab_decorator import qb_connect_failed_wait -from module.downloader.exceptions import ConflictError logger = logging.getLogger(__name__) @@ -27,9 +26,9 @@ class QbDownloader: self.host = host self.username = username - def auth(self): + def auth(self, retry=3): times = 0 - while times < 3: + while times < retry: try: self._client.auth_log_in() return True @@ -46,7 +45,8 @@ class QbDownloader: except APIConnectionError: logger.error(f"Cannot connect to qBittorrent Server") logger.info(f"Please check the IP and port in WebUI settings") - time.sleep(30) + time.sleep(10) + times += 1 except Exception as e: logger.error(f"Unknown error: {e}") break @@ -55,6 +55,16 @@ class QbDownloader: def logout(self): self._client.auth_log_out() + def check_host(self): + try: + self._client.app_version() + return True + except APIConnectionError: + return False + + def check_rss(self, rss_link: str): + pass + @qb_connect_failed_wait def prefs_init(self, prefs): return self._client.app_set_preferences(prefs=prefs) @@ -93,15 +103,16 @@ class QbDownloader: return False def rss_add_feed(self, url, item_path): - self._client.rss_add_feed(url, item_path) + try: + self._client.rss_add_feed(url, item_path) + except Conflict409Error: + logger.warning(f"[Downloader] RSS feed {url} already exists") def rss_remove_item(self, item_path): try: self._client.rss_remove_item(item_path) - except Conflict409Error as e: - logger.debug(e) - logger.info("Add new RSS") - raise ConflictError() + except Conflict409Error: + logger.warning(f"[Downloader] RSS item {item_path} does not exist") def rss_get_feeds(self): return self._client.rss_items() diff --git a/src/module/downloader/download_client.py b/src/module/downloader/download_client.py index a3fa6d98..37c843f2 100644 --- a/src/module/downloader/download_client.py +++ b/src/module/downloader/download_client.py @@ -33,6 +33,8 @@ class DownloadClient(TorrentPath): def __enter__(self): if not self.authed: self.auth() + else: + logger.error("[Downloader] Already authed.") return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -42,7 +44,13 @@ class DownloadClient(TorrentPath): def auth(self): self.authed = self.client.auth() - logger.debug("Authed.") + if self.authed: + logger.info("[Downloader] Authed.") + else: + logger.error("[Downloader] Auth failed.") + + def check_host(self): + return self.client.check_host() def init_downloader(self): prefs = { diff --git a/src/module/downloader/path.py b/src/module/downloader/path.py index b0e64df4..de4b9348 100644 --- a/src/module/downloader/path.py +++ b/src/module/downloader/path.py @@ -13,8 +13,8 @@ logger = logging.getLogger(__name__) class TorrentPath: - def __init__(self): - self.download_path = settings.downloader.path + def __init__(self, download_path: str = settings.downloader.path): + self.download_path = download_path @staticmethod def check_files(info): @@ -44,11 +44,11 @@ class TorrentPath: return bangumi_name, season @staticmethod - def _file_depth(path): - return len(path.split(path.sep)) + def _file_depth(file_path): + return len(file_path.split(path.sep)) - def is_ep(self, path): - return self._file_depth(path) <= 2 + def is_ep(self, file_path): + return self._file_depth(file_path) <= 2 def _gen_save_path(self, data: BangumiData): folder = ( diff --git a/src/module/manager/collector.py b/src/module/manager/collector.py index dbc3b798..1b341e3e 100644 --- a/src/module/manager/collector.py +++ b/src/module/manager/collector.py @@ -1,5 +1,7 @@ import logging +from fastapi.responses import JSONResponse + from module.downloader import DownloadClient from module.models import BangumiData from module.database import BangumiDatabase @@ -31,9 +33,9 @@ class SeasonCollector(DownloadClient): with BangumiDatabase() as db: data.added = True data.eps_collect = True + self.set_rule(data) db.insert(data) self.add_rss_feed(data.rss_link[0], item_path=data.official_title) - self.set_rule(data) def eps_complete(): diff --git a/src/module/manager/torrent.py b/src/module/manager/torrent.py index 0d698570..6d43be6f 100644 --- a/src/module/manager/torrent.py +++ b/src/module/manager/torrent.py @@ -1,4 +1,5 @@ import logging +from fastapi.responses import JSONResponse from module.downloader import DownloadClient from module.models import BangumiData @@ -20,49 +21,30 @@ class TorrentManager(BangumiDatabase): if hash_list: client.delete_torrent(hash_list) logger.info(f"Delete rule and torrents for {data.official_title}") - return { - "status": "success", - "msg": f"Delete torrents for {data.official_title}", - } + return f"Delete {data.official_title} torrents." else: - return { - "status": "error", - "msg": f"Can't find torrents for {data.official_title}", - } + return f"Can't find {data.official_title} torrents." def delete_rule(self, _id: int | str, file: bool = False): data = self.search_id(int(_id)) if isinstance(data, BangumiData): with DownloadClient() as client: client.remove_rule(data.rule_name) + client.remove_rss_feed(data.official_title) self.delete_one(int(_id)) if file: - self.delete_torrents(data, client) - return { - "status": "success", - "msg": f"Delete rule and torrents for {data.official_title}", - } + torrent_message = self.delete_torrents(data, client) + return JSONResponse(status_code=200, content={ + "msg": f"Delete {data.official_title} rule. {torrent_message}" + }) logger.info(f"[Manager] Delete rule for {data.official_title}") - return { - "status": "success", - "msg": f"Delete rule for {data.official_title}", - } + return JSONResponse(status_code=200, content={ + "msg": f"Delete rule for {data.official_title}" + }) else: - return {"status": "error", "msg": f"Can't find id {_id}"} - # data = self.search_id(int(_id)) - # if isinstance(data, BangumiData): - # self.delete_one(int(_id)) - # if file: - # self.delete_torrents(data) - # logger.info(f"Delete {data.official_title} and torrents.") - # return { - # "status": "success", - # "msg": f"Delete {data.official_title} and torrents.", - # } - # logger.info(f"Delete {data.official_title}") - # return {"status": "success", "msg": f"Delete {data.official_title}"} - # else: - # return data + return JSONResponse(status_code=406, content={ + "msg": f"Can't find id {_id}" + }) def disable_rule(self, _id: str | int, file: bool = False): data = self.search_id(int(_id)) @@ -72,18 +54,18 @@ class TorrentManager(BangumiDatabase): data.deleted = True self.update_one(data) if file: - self.delete_torrents(data, client) - return { - "status": "success", - "msg": f"Disable rule and delete torrents for {data.official_title}", - } + torrent_message = self.delete_torrents(data, client) + return JSONResponse(status_code=200, content={ + "msg": f"Disable {data.official_title} rule. {torrent_message}" + }) logger.info(f"[Manager] Disable rule for {data.official_title}") - return { - "status": "success", - "msg": f"Disable rule for {data.official_title}", - } + return JSONResponse(status_code=200, content={ + "msg": f"Disable {data.official_title} rule.", + }) else: - return {"status": "error", "msg": f"Can't find data with id {_id}"} + return JSONResponse(status_code=406, content={ + "msg": f"Can't find id {_id}" + }) def enable_rule(self, _id: str | int): data = self.search_id(int(_id)) @@ -93,18 +75,23 @@ class TorrentManager(BangumiDatabase): with DownloadClient() as client: client.set_rule(data) logger.info(f"[Manager] Enable rule for {data.official_title}") - return { - "status": "success", - "msg": f"Enable rule for {data.official_title}", - } + return JSONResponse(status_code=200, content={ + "msg": f"Enable {data.official_title} rule.", + }) + else: + return JSONResponse(status_code=406, content={ + "msg": f"Can't find bangumi id {_id}" + }) def update_rule(self, data: BangumiData): old_data = self.search_id(data.id) if not old_data: - logger.error(f"[Manager] Can't find data with id {data.id}") - return {"status": "error", "msg": f"Can't find data with id {data.id}"} + logger.error(f"[Manager] Can't find data with {data.id}") + return JSONResponse(status_code=406, content={ + "msg": f"Can't find data with {data.id}" + }) else: - # Set torrent path + # Move torrent match_list = self.__match_torrents_list(data) with DownloadClient() as client: path = client._gen_save_path(data) @@ -114,10 +101,9 @@ class TorrentManager(BangumiDatabase): client.remove_rule(data.rule_name) client.set_rule(data) self.update_one(data) - return { - "status": "success", + return JSONResponse(status_code=200, content={ "msg": f"Set new path for {data.official_title}", - } + }) def search_all_bangumi(self): datas = self.search_all() @@ -128,7 +114,7 @@ class TorrentManager(BangumiDatabase): def search_one(self, _id: int | str): data = self.search_id(int(_id)) if not data: - logger.error(f"[Manager] Can't find data with id {_id}") - return {"status": "error", "msg": f"Can't find data with id {_id}"} + logger.error(f"[Manager] Can't find data with {_id}") + return {"status": "error", "msg": f"Can't find data with {_id}"} else: return data