feat:弱引用单例

This commit is contained in:
jxxghp
2025-07-08 21:29:01 +08:00
parent 6156b9a481
commit 378777dc7c
13 changed files with 67 additions and 61 deletions
+2 -2
View File
@@ -18,14 +18,14 @@ from app.db.systemconfig_oper import SystemConfigOper
from app.log import logger from app.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
from app.utils.http import RequestUtils 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.system import SystemUtils
from app.utils.url import UrlUtils from app.utils.url import UrlUtils
PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins" PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins"
class PluginHelper(metaclass=Singleton): class PluginHelper(metaclass=WeakSingleton):
""" """
插件市场管理,下载安装插件到本地 插件市场管理,下载安装插件到本地
""" """
+3 -4
View File
@@ -1,12 +1,11 @@
from enum import Enum from enum import Enum
from typing import Union, Dict, Optional from typing import Union, Optional
from app.schemas.types import ProgressKey from app.schemas.types import ProgressKey
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
class ProgressHelper(metaclass=Singleton): class ProgressHelper(metaclass=WeakSingleton):
_process_detail: Dict[str, dict] = {}
def __init__(self): def __init__(self):
self._process_detail = {} self._process_detail = {}
+2 -2
View File
@@ -8,11 +8,11 @@ from app.db.systemconfig_oper import SystemConfigOper
from app.log import logger from app.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
from app.utils.http import RequestUtils 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.system import SystemUtils
class SubscribeHelper(metaclass=Singleton): class SubscribeHelper(metaclass=WeakSingleton):
""" """
订阅数据统计/订阅分享等 订阅数据统计/订阅分享等
""" """
+6 -7
View File
@@ -1,10 +1,9 @@
import datetime import datetime
import re import re
from pathlib import Path 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 urllib.parse import unquote
from requests import Response
from torrentool.api import Torrent from torrentool.api import Torrent
from app.core.config import settings from app.core.config import settings
@@ -16,17 +15,17 @@ from app.db.systemconfig_oper import SystemConfigOper
from app.log import logger from app.log import logger
from app.schemas.types import MediaType, SystemConfigKey from app.schemas.types import MediaType, SystemConfigKey
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.utils.string import StringUtils from app.utils.string import StringUtils
class TorrentHelper(metaclass=Singleton): class TorrentHelper(metaclass=WeakSingleton):
""" """
种子帮助类 种子帮助类
""" """
# 失败的种子:站点链接 def __init__(self):
_invalid_torrents = [] self._invalid_torrents = []
def download_torrent(self, url: str, def download_torrent(self, url: str,
cookie: Optional[str] = None, cookie: Optional[str] = None,
@@ -170,7 +169,7 @@ class TorrentHelper(metaclass=Singleton):
return "", [] return "", []
@staticmethod @staticmethod
def get_url_filename(req: Response, url: str) -> str: def get_url_filename(req: Any, url: str) -> str:
""" """
从下载请求中获取种子文件名 从下载请求中获取种子文件名
""" """
+2 -3
View File
@@ -12,10 +12,10 @@ import requests
from app.core.cache import cached from app.core.cache import cached
from app.core.config import settings from app.core.config import settings
from app.utils.http import RequestUtils 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 = { _urls = {
# 搜索类 # 搜索类
# sort=U:近期热门 T:标记最多 S:评分最高 R:最新上映 # sort=U:近期热门 T:标记最多 S:评分最高 R:最新上映
@@ -151,7 +151,6 @@ class DoubanApi(metaclass=Singleton):
_api_key2 = "0ab215a8b1977939201640fa14c66bab" _api_key2 = "0ab215a8b1977939201640fa14c66bab"
_base_url = "https://frodo.douban.com/api/v2" _base_url = "https://frodo.douban.com/api/v2"
_api_url = "https://api.douban.com/v2" _api_url = "https://api.douban.com/v2"
_session = None
def __init__(self): def __init__(self):
self._session = requests.Session() self._session = requests.Session()
+5 -5
View File
@@ -10,7 +10,7 @@ from app.core.config import settings
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfo
from app.log import logger from app.log import logger
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.schemas.types import MediaType from app.schemas.types import MediaType
lock = RLock() lock = RLock()
@@ -19,7 +19,7 @@ CACHE_EXPIRE_TIMESTAMP_STR = "cache_expire_timestamp"
EXPIRE_TIMESTAMP = settings.CONF.meta EXPIRE_TIMESTAMP = settings.CONF.meta
class DoubanCache(metaclass=Singleton): class DoubanCache(metaclass=WeakSingleton):
""" """
豆瓣缓存数据 豆瓣缓存数据
{ {
@@ -29,9 +29,6 @@ class DoubanCache(metaclass=Singleton):
"type": MediaType "type": MediaType
} }
""" """
_meta_data: dict = {}
# 缓存文件路径
_meta_path: Path = None
# TMDB缓存过期 # TMDB缓存过期
_tmdb_cache_expire: bool = True _tmdb_cache_expire: bool = True
@@ -233,3 +230,6 @@ class DoubanCache(metaclass=Singleton):
if not cache_media_info: if not cache_media_info:
return return
self._meta_data[key]['title'] = cn_title self._meta_data[key]['title'] = cn_title
def __del__(self):
self.save()
+5 -9
View File
@@ -15,7 +15,7 @@ from app.core.config import settings
from app.log import logger from app.log import logger
from app.modules.filemanager import StorageBase from app.modules.filemanager import StorageBase
from app.schemas.types import StorageSchema from app.schemas.types import StorageSchema
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.utils.string import StringUtils from app.utils.string import StringUtils
lock = threading.Lock() lock = threading.Lock()
@@ -29,7 +29,7 @@ class SessionInvalidException(Exception):
pass pass
class AliPan(StorageBase, metaclass=Singleton): class AliPan(StorageBase, metaclass=WeakSingleton):
""" """
阿里云盘相关操作 阿里云盘相关操作
""" """
@@ -43,17 +43,12 @@ class AliPan(StorageBase, metaclass=Singleton):
"copy": "复制" "copy": "复制"
} }
# 验证参数
_auth_state = {}
# 上传进度值
_last_progress = 0
# 基础url # 基础url
base_url = "https://openapi.alipan.com" base_url = "https://openapi.alipan.com"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._auth_state = {}
self.session = requests.Session() self.session = requests.Session()
self._init_session() self._init_session()
@@ -244,6 +239,7 @@ class AliPan(StorageBase, metaclass=Singleton):
conf = self.get_conf() conf = self.get_conf()
conf.update(result) conf.update(result)
self.set_config(conf) self.set_config(conf)
return None
def _request_api(self, method: str, endpoint: str, def _request_api(self, method: str, endpoint: str,
result_key: Optional[str] = None, **kwargs) -> Optional[Union[dict, list]]: result_key: Optional[str] = None, **kwargs) -> Optional[Union[dict, list]]:
@@ -369,7 +365,7 @@ class AliPan(StorageBase, metaclass=Singleton):
break break
next_marker = resp.get("next_marker") next_marker = resp.get("next_marker")
for item in resp.get("items", []): 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: if len(resp.get("items")) < 100:
break break
return items return items
+3 -3
View File
@@ -12,11 +12,11 @@ from app.log import logger
from app.modules.filemanager.storages import StorageBase from app.modules.filemanager.storages import StorageBase
from app.schemas.types import StorageSchema from app.schemas.types import StorageSchema
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.utils.url import UrlUtils from app.utils.url import UrlUtils
class Alist(StorageBase, metaclass=Singleton): class Alist(StorageBase, metaclass=WeakSingleton):
""" """
Alist相关操作 Alist相关操作
api文档:https://oplist.org/zh/ 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 @property
def __get_base_url(self) -> str: def __get_base_url(self) -> str:
+5 -3
View File
@@ -12,17 +12,19 @@ from app.core.config import settings
from app.log import logger from app.log import logger
from app.modules.filemanager import StorageBase from app.modules.filemanager import StorageBase
from app.schemas.types import StorageSchema from app.schemas.types import StorageSchema
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
lock = threading.Lock() lock = threading.Lock()
class SMBConnectionError(Exception): class SMBConnectionError(Exception):
"""SMB 连接错误""" """
SMB 连接错误
"""
pass pass
class SMB(StorageBase, metaclass=Singleton): class SMB(StorageBase, metaclass=WeakSingleton):
""" """
SMB网络挂载存储相关操作 - 使用 smbclient 高级接口 SMB网络挂载存储相关操作 - 使用 smbclient 高级接口
""" """
+5 -10
View File
@@ -18,7 +18,7 @@ from app.core.config import settings
from app.log import logger from app.log import logger
from app.modules.filemanager import StorageBase from app.modules.filemanager import StorageBase
from app.schemas.types import StorageSchema from app.schemas.types import StorageSchema
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.utils.string import StringUtils from app.utils.string import StringUtils
lock = threading.Lock() lock = threading.Lock()
@@ -28,7 +28,7 @@ class NoCheckInException(Exception):
pass pass
class U115Pan(StorageBase, metaclass=Singleton): class U115Pan(StorageBase, metaclass=WeakSingleton):
""" """
115相关操作 115相关操作
""" """
@@ -41,18 +41,12 @@ class U115Pan(StorageBase, metaclass=Singleton):
"move": "移动", "move": "移动",
"copy": "复制" "copy": "复制"
} }
# 验证参数
_auth_state = {}
# 上传进度值
_last_progress = 0
# 基础url # 基础url
base_url = "https://proapi.115.com" base_url = "https://proapi.115.com"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._auth_state = {}
self.session = requests.Session() self.session = requests.Session()
self._init_session() self._init_session()
@@ -492,7 +486,8 @@ class U115Pan(StorageBase, metaclass=Singleton):
type="file" if info_resp["file_category"] == "1" else "dir", type="file" if info_resp["file_category"] == "1" else "dir",
name=info_resp["file_name"], name=info_resp["file_name"],
basename=Path(info_resp["file_name"]).stem, 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"], pickcode=info_resp["pick_code"],
size=StringUtils.num_filesize(info_resp['size']) if info_resp["file_category"] == "1" else None, size=StringUtils.num_filesize(info_resp['size']) if info_resp["file_category"] == "1" else None,
modify_time=info_resp["utime"] modify_time=info_resp["utime"]
+8 -8
View File
@@ -7,19 +7,19 @@ from ruamel.yaml import CommentedMap
from app.core.config import settings from app.core.config import settings
from app.log import logger 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): def __init__(self):
self._category_path: Path = settings.CONFIG_PATH / "category.yaml" self._category_path: Path = settings.CONFIG_PATH / "category.yaml"
self._categorys = {}
self._movie_categorys = {}
self._tv_categorys = {}
self.init() self.init()
def init(self): def init(self):
@@ -69,7 +69,7 @@ class CategoryHelper(metaclass=Singleton):
""" """
if not self._movie_categorys: if not self._movie_categorys:
return [] return []
return self._movie_categorys.keys() return list(self._movie_categorys.keys())
@property @property
def tv_categorys(self) -> list: def tv_categorys(self) -> list:
@@ -78,7 +78,7 @@ class CategoryHelper(metaclass=Singleton):
""" """
if not self._tv_categorys: if not self._tv_categorys:
return [] return []
return self._tv_categorys.keys() return list(self._tv_categorys.keys())
def get_movie_category(self, tmdb_info) -> str: def get_movie_category(self, tmdb_info) -> str:
""" """
@@ -127,7 +127,7 @@ class CategoryHelper(metaclass=Singleton):
continue continue
elif attr == "production_countries": 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: else:
if isinstance(info_value, list): if isinstance(info_value, list):
info_values = [str(val).upper() for val in info_value] info_values = [str(val).upper() for val in info_value]
+5 -5
View File
@@ -9,7 +9,7 @@ from typing import Optional
from app.core.config import settings from app.core.config import settings
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.log import logger from app.log import logger
from app.utils.singleton import Singleton from app.utils.singleton import WeakSingleton
from app.schemas.types import MediaType from app.schemas.types import MediaType
lock = RLock() lock = RLock()
@@ -18,7 +18,7 @@ CACHE_EXPIRE_TIMESTAMP_STR = "cache_expire_timestamp"
EXPIRE_TIMESTAMP = settings.CONF.meta EXPIRE_TIMESTAMP = settings.CONF.meta
class TmdbCache(metaclass=Singleton): class TmdbCache(metaclass=WeakSingleton):
""" """
TMDB缓存数据 TMDB缓存数据
{ {
@@ -28,9 +28,6 @@ class TmdbCache(metaclass=Singleton):
"type": MediaType "type": MediaType
} }
""" """
_meta_data: dict = {}
# 缓存文件路径
_meta_path: Path = None
# TMDB缓存过期 # TMDB缓存过期
_tmdb_cache_expire: bool = True _tmdb_cache_expire: bool = True
@@ -218,3 +215,6 @@ class TmdbCache(metaclass=Singleton):
if not cache_media_info: if not cache_media_info:
return return
self._meta_data[key]['title'] = cn_title self._meta_data[key]['title'] = cn_title
def __del__(self):
self.save()
+16
View File
@@ -1,4 +1,6 @@
import abc import abc
import threading
import weakref
class Singleton(abc.ABCMeta, type): class Singleton(abc.ABCMeta, type):
@@ -40,3 +42,17 @@ class AbstractSingletonClass(abc.ABC, metaclass=SingletonClass):
抽像类单例模式(按类) 抽像类单例模式(按类)
""" """
pass 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]