fix:优化单例模式和类引用

This commit is contained in:
jxxghp
2025-06-05 13:22:16 +08:00
parent 504827b7e5
commit a9300faaf8
25 changed files with 511 additions and 552 deletions
+104 -113
View File
@@ -24,36 +24,20 @@ from app.db.models.subscribe import Subscribe
from app.db.site_oper import SiteOper
from app.db.subscribe_oper import SubscribeOper
from app.db.systemconfig_oper import SystemConfigOper
from app.helper.message import MessageHelper
from app.helper.subscribe import SubscribeHelper
from app.helper.torrent import TorrentHelper
from app.log import logger
from app.schemas import MediaRecognizeConvertEventData
from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType, ChainEventType, \
ContentType
from app.utils.singleton import Singleton
class SubscribeChain(ChainBase, metaclass=Singleton):
class SubscribeChain(ChainBase):
"""
订阅管理处理链
"""
def __init__(self):
super().__init__()
self._rlock = threading.RLock()
self.downloadchain = DownloadChain()
self.downloadhis = DownloadHistoryOper()
self.searchchain = SearchChain()
self.subscribeoper = SubscribeOper()
self.subscribehelper = SubscribeHelper()
self.torrentschain = TorrentsChain()
self.mediachain = MediaChain()
self.tmdbchain = TmdbChain()
self.message = MessageHelper()
self.systemconfig = SystemConfigOper()
self.torrenthelper = TorrentHelper()
self.siteoper = SiteOper()
_rlock = threading.RLock()
def add(self, title: str, year: str,
mtype: MediaType = None,
@@ -87,11 +71,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
if event and event.event_data:
event_data: MediaRecognizeConvertEventData = event.event_data
if event_data.media_dict:
mediachain = MediaChain()
new_id = event_data.media_dict.get("id")
if event_data.convert_type == "themoviedb":
return self.mediachain.recognize_media(meta=_meta, tmdbid=new_id)
return mediachain.recognize_media(meta=_meta, tmdbid=new_id)
elif event_data.convert_type == "douban":
return self.mediachain.recognize_media(meta=_meta, doubanid=new_id)
return mediachain.recognize_media(meta=_meta, doubanid=new_id)
return None
logger.info(f'开始添加订阅,标题:{title} ...')
@@ -111,7 +96,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
if not tmdbid:
if doubanid:
# 将豆瓣信息转换为TMDB信息
tmdbinfo = self.mediachain.get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=mtype)
tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=mtype)
if tmdbinfo:
mediainfo = MediaInfo(tmdb_info=tmdbinfo)
elif mediaid:
@@ -214,7 +199,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"filter_groups") else kwargs.get("filter_groups")
})
# 操作数据库
sid, err_msg = self.subscribeoper.add(mediainfo=mediainfo, season=season, username=username, **kwargs)
sid, err_msg = SubscribeOper().add(mediainfo=mediainfo, season=season, username=username, **kwargs)
if not sid:
logger.error(f'{mediainfo.title_year} {err_msg}')
if not exist_ok and message:
@@ -253,7 +238,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"mediainfo": mediainfo.to_dict(),
})
# 统计订阅
self.subscribehelper.sub_reg_async({
SubscribeHelper().sub_reg_async({
"name": title,
"year": year,
"type": metainfo.type.value,
@@ -271,13 +256,14 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 返回结果
return sid, ""
def exists(self, mediainfo: MediaInfo, meta: MetaBase = None):
@staticmethod
def exists(mediainfo: MediaInfo, meta: MetaBase = None):
"""
判断订阅是否已存在
"""
if self.subscribeoper.exists(tmdbid=mediainfo.tmdb_id,
doubanid=mediainfo.douban_id,
season=meta.begin_season if meta else None):
if SubscribeOper().exists(tmdbid=mediainfo.tmdb_id,
doubanid=mediainfo.douban_id,
season=meta.begin_season if meta else None):
return True
return False
@@ -291,11 +277,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"""
with self._rlock:
logger.debug(f"search lock acquired at {datetime.now()}")
subscribeoper = SubscribeOper()
if sid:
subscribe = self.subscribeoper.get(sid)
subscribe = subscribeoper.get(sid)
subscribes = [subscribe] if subscribe else []
else:
subscribes = self.subscribeoper.list(self.get_states_for_search(state))
subscribes = subscribeoper.list(self.get_states_for_search(state))
# 遍历订阅
for subscribe in subscribes:
if global_vars.is_system_stopped:
@@ -350,20 +337,20 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 优先级过滤规则
if subscribe.best_version:
rule_groups = subscribe.filter_groups \
or self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups) or []
or SystemConfigOper().get(SystemConfigKey.BestVersionFilterRuleGroups) or []
else:
rule_groups = subscribe.filter_groups \
or self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups) or []
or SystemConfigOper().get(SystemConfigKey.SubscribeFilterRuleGroups) or []
# 搜索,同时电视剧会过滤掉不需要的剧集
contexts = self.searchchain.process(mediainfo=mediainfo,
keyword=subscribe.keyword,
no_exists=no_exists,
sites=sites,
rule_groups=rule_groups,
area="imdbid" if subscribe.search_imdbid else "title",
custom_words=custom_word_list,
filter_params=self.get_params(subscribe))
contexts = SearchChain().process(mediainfo=mediainfo,
keyword=subscribe.keyword,
no_exists=no_exists,
sites=sites,
rule_groups=rule_groups,
area="imdbid" if subscribe.search_imdbid else "title",
custom_words=custom_word_list,
filter_params=self.get_params(subscribe))
if not contexts:
logger.warn(f'订阅 {subscribe.keyword or subscribe.name} 未搜索到资源')
self.finish_subscribe_or_not(subscribe=subscribe, meta=meta,
@@ -404,7 +391,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
continue
# 自动下载
downloads, lefts = self.downloadchain.batch_download(
downloads, lefts = DownloadChain().batch_download(
contexts=matched_contexts,
no_exists=no_exists,
userid=subscribe.username,
@@ -415,7 +402,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
)
# 同步外部修改,更新订阅信息
subscribe = self.subscribeoper.get(subscribe.id)
subscribe = subscribeoper.get(subscribe.id)
# 判断是否应完成订阅
if subscribe:
@@ -424,17 +411,17 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
finally:
# 如果状态为N则更新为R
if subscribe and subscribe.state == 'N':
self.subscribeoper.update(subscribe.id, {'state': 'R'})
subscribeoper.update(subscribe.id, {'state': 'R'})
# 手动触发时发送系统消息
if manual:
if subscribes:
if sid:
self.message.put(f'{subscribes[0].name} 搜索完成!', title="订阅搜索", role="system")
self.messagehelper.put(f'{subscribes[0].name} 搜索完成!', title="订阅搜索", role="system")
else:
self.message.put('所有订阅搜索完成!', title="订阅搜索", role="system")
self.messagehelper.put('所有订阅搜索完成!', title="订阅搜索", role="system")
else:
self.message.put('没有找到订阅!', title="订阅搜索", role="system")
self.messagehelper.put('没有找到订阅!', title="订阅搜索", role="system")
logger.debug(f"search Lock released at {datetime.now()}")
def update_subscribe_priority(self, subscribe: Subscribe, meta: MetaBase,
@@ -449,7 +436,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 当前下载资源的优先级
priority = max([item.torrent_info.pri_order for item in downloads])
# 订阅存在待定策略,不管是否已完成,均需更新订阅信息
self.subscribeoper.update(subscribe.id, {
SubscribeOper().update(subscribe.id, {
"current_priority": priority,
"last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
@@ -506,17 +493,18 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
if sites is None:
return
self.match(
self.torrentschain.refresh(sites=sites)
TorrentsChain().refresh(sites=sites)
)
def get_sub_sites(self, subscribe: Subscribe) -> List[int]:
@staticmethod
def get_sub_sites(subscribe: Subscribe) -> List[int]:
"""
获取订阅中涉及的站点清单
:param subscribe: 订阅信息对象
:return: 涉及的站点清单
"""
# 从系统配置获取默认订阅站点
default_sites = self.systemconfig.get(SystemConfigKey.RssSites) or []
default_sites = SystemConfigOper().get(SystemConfigKey.RssSites) or []
# 如果订阅未指定站点,直接返回默认站点
if not subscribe.sites:
return default_sites
@@ -536,7 +524,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
:return: 返回[]代表所有站点命中,返回None代表没有订阅
"""
# 查询所有订阅
subscribes = self.subscribeoper.list(self.get_states_for_search('R'))
subscribes = SubscribeOper().list(self.get_states_for_search('R'))
if not subscribes:
return None
ret_sites = []
@@ -561,7 +549,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
with self._rlock:
logger.debug(f"match lock acquired at {datetime.now()}")
# 所有订阅
subscribes = self.subscribeoper.list(self.get_states_for_search('R'))
subscribes = SubscribeOper().list(self.get_states_for_search('R'))
# 预识别所有未识别的种子
processed_torrents: Dict[str, List[Context]] = {}
@@ -598,7 +586,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 订阅的站点域名列表
domains = []
if subscribe.sites:
domains = self.siteoper.get_domains_by_ids(subscribe.sites)
domains = SiteOper().get_domains_by_ids(subscribe.sites)
# 识别媒体信息
mediainfo: MediaInfo = self.recognize_media(meta=meta, mtype=meta.type,
tmdbid=subscribe.tmdbid,
@@ -628,6 +616,9 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 遍历预识别后的种子
_match_context = []
torrenthelper = TorrentHelper()
systemconfig = SystemConfigOper()
wordsmatcher = WordsMatcher()
for domain, contexts in processed_torrents.items():
if global_vars.is_system_stopped:
break
@@ -650,8 +641,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 有自定义识别词时,需要判断是否需要重新识别
if custom_words_list:
# 使用org_string,应用一次后理论上不能再次应用
_, apply_words = WordsMatcher().prepare(torrent_meta.org_string,
custom_words=custom_words_list)
_, apply_words = wordsmatcher.prepare(torrent_meta.org_string,
custom_words=custom_words_list)
if apply_words:
logger.info(
f'{torrent_info.site_name} - {torrent_info.title} 因订阅存在自定义识别词,重新识别元数据...')
@@ -673,9 +664,9 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
if not torrent_mediainfo or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
logger.info(
f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,尝试通过标题匹配...')
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
torrent_meta=torrent_meta,
torrent=torrent_info):
if torrenthelper.match_torrent(mediainfo=mediainfo,
torrent_meta=torrent_meta,
torrent=torrent_info):
# 匹配成功
logger.info(
f'{mediainfo.title_year} 通过标题匹配到可选资源:{torrent_info.site_name} - {torrent_info.title}')
@@ -739,17 +730,17 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
continue
# 匹配订阅附加参数
if not self.torrenthelper.filter_torrent(torrent_info=torrent_info,
filter_params=self.get_params(subscribe)):
if not torrenthelper.filter_torrent(torrent_info=torrent_info,
filter_params=self.get_params(subscribe)):
continue
# 优先级过滤规则
if subscribe.best_version:
rule_groups = subscribe.filter_groups \
or self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
or systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
else:
rule_groups = subscribe.filter_groups \
or self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
or systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
result: List[TorrentInfo] = self.filter_torrents(
rule_groups=rule_groups,
torrent_list=[torrent_info],
@@ -789,33 +780,22 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 开始批量择优下载
logger.info(f'{mediainfo.title_year} 匹配完成,共匹配到{len(_match_context)}个资源')
downloads, lefts = self.downloadchain.batch_download(contexts=_match_context,
no_exists=no_exists,
userid=subscribe.username,
username=subscribe.username,
save_path=subscribe.save_path,
downloader=subscribe.downloader,
source=self.get_subscribe_source_keyword(subscribe)
)
downloads, lefts = DownloadChain().batch_download(contexts=_match_context,
no_exists=no_exists,
userid=subscribe.username,
username=subscribe.username,
save_path=subscribe.save_path,
downloader=subscribe.downloader,
source=self.get_subscribe_source_keyword(subscribe)
)
# 同步外部修改,更新订阅信息
subscribe = self.subscribeoper.get(subscribe.id)
subscribe = SubscribeOper().get(subscribe.id)
# 判断是否要完成订阅
if subscribe:
self.finish_subscribe_or_not(subscribe=subscribe, meta=meta, mediainfo=mediainfo,
downloads=downloads, lefts=lefts)
# 清理内存
_match_context.clear()
del _match_context
downloads.clear()
del downloads
lefts.clear()
del lefts
# 清理内存
processed_torrents.clear()
del processed_torrents
logger.debug(f"match Lock released at {datetime.now()}")
@@ -824,7 +804,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
定时检查订阅,更新订阅信息
"""
# 查询所有订阅
subscribes = self.subscribeoper.list()
subscribeoper = SubscribeOper()
subscribes = subscribeoper.list()
if not subscribes:
# 没有订阅不运行
return
@@ -863,7 +844,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
total_episode = subscribe.total_episode
lack_episode = subscribe.lack_episode
# 更新TMDB信息
self.subscribeoper.update(subscribe.id, {
subscribeoper.update(subscribe.id, {
"name": mediainfo.title,
"year": mediainfo.year,
"vote": mediainfo.vote_average,
@@ -877,28 +858,30 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
})
logger.info(f'{subscribe.name} 订阅元数据更新完成')
def follow(self):
@staticmethod
def follow():
"""
刷新follow的用户分享,并自动添加订阅
"""
follow_users: List[str] = self.systemconfig.get(SystemConfigKey.FollowSubscribers)
follow_users: List[str] = SystemConfigOper().get(SystemConfigKey.FollowSubscribers)
if not follow_users:
return
share_subs = self.subscribehelper.get_shares()
share_subs = SubscribeHelper().get_shares()
logger.info(f'开始刷新follow用户分享订阅 ...')
success_count = 0
subscribeoper = SubscribeOper()
for share_sub in share_subs:
uid = share_sub.get("share_uid")
if uid and uid in follow_users:
# 订阅已存在则跳过
if self.subscribeoper.exists(tmdbid=share_sub.get("tmdbid"),
doubanid=share_sub.get("doubanid"),
season=share_sub.get("season")):
if subscribeoper.exists(tmdbid=share_sub.get("tmdbid"),
doubanid=share_sub.get("doubanid"),
season=share_sub.get("season")):
continue
# 已经订阅过跳过
if self.subscribeoper.exist_history(tmdbid=share_sub.get("tmdbid"),
doubanid=share_sub.get("doubanid"),
season=share_sub.get("season")):
if subscribeoper.exist_history(tmdbid=share_sub.get("tmdbid"),
doubanid=share_sub.get("doubanid"),
season=share_sub.get("season")):
continue
# 去除无效属性
for key in list(share_sub.keys()):
@@ -939,7 +922,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
logger.error(f'follow用户分享订阅 {title} 添加失败:{message}')
logger.info(f'follow用户分享订阅刷新完成,共添加 {success_count} 个订阅')
def __update_subscribe_note(self, subscribe: Subscribe, downloads: Optional[List[Context]]):
@staticmethod
def __update_subscribe_note(subscribe: Subscribe, downloads: Optional[List[Context]]):
"""
更新已下载信息到note字段
"""
@@ -971,7 +955,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
note = list(set(note).union(set(items)))
# 更新订阅
if note:
self.subscribeoper.update(subscribe.id, {
SubscribeOper().update(subscribe.id, {
"note": note
})
@@ -995,7 +979,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
return note
return []
def __update_lack_episodes(self, lefts: Dict[Union[int, str], Dict[int, schemas.NotExistMediaInfo]],
@staticmethod
def __update_lack_episodes(lefts: Dict[Union[int, str], Dict[int, schemas.NotExistMediaInfo]],
subscribe: Subscribe,
mediainfo: MediaInfo,
update_date: Optional[bool] = False):
@@ -1028,7 +1013,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
update_data["lack_episode"] = lack_episode
# 更新数据库
if update_data:
self.subscribeoper.update(subscribe.id, update_data)
SubscribeOper().update(subscribe.id, update_data)
def __finish_subscribe(self, subscribe: Subscribe, mediainfo: MediaInfo, meta: MetaBase):
"""
@@ -1041,9 +1026,10 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
msgstr = "订阅" if not subscribe.best_version else "洗版"
logger.info(f'{mediainfo.title_year} 完成{msgstr}')
# 新增订阅历史
self.subscribeoper.add_history(**subscribe.to_dict())
subscribeoper = SubscribeOper()
subscribeoper.add_history(**subscribe.to_dict())
# 删除订阅
self.subscribeoper.delete(subscribe.id)
subscribeoper.delete(subscribe.id)
# 发送通知
if mediainfo.type == MediaType.TV:
link = settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
@@ -1070,7 +1056,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"mediainfo": mediainfo.to_dict(),
})
# 统计订阅
self.subscribehelper.sub_done_async({
SubscribeHelper().sub_done_async({
"tmdbid": mediainfo.tmdb_id,
"doubanid": mediainfo.douban_id
})
@@ -1080,7 +1066,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"""
查询订阅并发送消息
"""
subscribes = self.subscribeoper.list()
subscribes = SubscribeOper().list()
if not subscribes:
self.post_message(schemas.Notification(channel=channel,
source=source,
@@ -1114,20 +1100,22 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"[id]为订阅编号", userid=userid))
return
arg_strs = str(arg_str).split()
subscribeoper = SubscribeOper()
subscribehelper = SubscribeHelper()
for arg_str in arg_strs:
arg_str = arg_str.strip()
if not arg_str.isdigit():
continue
subscribe_id = int(arg_str)
subscribe = self.subscribeoper.get(subscribe_id)
subscribe = subscribeoper.get(subscribe_id)
if not subscribe:
self.post_message(schemas.Notification(channel=channel, source=source,
title=f"订阅编号 {subscribe_id} 不存在!", userid=userid))
return
# 删除订阅
self.subscribeoper.delete(subscribe_id)
subscribeoper.delete(subscribe_id)
# 统计订阅
self.subscribehelper.sub_done_async({
subscribehelper.sub_done_async({
"tmdbid": subscribe.tmdbid,
"doubanid": subscribe.doubanid
})
@@ -1253,13 +1241,14 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
site_id = event_data.get("site_id")
if not site_id:
return
subscribeoper = SubscribeOper()
if site_id == "*":
# 站点被重置
SystemConfigOper().set(SystemConfigKey.RssSites, [])
for subscribe in self.subscribeoper.list():
for subscribe in subscribeoper.list():
if not subscribe.sites:
continue
self.subscribeoper.update(subscribe.id, {
subscribeoper.update(subscribe.id, {
"sites": []
})
return
@@ -1269,14 +1258,14 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
selected_sites.remove(site_id)
SystemConfigOper().set(SystemConfigKey.RssSites, selected_sites)
# 查询所有订阅
for subscribe in self.subscribeoper.list():
for subscribe in subscribeoper.list():
if not subscribe.sites:
continue
sites = subscribe.sites or []
if site_id not in sites:
continue
sites.remove(site_id)
self.subscribeoper.update(subscribe.id, {
subscribeoper.update(subscribe.id, {
"sites": sites
})
@@ -1301,12 +1290,13 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
return None
return value.get(default_config_key) or None
def get_params(self, subscribe: Subscribe):
@staticmethod
def get_params(subscribe: Subscribe):
"""
获取订阅默认参数
"""
# 默认过滤规则
default_rule = self.systemconfig.get(SystemConfigKey.SubscribeDefaultParams) or {}
default_rule = SystemConfigOper().get(SystemConfigKey.SubscribeDefaultParams) or {}
return {
key: value for key, value in {
"include": subscribe.include or default_rule.get("include"),
@@ -1334,7 +1324,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
episodes: Dict[int, schemas.SubscribeEpisodeInfo] = {}
if subscribe.tmdbid and subscribe.type == MediaType.TV.value:
# 查询TMDB中的集信息
tmdb_episodes = self.tmdbchain.tmdb_episodes(
tmdb_episodes = TmdbChain().tmdb_episodes(
tmdbid=subscribe.tmdbid,
season=subscribe.season,
episode_group=subscribe.episode_group
@@ -1359,11 +1349,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
episodes[0] = info
# 所有下载记录
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
downloadhis = DownloadHistoryOper()
download_his = downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
if download_his:
for his in download_his:
# 查询下载文件
files = self.downloadhis.get_files_by_hash(his.download_hash)
files = downloadhis.get_files_by_hash(his.download_hash)
if files:
for file in files:
# 识别文件名
@@ -1457,7 +1448,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
subscribe.season: subscribe.total_episode
}
# 查询媒体库缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(
exist_flag, no_exists = DownloadChain().get_no_exists_info(
meta=meta,
mediainfo=mediainfo,
totals=totals