diff --git a/app/helper/plugin.py b/app/helper/plugin.py index 8f6132eb..ca8015b5 100644 --- a/app/helper/plugin.py +++ b/app/helper/plugin.py @@ -18,14 +18,14 @@ from app.db.systemconfig_oper import SystemConfigOper from app.log import logger from app.schemas.types import SystemConfigKey from app.utils.http import RequestUtils -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.system import SystemUtils from app.utils.url import UrlUtils PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins" -class PluginHelper(metaclass=Singleton): +class PluginHelper(metaclass=WeakSingleton): """ 插件市场管理,下载安装插件到本地 """ diff --git a/app/helper/progress.py b/app/helper/progress.py index abcb4b67..54f8d012 100644 --- a/app/helper/progress.py +++ b/app/helper/progress.py @@ -1,12 +1,11 @@ from enum import Enum -from typing import Union, Dict, Optional +from typing import Union, Optional from app.schemas.types import ProgressKey -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton -class ProgressHelper(metaclass=Singleton): - _process_detail: Dict[str, dict] = {} +class ProgressHelper(metaclass=WeakSingleton): def __init__(self): self._process_detail = {} diff --git a/app/helper/subscribe.py b/app/helper/subscribe.py index 91f8347a..682378d7 100644 --- a/app/helper/subscribe.py +++ b/app/helper/subscribe.py @@ -8,11 +8,11 @@ from app.db.systemconfig_oper import SystemConfigOper from app.log import logger from app.schemas.types import SystemConfigKey from app.utils.http import RequestUtils -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.system import SystemUtils -class SubscribeHelper(metaclass=Singleton): +class SubscribeHelper(metaclass=WeakSingleton): """ 订阅数据统计/订阅分享等 """ diff --git a/app/helper/torrent.py b/app/helper/torrent.py index 581c8215..e650e23d 100644 --- a/app/helper/torrent.py +++ b/app/helper/torrent.py @@ -1,10 +1,9 @@ import datetime import re from pathlib import Path -from typing import Tuple, Optional, List, Union, Dict +from typing import Tuple, Optional, List, Union, Dict, Any from urllib.parse import unquote -from requests import Response from torrentool.api import Torrent from app.core.config import settings @@ -16,17 +15,17 @@ from app.db.systemconfig_oper import SystemConfigOper from app.log import logger from app.schemas.types import MediaType, SystemConfigKey from app.utils.http import RequestUtils -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.string import StringUtils -class TorrentHelper(metaclass=Singleton): +class TorrentHelper(metaclass=WeakSingleton): """ 种子帮助类 """ - # 失败的种子:站点链接 - _invalid_torrents = [] + def __init__(self): + self._invalid_torrents = [] def download_torrent(self, url: str, cookie: Optional[str] = None, @@ -170,7 +169,7 @@ class TorrentHelper(metaclass=Singleton): return "", [] @staticmethod - def get_url_filename(req: Response, url: str) -> str: + def get_url_filename(req: Any, url: str) -> str: """ 从下载请求中获取种子文件名 """ diff --git a/app/modules/douban/apiv2.py b/app/modules/douban/apiv2.py index a9fcfd23..4c32e266 100644 --- a/app/modules/douban/apiv2.py +++ b/app/modules/douban/apiv2.py @@ -12,10 +12,10 @@ import requests from app.core.cache import cached from app.core.config import settings from app.utils.http import RequestUtils -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton -class DoubanApi(metaclass=Singleton): +class DoubanApi(metaclass=WeakSingleton): _urls = { # 搜索类 # sort=U:近期热门 T:标记最多 S:评分最高 R:最新上映 @@ -151,7 +151,6 @@ class DoubanApi(metaclass=Singleton): _api_key2 = "0ab215a8b1977939201640fa14c66bab" _base_url = "https://frodo.douban.com/api/v2" _api_url = "https://api.douban.com/v2" - _session = None def __init__(self): self._session = requests.Session() diff --git a/app/modules/douban/douban_cache.py b/app/modules/douban/douban_cache.py index b0d996d3..95fc7511 100644 --- a/app/modules/douban/douban_cache.py +++ b/app/modules/douban/douban_cache.py @@ -10,7 +10,7 @@ from app.core.config import settings from app.core.meta import MetaBase from app.core.metainfo import MetaInfo from app.log import logger -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.schemas.types import MediaType lock = RLock() @@ -19,7 +19,7 @@ CACHE_EXPIRE_TIMESTAMP_STR = "cache_expire_timestamp" EXPIRE_TIMESTAMP = settings.CONF.meta -class DoubanCache(metaclass=Singleton): +class DoubanCache(metaclass=WeakSingleton): """ 豆瓣缓存数据 { @@ -29,9 +29,6 @@ class DoubanCache(metaclass=Singleton): "type": MediaType } """ - _meta_data: dict = {} - # 缓存文件路径 - _meta_path: Path = None # TMDB缓存过期 _tmdb_cache_expire: bool = True @@ -233,3 +230,6 @@ class DoubanCache(metaclass=Singleton): if not cache_media_info: return self._meta_data[key]['title'] = cn_title + + def __del__(self): + self.save() diff --git a/app/modules/filemanager/storages/alipan.py b/app/modules/filemanager/storages/alipan.py index e85dd422..7bd79703 100644 --- a/app/modules/filemanager/storages/alipan.py +++ b/app/modules/filemanager/storages/alipan.py @@ -15,7 +15,7 @@ from app.core.config import settings from app.log import logger from app.modules.filemanager import StorageBase from app.schemas.types import StorageSchema -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.string import StringUtils lock = threading.Lock() @@ -29,7 +29,7 @@ class SessionInvalidException(Exception): pass -class AliPan(StorageBase, metaclass=Singleton): +class AliPan(StorageBase, metaclass=WeakSingleton): """ 阿里云盘相关操作 """ @@ -43,17 +43,12 @@ class AliPan(StorageBase, metaclass=Singleton): "copy": "复制" } - # 验证参数 - _auth_state = {} - - # 上传进度值 - _last_progress = 0 - # 基础url base_url = "https://openapi.alipan.com" def __init__(self): super().__init__() + self._auth_state = {} self.session = requests.Session() self._init_session() @@ -244,6 +239,7 @@ class AliPan(StorageBase, metaclass=Singleton): conf = self.get_conf() conf.update(result) self.set_config(conf) + return None def _request_api(self, method: str, endpoint: str, result_key: Optional[str] = None, **kwargs) -> Optional[Union[dict, list]]: @@ -369,7 +365,7 @@ class AliPan(StorageBase, metaclass=Singleton): break next_marker = resp.get("next_marker") for item in resp.get("items", []): - items.append(self.__get_fileitem(item, parent=fileitem.path)) + items.append(self.__get_fileitem(item, parent=str(fileitem.path))) if len(resp.get("items")) < 100: break return items diff --git a/app/modules/filemanager/storages/alist.py b/app/modules/filemanager/storages/alist.py index 53972338..37ec9e4d 100644 --- a/app/modules/filemanager/storages/alist.py +++ b/app/modules/filemanager/storages/alist.py @@ -12,11 +12,11 @@ from app.log import logger from app.modules.filemanager.storages import StorageBase from app.schemas.types import StorageSchema from app.utils.http import RequestUtils -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.url import UrlUtils -class Alist(StorageBase, metaclass=Singleton): +class Alist(StorageBase, metaclass=WeakSingleton): """ Alist相关操作 api文档:https://oplist.org/zh/ @@ -38,7 +38,7 @@ class Alist(StorageBase, metaclass=Singleton): """ 初始化 """ - self.__generate_token.clear_cache() + self.__generate_token.clear_cache() # noqa @property def __get_base_url(self) -> str: diff --git a/app/modules/filemanager/storages/smb.py b/app/modules/filemanager/storages/smb.py index a5212398..f6e0e955 100644 --- a/app/modules/filemanager/storages/smb.py +++ b/app/modules/filemanager/storages/smb.py @@ -12,17 +12,19 @@ from app.core.config import settings from app.log import logger from app.modules.filemanager import StorageBase from app.schemas.types import StorageSchema -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton lock = threading.Lock() class SMBConnectionError(Exception): - """SMB 连接错误""" + """ + SMB 连接错误 + """ pass -class SMB(StorageBase, metaclass=Singleton): +class SMB(StorageBase, metaclass=WeakSingleton): """ SMB网络挂载存储相关操作 - 使用 smbclient 高级接口 """ diff --git a/app/modules/filemanager/storages/u115.py b/app/modules/filemanager/storages/u115.py index ced7e8df..81bd3569 100644 --- a/app/modules/filemanager/storages/u115.py +++ b/app/modules/filemanager/storages/u115.py @@ -18,7 +18,7 @@ from app.core.config import settings from app.log import logger from app.modules.filemanager import StorageBase from app.schemas.types import StorageSchema -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.utils.string import StringUtils lock = threading.Lock() @@ -28,7 +28,7 @@ class NoCheckInException(Exception): pass -class U115Pan(StorageBase, metaclass=Singleton): +class U115Pan(StorageBase, metaclass=WeakSingleton): """ 115相关操作 """ @@ -41,18 +41,12 @@ class U115Pan(StorageBase, metaclass=Singleton): "move": "移动", "copy": "复制" } - - # 验证参数 - _auth_state = {} - - # 上传进度值 - _last_progress = 0 - # 基础url base_url = "https://proapi.115.com" def __init__(self): super().__init__() + self._auth_state = {} self.session = requests.Session() self._init_session() @@ -492,7 +486,8 @@ class U115Pan(StorageBase, metaclass=Singleton): type="file" if info_resp["file_category"] == "1" else "dir", name=info_resp["file_name"], basename=Path(info_resp["file_name"]).stem, - extension=Path(info_resp["file_name"]).suffix[1:] if info_resp["file_category"] == "1" else None, + extension=Path(info_resp["file_name"]).suffix[1:] if info_resp[ + "file_category"] == "1" else None, pickcode=info_resp["pick_code"], size=StringUtils.num_filesize(info_resp['size']) if info_resp["file_category"] == "1" else None, modify_time=info_resp["utime"] diff --git a/app/modules/themoviedb/category.py b/app/modules/themoviedb/category.py index 56dd844c..a1066e4b 100644 --- a/app/modules/themoviedb/category.py +++ b/app/modules/themoviedb/category.py @@ -7,19 +7,19 @@ from ruamel.yaml import CommentedMap from app.core.config import settings from app.log import logger -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton -class CategoryHelper(metaclass=Singleton): +class CategoryHelper(metaclass=WeakSingleton): """ 二级分类 """ - _categorys = {} - _movie_categorys = {} - _tv_categorys = {} def __init__(self): self._category_path: Path = settings.CONFIG_PATH / "category.yaml" + self._categorys = {} + self._movie_categorys = {} + self._tv_categorys = {} self.init() def init(self): @@ -69,7 +69,7 @@ class CategoryHelper(metaclass=Singleton): """ if not self._movie_categorys: return [] - return self._movie_categorys.keys() + return list(self._movie_categorys.keys()) @property def tv_categorys(self) -> list: @@ -78,7 +78,7 @@ class CategoryHelper(metaclass=Singleton): """ if not self._tv_categorys: return [] - return self._tv_categorys.keys() + return list(self._tv_categorys.keys()) def get_movie_category(self, tmdb_info) -> str: """ @@ -127,7 +127,7 @@ class CategoryHelper(metaclass=Singleton): continue elif attr == "production_countries": # 制片国家 - info_values = [str(val.get("iso_3166_1")).upper() for val in info_value] + info_values = [str(val.get("iso_3166_1")).upper() for val in info_value] # type: ignore else: if isinstance(info_value, list): info_values = [str(val).upper() for val in info_value] diff --git a/app/modules/themoviedb/tmdb_cache.py b/app/modules/themoviedb/tmdb_cache.py index 52cc2988..61b7c4e4 100644 --- a/app/modules/themoviedb/tmdb_cache.py +++ b/app/modules/themoviedb/tmdb_cache.py @@ -9,7 +9,7 @@ from typing import Optional from app.core.config import settings from app.core.meta import MetaBase from app.log import logger -from app.utils.singleton import Singleton +from app.utils.singleton import WeakSingleton from app.schemas.types import MediaType lock = RLock() @@ -18,7 +18,7 @@ CACHE_EXPIRE_TIMESTAMP_STR = "cache_expire_timestamp" EXPIRE_TIMESTAMP = settings.CONF.meta -class TmdbCache(metaclass=Singleton): +class TmdbCache(metaclass=WeakSingleton): """ TMDB缓存数据 { @@ -28,9 +28,6 @@ class TmdbCache(metaclass=Singleton): "type": MediaType } """ - _meta_data: dict = {} - # 缓存文件路径 - _meta_path: Path = None # TMDB缓存过期 _tmdb_cache_expire: bool = True @@ -218,3 +215,6 @@ class TmdbCache(metaclass=Singleton): if not cache_media_info: return self._meta_data[key]['title'] = cn_title + + def __del__(self): + self.save() diff --git a/app/utils/singleton.py b/app/utils/singleton.py index 3cb20076..15503ab3 100644 --- a/app/utils/singleton.py +++ b/app/utils/singleton.py @@ -1,4 +1,6 @@ import abc +import threading +import weakref class Singleton(abc.ABCMeta, type): @@ -40,3 +42,17 @@ class AbstractSingletonClass(abc.ABC, metaclass=SingletonClass): 抽像类单例模式(按类) """ pass + + +class WeakSingleton(abc.ABCMeta, type): + """ + 弱引用单例模式 - 当没有强引用时自动清理 + """ + _instances: weakref.WeakKeyDictionary = weakref.WeakKeyDictionary() + _lock = threading.RLock() + + def __call__(cls, *args, **kwargs): + with cls._lock: + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls]