mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-03-20 11:57:46 +08:00
- Use per-task stop events instead of shared stop_event to prevent stopping one task from killing all others - Track running state via _tasks_started flag instead of stop_event - Add error handling in RSS, rename, scan, and calendar loops - Make restart() resilient to stop failures (catch and continue) - Cache downloader status check with 60s TTL - Fix _startup_done set before start() completes (race condition) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
177 lines
6.0 KiB
Python
177 lines
6.0 KiB
Python
import asyncio
|
|
import logging
|
|
|
|
from module.conf import VERSION, settings
|
|
from module.models import ResponseModel
|
|
from module.update import (
|
|
cache_image,
|
|
data_migration,
|
|
first_run,
|
|
from_30_to_31,
|
|
from_31_to_32,
|
|
run_migrations,
|
|
start_up,
|
|
)
|
|
|
|
from .sub_thread import CalendarRefreshThread, OffsetScanThread, RenameThread, RSSThread
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
figlet = r"""
|
|
_ ____ _
|
|
/\ | | | _ \ (_)
|
|
/ \ _ _| |_ ___ | |_) | __ _ _ __ __ _ _ _ _ __ ___ _
|
|
/ /\ \| | | | __/ _ \| _ < / _` | '_ \ / _` | | | | '_ ` _ \| |
|
|
/ ____ \ |_| | || (_) | |_) | (_| | | | | (_| | |_| | | | | | | |
|
|
/_/ \_\__,_|\__\___/|____/ \__,_|_| |_|\__, |\__,_|_| |_| |_|_|
|
|
__/ |
|
|
|___/
|
|
"""
|
|
|
|
|
|
class Program(RenameThread, RSSThread, OffsetScanThread, CalendarRefreshThread):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._startup_done = False
|
|
|
|
@staticmethod
|
|
def __start_info():
|
|
for line in figlet.splitlines():
|
|
logger.info(line.strip("\n"))
|
|
logger.info(
|
|
f"Version {VERSION} Author: EstrellaXD Twitter: https://twitter.com/Estrella_Pan"
|
|
)
|
|
logger.info("GitHub: https://github.com/EstrellaXD/Auto_Bangumi/")
|
|
logger.info("Starting AutoBangumi...")
|
|
|
|
async def startup(self):
|
|
# Prevent duplicate startup due to nested router lifespan events
|
|
if self._startup_done:
|
|
return
|
|
self.__start_info()
|
|
if not self.database:
|
|
first_run()
|
|
logger.info("[Core] No db file exists, create database file.")
|
|
return {"status": "First run detected."}
|
|
if self.legacy_data:
|
|
logger.info(
|
|
"[Core] Legacy data detected, starting data migration, please wait patiently."
|
|
)
|
|
data_migration()
|
|
else:
|
|
need_update, last_minor = self.version_update
|
|
if need_update:
|
|
if last_minor is not None and last_minor == 0:
|
|
await from_30_to_31()
|
|
logger.info("[Core] Database migrated from 3.0 to 3.1.")
|
|
await from_31_to_32()
|
|
logger.info("[Core] Database updated.")
|
|
else:
|
|
# Always check schema version and run pending migrations,
|
|
# in case a previous migration was interrupted or failed.
|
|
run_migrations()
|
|
if not self.img_cache:
|
|
logger.info("[Core] No image cache exists, create image cache.")
|
|
await cache_image()
|
|
await self.start()
|
|
self._startup_done = True
|
|
|
|
async def start(self):
|
|
settings.load()
|
|
max_retries = 10
|
|
retry_count = 0
|
|
while not await self.check_downloader_status():
|
|
retry_count += 1
|
|
logger.warning(
|
|
f"Downloader is not running. (attempt {retry_count}/{max_retries})"
|
|
)
|
|
if retry_count >= max_retries:
|
|
logger.error(
|
|
"Failed to connect to downloader after maximum retries. "
|
|
"Please check downloader settings and network/proxy configuration. "
|
|
"Program will continue but download functions will not work."
|
|
)
|
|
break
|
|
logger.info("Waiting for downloader to start...")
|
|
await asyncio.sleep(30)
|
|
if self.enable_renamer:
|
|
self.rename_start()
|
|
if self.enable_rss:
|
|
self.rss_start()
|
|
# Start offset scanner for background mismatch detection
|
|
self.scan_start()
|
|
# Start calendar refresh (every 24 hours)
|
|
self.calendar_start()
|
|
self._tasks_started = True
|
|
logger.info("Program running.")
|
|
return ResponseModel(
|
|
status=True,
|
|
status_code=200,
|
|
msg_en="Program started.",
|
|
msg_zh="程序启动成功。",
|
|
)
|
|
|
|
async def stop(self):
|
|
if self.is_running:
|
|
await self.rename_stop()
|
|
await self.rss_stop()
|
|
await self.scan_stop()
|
|
await self.calendar_stop()
|
|
self._tasks_started = False
|
|
return ResponseModel(
|
|
status=True,
|
|
status_code=200,
|
|
msg_en="Program stopped.",
|
|
msg_zh="程序停止成功。",
|
|
)
|
|
else:
|
|
return ResponseModel(
|
|
status=False,
|
|
status_code=406,
|
|
msg_en="Program is not running.",
|
|
msg_zh="程序未运行。",
|
|
)
|
|
|
|
async def restart(self):
|
|
stop_ok = True
|
|
try:
|
|
await self.stop()
|
|
except Exception as e:
|
|
logger.warning(f"[Core] Error during stop in restart: {e}")
|
|
stop_ok = False
|
|
start_ok = True
|
|
try:
|
|
await self.start()
|
|
except Exception as e:
|
|
logger.error(f"[Core] Error during start in restart: {e}")
|
|
start_ok = False
|
|
if start_ok and stop_ok:
|
|
return ResponseModel(
|
|
status=True,
|
|
status_code=200,
|
|
msg_en="Program restarted.",
|
|
msg_zh="程序重启成功。",
|
|
)
|
|
elif start_ok:
|
|
return ResponseModel(
|
|
status=True,
|
|
status_code=200,
|
|
msg_en="Program restarted (stop had warnings).",
|
|
msg_zh="程序重启成功(停止时有警告)。",
|
|
)
|
|
else:
|
|
return ResponseModel(
|
|
status=False,
|
|
status_code=500,
|
|
msg_en="Program failed to restart.",
|
|
msg_zh="程序重启失败。",
|
|
)
|
|
|
|
def update_database(self):
|
|
need_update, _ = self.version_update
|
|
if not need_update:
|
|
return {"status": "No update found."}
|
|
else:
|
|
start_up()
|
|
return {"status": "Database updated."}
|