feat: 没测过的MAA调度方案
This commit is contained in:
@@ -20,19 +20,25 @@
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
import uuid
|
||||
from fastapi import APIRouter, WebSocket, Body, Path
|
||||
|
||||
from app.core import Config
|
||||
from app.core import Config, TaskManager
|
||||
from app.models.schema import *
|
||||
|
||||
router = APIRouter(prefix="/api/dispatch", tags=["任务调度"])
|
||||
|
||||
|
||||
@router.post("/add", summary="添加任务", response_model=OutBase, status_code=200)
|
||||
async def add_plan(plan: DispatchIn = Body(...)) -> OutBase:
|
||||
@router.post(
|
||||
"/start", summary="添加任务", response_model=TaskCreateOut, status_code=200
|
||||
)
|
||||
async def add_plan(task: TaskCreateIn = Body(...)) -> TaskCreateOut:
|
||||
|
||||
uid, config = await Config.add_plan(plan.type)
|
||||
return OutBase(code=200, status="success", message="任务添加成功")
|
||||
try:
|
||||
task_id = await TaskManager.add_task(task.mode, task.taskId)
|
||||
except Exception as e:
|
||||
return TaskCreateOut(code=500, status="error", message=str(e), taskId="")
|
||||
return TaskCreateOut(taskId=str(task_id))
|
||||
|
||||
|
||||
@router.post("/stop", summary="中止任务", response_model=OutBase, status_code=200)
|
||||
@@ -45,13 +51,18 @@ async def stop_plan(plan: DispatchIn = Body(...)) -> OutBase:
|
||||
return OutBase()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/order", summary="重新排序计划表", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def reorder_plan(plan: PlanReorderIn = Body(...)) -> OutBase:
|
||||
|
||||
@router.websocket("/ws/{taskId}")
|
||||
async def websocket_endpoint(
|
||||
websocket: WebSocket, taskId: str = Path(..., description="要连接的任务ID")
|
||||
):
|
||||
try:
|
||||
await Config.reorder_plan(plan.indexList)
|
||||
except Exception as e:
|
||||
return OutBase(code=500, status="error", message=str(e))
|
||||
return OutBase()
|
||||
uid = uuid.UUID(taskId)
|
||||
except ValueError:
|
||||
await websocket.close(code=1008, reason="无效的任务ID")
|
||||
return
|
||||
|
||||
if uid in TaskManager.connection_events:
|
||||
TaskManager.websocket_dict[uid] = websocket
|
||||
TaskManager.connection_events[uid].set()
|
||||
else:
|
||||
await websocket.close(code=1008, reason="任务不存在或已结束")
|
||||
|
||||
@@ -23,7 +23,15 @@ __version__ = "5.0.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .config import Config
|
||||
from .config import Config, MaaConfig, GeneralConfig, MaaUserConfig
|
||||
from .timer import MainTimer
|
||||
from .task_manager import TaskManager
|
||||
|
||||
__all__ = ["Config", "MainTimer"]
|
||||
__all__ = [
|
||||
"Config",
|
||||
"MaaConfig",
|
||||
"GeneralConfig",
|
||||
"MainTimer",
|
||||
"TaskManager",
|
||||
"MaaUserConfig",
|
||||
]
|
||||
|
||||
@@ -20,20 +20,18 @@
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import sys
|
||||
import shutil
|
||||
import re
|
||||
import base64
|
||||
import shutil
|
||||
import asyncio
|
||||
import requests
|
||||
import truststore
|
||||
import calendar
|
||||
from datetime import datetime, timedelta, date
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
from typing import Union, Dict, List, Literal, Optional, Any, Tuple, Callable, TypeVar
|
||||
from typing import Dict, List, Literal, Optional, Any
|
||||
|
||||
from app.utils import get_logger
|
||||
from app.models.ConfigBase import *
|
||||
@@ -546,7 +544,7 @@ class GeneralConfig(ConfigBase):
|
||||
|
||||
class AppConfig(GlobalConfig):
|
||||
|
||||
VERSION = "4.5.0.1"
|
||||
VERSION = "5.0.0.1"
|
||||
|
||||
CLASS_BOOK = {
|
||||
"MAA": MaaConfig,
|
||||
@@ -580,15 +578,11 @@ class AppConfig(GlobalConfig):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(if_save_multi_config=False)
|
||||
|
||||
self.root_path = Path.cwd()
|
||||
self.log_path = Path.cwd() / "debug/app.log"
|
||||
self.database_path = Path.cwd() / "data/data.db"
|
||||
self.config_path = Path.cwd() / "config"
|
||||
self.key_path = Path.cwd() / "data/key"
|
||||
|
||||
self.log_path = self.root_path / "debug/app.log"
|
||||
self.database_path = self.root_path / "data/data.db"
|
||||
self.config_path = self.root_path / "config"
|
||||
self.key_path = self.root_path / "data/key"
|
||||
|
||||
# self.PASSWORD = ""
|
||||
self.running_list = []
|
||||
self.silence_dict: Dict[Path, datetime] = {}
|
||||
self.power_sign = "NoAction"
|
||||
self.if_ignore_silence = False
|
||||
@@ -597,7 +591,7 @@ class AppConfig(GlobalConfig):
|
||||
logger.info("===================================")
|
||||
logger.info("AUTO_MAA 后端应用程序")
|
||||
logger.info(f"版本号: v{self.VERSION}")
|
||||
logger.info(f"根目录: {self.root_path}")
|
||||
logger.info(f"工作目录: {Path.cwd()}")
|
||||
logger.info("===================================")
|
||||
|
||||
# 检查目录
|
||||
@@ -618,6 +612,10 @@ class AppConfig(GlobalConfig):
|
||||
await self.PlanConfig.connect(self.config_path / "PlanConfig.json")
|
||||
await self.QueueConfig.connect(self.config_path / "QueueConfig.json")
|
||||
|
||||
from .task_manager import TaskManager
|
||||
|
||||
self.task_dict = TaskManager.task_dict
|
||||
|
||||
# self.check_data()
|
||||
logger.info("程序初始化完成")
|
||||
|
||||
@@ -653,6 +651,11 @@ class AppConfig(GlobalConfig):
|
||||
|
||||
uid = uuid.UUID(script_id)
|
||||
|
||||
if uid in self.task_dict:
|
||||
raise RuntimeError(
|
||||
f"Cannot update script {script_id} while tasks are running."
|
||||
)
|
||||
|
||||
for group, items in data.items():
|
||||
for name, value in items.items():
|
||||
logger.debug(f"更新脚本配置:{script_id} - {group}.{name} = {value}")
|
||||
@@ -665,7 +668,14 @@ class AppConfig(GlobalConfig):
|
||||
|
||||
logger.info(f"删除脚本配置:{script_id}")
|
||||
|
||||
await self.ScriptConfig.remove(uuid.UUID(script_id))
|
||||
uid = uuid.UUID(script_id)
|
||||
|
||||
if uid in self.task_dict:
|
||||
raise RuntimeError(
|
||||
f"Cannot delete script {script_id} while tasks are running."
|
||||
)
|
||||
|
||||
await self.ScriptConfig.remove(uid)
|
||||
|
||||
async def reorder_script(self, index_list: list[str]) -> None:
|
||||
"""重新排序脚本"""
|
||||
@@ -1058,5 +1068,343 @@ class AppConfig(GlobalConfig):
|
||||
"Cannot connect to the notice server. Please check your network connection or try again later."
|
||||
)
|
||||
|
||||
async def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool:
|
||||
"""
|
||||
保存MAA日志并生成对应统计数据
|
||||
|
||||
:param log_path: 日志文件保存路径
|
||||
:type log_path: Path
|
||||
:param logs: 日志内容列表
|
||||
:type logs: list
|
||||
:param maa_result: MAA 结果
|
||||
:type maa_result: str
|
||||
:return: 是否包含6★招募
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
logger.info(f"开始处理 MAA 日志,日志长度: {len(logs)},日志标记:{maa_result}")
|
||||
|
||||
data = {
|
||||
"recruit_statistics": defaultdict(int),
|
||||
"drop_statistics": defaultdict(dict),
|
||||
"maa_result": maa_result,
|
||||
}
|
||||
|
||||
if_six_star = False
|
||||
|
||||
# 公招统计(仅统计招募到的)
|
||||
confirmed_recruit = False
|
||||
current_star_level = None
|
||||
i = 0
|
||||
while i < len(logs):
|
||||
if "公招识别结果:" in logs[i]:
|
||||
current_star_level = None # 每次识别公招时清空之前的星级
|
||||
i += 1
|
||||
while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
|
||||
i += 1
|
||||
|
||||
if i < len(logs) and "Tags" in logs[i]: # 识别星级
|
||||
star_match = re.search(r"(\d+)\s*★ Tags", logs[i])
|
||||
if star_match:
|
||||
current_star_level = f"{star_match.group(1)}★"
|
||||
if current_star_level == "6★":
|
||||
if_six_star = True
|
||||
|
||||
if "已确认招募" in logs[i]: # 只有确认招募后才统计
|
||||
confirmed_recruit = True
|
||||
|
||||
if confirmed_recruit and current_star_level:
|
||||
data["recruit_statistics"][current_star_level] += 1
|
||||
confirmed_recruit = False # 重置,等待下一次公招
|
||||
current_star_level = None # 清空已处理的星级
|
||||
|
||||
i += 1
|
||||
|
||||
# 掉落统计
|
||||
# 存储所有关卡的掉落统计
|
||||
all_stage_drops = {}
|
||||
|
||||
# 查找所有Fight任务的开始和结束位置
|
||||
fight_tasks = []
|
||||
for i, line in enumerate(logs):
|
||||
if "开始任务: Fight" in line or "开始任务: 刷理智" in line:
|
||||
# 查找对应的任务结束位置
|
||||
end_index = -1
|
||||
for j in range(i + 1, len(logs)):
|
||||
if "完成任务: Fight" in logs[j] or "完成任务: 刷理智" in logs[j]:
|
||||
end_index = j
|
||||
break
|
||||
# 如果遇到新的Fight任务开始,则当前任务没有正常结束
|
||||
if j < len(logs) and (
|
||||
"开始任务: Fight" in logs[j] or "开始任务: 刷理智" in logs[j]
|
||||
):
|
||||
break
|
||||
|
||||
# 如果找到了结束位置,记录这个任务的范围
|
||||
if end_index != -1:
|
||||
fight_tasks.append((i, end_index))
|
||||
|
||||
# 处理每个Fight任务
|
||||
for start_idx, end_idx in fight_tasks:
|
||||
# 提取当前任务的日志
|
||||
task_logs = logs[start_idx : end_idx + 1]
|
||||
|
||||
# 查找任务中的最后一次掉落统计
|
||||
last_drop_stats = {}
|
||||
current_stage = None
|
||||
|
||||
for line in task_logs:
|
||||
# 匹配掉落统计行,如"1-7 掉落统计:"
|
||||
drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line)
|
||||
if drop_match:
|
||||
# 发现新的掉落统计,重置当前关卡的掉落数据
|
||||
current_stage = drop_match.group(1)
|
||||
last_drop_stats = {}
|
||||
continue
|
||||
|
||||
# 如果已经找到了关卡,处理掉落物
|
||||
if current_stage:
|
||||
item_match: List[str] = re.findall(
|
||||
r"^(?!\[)(\S+?)\s*:\s*([\d,]+)(?:\s*\(\+[\d,]+\))?",
|
||||
line,
|
||||
re.M,
|
||||
)
|
||||
for item, total in item_match:
|
||||
# 解析数值时去掉逗号 (如 2,160 -> 2160)
|
||||
total = int(total.replace(",", ""))
|
||||
|
||||
# 黑名单
|
||||
if item not in [
|
||||
"当前次数",
|
||||
"理智",
|
||||
"最快截图耗时",
|
||||
"专精等级",
|
||||
"剩余时间",
|
||||
]:
|
||||
last_drop_stats[item] = total
|
||||
|
||||
# 如果任务中有掉落统计,更新总统计
|
||||
if current_stage and last_drop_stats:
|
||||
if current_stage not in all_stage_drops:
|
||||
all_stage_drops[current_stage] = {}
|
||||
|
||||
# 累加掉落数据
|
||||
for item, count in last_drop_stats.items():
|
||||
all_stage_drops[current_stage].setdefault(item, 0)
|
||||
all_stage_drops[current_stage][item] += count
|
||||
|
||||
# 将累加后的掉落数据保存到结果中
|
||||
data["drop_statistics"] = all_stage_drops
|
||||
|
||||
# 保存日志
|
||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with log_path.open("w", encoding="utf-8") as f:
|
||||
f.writelines(logs)
|
||||
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
logger.success(
|
||||
f"MAA 日志统计完成,日志路径:{log_path}",
|
||||
)
|
||||
|
||||
return if_six_star
|
||||
|
||||
async def save_general_log(
|
||||
self, log_path: Path, logs: list, general_result: str
|
||||
) -> None:
|
||||
"""
|
||||
保存通用日志并生成对应统计数据
|
||||
|
||||
:param log_path: 日志文件保存路径
|
||||
:param logs: 日志内容列表
|
||||
:param general_result: 待保存的日志结果信息
|
||||
"""
|
||||
|
||||
logger.info(
|
||||
f"开始处理通用日志,日志长度: {len(logs)},日志标记:{general_result}"
|
||||
)
|
||||
|
||||
data: Dict[str, str] = {"general_result": general_result}
|
||||
|
||||
# 保存日志
|
||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with log_path.with_suffix(".log").open("w", encoding="utf-8") as f:
|
||||
f.writelines(logs)
|
||||
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
logger.success(f"通用日志统计完成,日志路径:{log_path.with_suffix('.log')}")
|
||||
|
||||
def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict:
|
||||
"""
|
||||
合并指定数据统计信息文件
|
||||
|
||||
:param statistic_path_list: 需要合并的统计信息文件路径列表
|
||||
:return: 合并后的统计信息字典
|
||||
"""
|
||||
|
||||
logger.info(f"开始合并统计信息文件,共计 {len(statistic_path_list)} 个文件")
|
||||
|
||||
data: Dict[str, Any] = {"index": {}}
|
||||
|
||||
for json_file in statistic_path_list:
|
||||
|
||||
with json_file.open("r", encoding="utf-8") as f:
|
||||
single_data = json.load(f)
|
||||
|
||||
for key in single_data.keys():
|
||||
|
||||
if key not in data:
|
||||
data[key] = {}
|
||||
|
||||
# 合并公招统计
|
||||
if key == "recruit_statistics":
|
||||
|
||||
for star_level, count in single_data[key].items():
|
||||
if star_level not in data[key]:
|
||||
data[key][star_level] = 0
|
||||
data[key][star_level] += count
|
||||
|
||||
# 合并掉落统计
|
||||
elif key == "drop_statistics":
|
||||
|
||||
for stage, drops in single_data[key].items():
|
||||
if stage not in data[key]:
|
||||
data[key][stage] = {} # 初始化关卡
|
||||
|
||||
for item, count in drops.items():
|
||||
|
||||
if item not in data[key][stage]:
|
||||
data[key][stage][item] = 0
|
||||
data[key][stage][item] += count
|
||||
|
||||
# 录入运行结果
|
||||
elif key in ["maa_result", "general_result"]:
|
||||
|
||||
actual_date = datetime.strptime(
|
||||
f"{json_file.parent.parent.name} {json_file.stem}",
|
||||
"%Y-%m-%d %H-%M-%S",
|
||||
) + timedelta(
|
||||
days=(
|
||||
1
|
||||
if datetime.strptime(json_file.stem, "%H-%M-%S").time()
|
||||
< datetime.min.time().replace(hour=4)
|
||||
else 0
|
||||
)
|
||||
)
|
||||
|
||||
if single_data[key] != "Success!":
|
||||
if "error_info" not in data:
|
||||
data["error_info"] = {}
|
||||
data["error_info"][actual_date.strftime("%d日 %H:%M:%S")] = (
|
||||
single_data[key]
|
||||
)
|
||||
|
||||
data["index"][actual_date] = [
|
||||
actual_date.strftime("%d日 %H:%M:%S"),
|
||||
("完成" if single_data[key] == "Success!" else "异常"),
|
||||
json_file,
|
||||
]
|
||||
|
||||
data["index"] = [data["index"][_] for _ in sorted(data["index"])]
|
||||
|
||||
logger.success(
|
||||
f"统计信息合并完成,共计 {len(data['index'])} 条记录",
|
||||
)
|
||||
|
||||
return {k: v for k, v in data.items() if v}
|
||||
|
||||
def search_history(
|
||||
self, mode: str, start_date: datetime, end_date: datetime
|
||||
) -> dict:
|
||||
"""
|
||||
搜索指定范围内的历史记录
|
||||
|
||||
:param mode: 合并模式(按日合并、按周合并、按月合并)
|
||||
:param start_date: 开始日期
|
||||
:param end_date: 结束日期
|
||||
:return: 搜索到的历史记录字典
|
||||
"""
|
||||
|
||||
logger.info(
|
||||
f"开始搜索历史记录,合并模式:{mode},日期范围:{start_date} 至 {end_date}"
|
||||
)
|
||||
|
||||
history_dict = {}
|
||||
|
||||
for date_folder in (Path.cwd() / "history").iterdir():
|
||||
if not date_folder.is_dir():
|
||||
continue # 只处理日期文件夹
|
||||
|
||||
try:
|
||||
|
||||
date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||
|
||||
if not (start_date <= date <= end_date):
|
||||
continue # 只统计在范围内的日期
|
||||
|
||||
if mode == "按日合并":
|
||||
date_name = date.strftime("%Y年 %m月 %d日")
|
||||
elif mode == "按周合并":
|
||||
year, week, _ = date.isocalendar()
|
||||
date_name = f"{year}年 第{week}周"
|
||||
elif mode == "按月合并":
|
||||
date_name = date.strftime("%Y年 %m月")
|
||||
|
||||
if date_name not in history_dict:
|
||||
history_dict[date_name] = {}
|
||||
|
||||
for user_folder in date_folder.iterdir():
|
||||
if not user_folder.is_dir():
|
||||
continue # 只处理用户文件夹
|
||||
|
||||
if user_folder.stem not in history_dict[date_name]:
|
||||
history_dict[date_name][user_folder.stem] = list(
|
||||
user_folder.with_suffix("").glob("*.json")
|
||||
)
|
||||
else:
|
||||
history_dict[date_name][user_folder.stem] += list(
|
||||
user_folder.with_suffix("").glob("*.json")
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
logger.warning(f"非日期格式的目录: {date_folder}")
|
||||
|
||||
logger.success(f"历史记录搜索完成,共计 {len(history_dict)} 条记录")
|
||||
|
||||
return {
|
||||
k: v
|
||||
for k, v in sorted(history_dict.items(), key=lambda x: x[0], reverse=True)
|
||||
}
|
||||
|
||||
def clean_old_history(self):
|
||||
"""删除超过用户设定天数的历史记录文件(基于目录日期)"""
|
||||
|
||||
if self.get("Function", "HistoryRetentionTime") == 0:
|
||||
logger.info("历史记录永久保留,跳过历史记录清理")
|
||||
return
|
||||
|
||||
logger.info("开始清理超过设定天数的历史记录")
|
||||
|
||||
deleted_count = 0
|
||||
|
||||
for date_folder in (Path.cwd() / "history").iterdir():
|
||||
if not date_folder.is_dir():
|
||||
continue # 只处理日期文件夹
|
||||
|
||||
try:
|
||||
# 只检查 `YYYY-MM-DD` 格式的文件夹
|
||||
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||
if datetime.now() - folder_date > timedelta(
|
||||
days=self.get("Function", "HistoryRetentionTime")
|
||||
):
|
||||
shutil.rmtree(date_folder, ignore_errors=True)
|
||||
deleted_count += 1
|
||||
logger.info(f"已删除超期日志目录: {date_folder}")
|
||||
except ValueError:
|
||||
logger.warning(f"非日期格式的目录: {date_folder}")
|
||||
|
||||
logger.success(f"清理完成: {deleted_count} 个日期目录")
|
||||
|
||||
|
||||
Config = AppConfig()
|
||||
|
||||
@@ -20,242 +20,30 @@
|
||||
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from packaging import version
|
||||
from typing import Dict, Union
|
||||
import asyncio
|
||||
from fastapi import WebSocket
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .config import Config, MaaConfig, GeneralConfig
|
||||
from utils import get_logger
|
||||
from task import *
|
||||
from .config import Config, MaaConfig, GeneralConfig, QueueConfig
|
||||
from app.models.schema import TaskMessage
|
||||
from app.utils import get_logger
|
||||
from app.task import *
|
||||
|
||||
|
||||
logger = get_logger("业务调度")
|
||||
|
||||
|
||||
class Task:
|
||||
"""业务线程"""
|
||||
|
||||
check_maa_version = Signal(str)
|
||||
push_info_bar = Signal(str, str, str, int)
|
||||
play_sound = Signal(str)
|
||||
question = Signal(str, str)
|
||||
question_response = Signal(bool)
|
||||
update_maa_user_info = Signal(str, dict)
|
||||
update_general_sub_info = Signal(str, dict)
|
||||
create_task_list = Signal(list)
|
||||
create_user_list = Signal(list)
|
||||
update_task_list = Signal(list)
|
||||
update_user_list = Signal(list)
|
||||
update_log_text = Signal(str)
|
||||
accomplish = Signal(list)
|
||||
|
||||
def __init__(
|
||||
self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]]
|
||||
):
|
||||
super(Task, self).__init__()
|
||||
|
||||
self.setObjectName(f"Task-{mode}-{name}")
|
||||
|
||||
self.mode = mode
|
||||
self.name = name
|
||||
self.info = info
|
||||
|
||||
self.logs = []
|
||||
|
||||
self.question_response.connect(lambda: print("response"))
|
||||
|
||||
@logger.catch
|
||||
def run(self):
|
||||
|
||||
if "设置MAA" in self.mode:
|
||||
|
||||
logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}")
|
||||
self.push_info_bar.emit("info", "设置MAA", self.name, 3000)
|
||||
|
||||
self.task = MaaManager(
|
||||
self.mode,
|
||||
Config.script_dict[self.name],
|
||||
(None if "全局" in self.mode else self.info["SetMaaInfo"]["Path"]),
|
||||
)
|
||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||
|
||||
try:
|
||||
self.task.run()
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}"
|
||||
)
|
||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
||||
|
||||
elif self.mode == "设置通用脚本":
|
||||
|
||||
logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}")
|
||||
self.push_info_bar.emit("info", "设置通用脚本", self.name, 3000)
|
||||
|
||||
self.task = GeneralManager(
|
||||
self.mode,
|
||||
Config.script_dict[self.name],
|
||||
self.info["SetSubInfo"]["Path"],
|
||||
)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||
|
||||
try:
|
||||
self.task.run()
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}"
|
||||
)
|
||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
||||
|
||||
else:
|
||||
|
||||
logger.info(f"任务开始:{self.name}", module=f"业务 {self.name}")
|
||||
self.task_list = [
|
||||
[
|
||||
(
|
||||
value
|
||||
if Config.script_dict[value]["Config"].get_name() == ""
|
||||
else f"{value} - {Config.script_dict[value]["Config"].get_name()}"
|
||||
),
|
||||
"等待",
|
||||
value,
|
||||
]
|
||||
for _, value in sorted(
|
||||
self.info["Queue"].items(), key=lambda x: int(x[0][7:])
|
||||
)
|
||||
if value != "禁用"
|
||||
]
|
||||
|
||||
self.create_task_list.emit(self.task_list)
|
||||
|
||||
for task in self.task_list:
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
break
|
||||
|
||||
task[1] = "运行"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
|
||||
# 检查任务是否在运行列表中
|
||||
if task[2] in Config.running_list:
|
||||
|
||||
task[1] = "跳过"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(
|
||||
f"跳过任务:{task[0]},该任务已在运行列表中",
|
||||
module=f"业务 {self.name}",
|
||||
)
|
||||
self.push_info_bar.emit("info", "跳过任务", task[0], 3000)
|
||||
continue
|
||||
|
||||
# 标记为运行中
|
||||
Config.running_list.append(task[2])
|
||||
logger.info(f"任务开始:{task[0]}", module=f"业务 {self.name}")
|
||||
self.push_info_bar.emit("info", "任务开始", task[0], 3000)
|
||||
|
||||
if Config.script_dict[task[2]]["Type"] == "Maa":
|
||||
|
||||
self.task = MaaManager(
|
||||
self.mode[0:4],
|
||||
Config.script_dict[task[2]],
|
||||
)
|
||||
|
||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||
self.task.question.connect(self.question.emit)
|
||||
self.question_response.disconnect()
|
||||
self.question_response.connect(self.task.question_response.emit)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
||||
self.task.update_user_info.connect(self.update_maa_user_info.emit)
|
||||
self.task.accomplish.connect(
|
||||
lambda log: self.task_accomplish(task[2], log)
|
||||
)
|
||||
|
||||
elif Config.script_dict[task[2]]["Type"] == "General":
|
||||
|
||||
self.task = GeneralManager(
|
||||
self.mode[0:4],
|
||||
Config.script_dict[task[2]],
|
||||
)
|
||||
|
||||
self.task.question.connect(self.question.emit)
|
||||
self.question_response.disconnect()
|
||||
self.question_response.connect(self.task.question_response.emit)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
||||
self.task.update_sub_info.connect(self.update_general_sub_info.emit)
|
||||
self.task.accomplish.connect(
|
||||
lambda log: self.task_accomplish(task[2], log)
|
||||
)
|
||||
|
||||
try:
|
||||
self.task.run() # 运行任务业务
|
||||
|
||||
task[1] = "完成"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(f"任务完成:{task[0]}", module=f"业务 {self.name}")
|
||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
self.task_accomplish(
|
||||
task[2],
|
||||
{
|
||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"History": f"任务异常,异常简报:{e}",
|
||||
},
|
||||
)
|
||||
|
||||
task[1] = "异常"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.exception(
|
||||
f"任务异常:{task[0]},错误信息:{e}",
|
||||
module=f"业务 {self.name}",
|
||||
)
|
||||
self.push_info_bar.emit("error", "任务异常", task[0], -1)
|
||||
|
||||
# 任务结束后从运行列表中移除
|
||||
Config.running_list.remove(task[2])
|
||||
|
||||
self.accomplish.emit(self.logs)
|
||||
|
||||
def task_accomplish(self, name: str, log: dict):
|
||||
"""
|
||||
销毁任务线程并保存任务结果
|
||||
|
||||
:param name: 任务名称
|
||||
:param log: 任务日志记录
|
||||
"""
|
||||
|
||||
logger.info(
|
||||
f"任务完成:{name},日志记录:{list(log.values())}",
|
||||
module=f"业务 {self.name}",
|
||||
)
|
||||
|
||||
self.logs.append([name, log])
|
||||
self.task.deleteLater()
|
||||
|
||||
|
||||
class _TaskManager:
|
||||
"""业务调度器"""
|
||||
|
||||
def __init__(self):
|
||||
super(_TaskManager, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
self.task_dict: Dict[str, Task] = {}
|
||||
self.task_dict: Dict[uuid.UUID, asyncio.Task] = {}
|
||||
self.connection_events: Dict[uuid.UUID, asyncio.Event] = {}
|
||||
self.websocket_dict: Dict[uuid.UUID, WebSocket] = {}
|
||||
|
||||
def add_task(self, mode: str, uid: str):
|
||||
async def add_task(self, mode: str, uid: str) -> uuid.UUID:
|
||||
"""
|
||||
添加任务
|
||||
|
||||
@@ -268,6 +56,7 @@ class _TaskManager:
|
||||
if mode == "设置脚本":
|
||||
if actual_id in Config.ScriptConfig:
|
||||
task_id = actual_id
|
||||
actual_id = None
|
||||
else:
|
||||
for script_id, script in Config.ScriptConfig.items():
|
||||
if (
|
||||
@@ -280,128 +69,192 @@ class _TaskManager:
|
||||
raise ValueError(
|
||||
f"The task corresponding to UID {uid} could not be found."
|
||||
)
|
||||
elif actual_id in Config.QueueConfig or actual_id in Config.ScriptConfig:
|
||||
elif actual_id in Config.QueueConfig:
|
||||
task_id = actual_id
|
||||
actual_id = None
|
||||
elif actual_id in Config.ScriptConfig:
|
||||
task_id = uuid.uuid4()
|
||||
else:
|
||||
raise ValueError(f"The task corresponding to UID {uid} could not be found.")
|
||||
|
||||
if name in Config.running_list or name in self.task_dict:
|
||||
if task_id in self.task_dict:
|
||||
|
||||
logger.warning(f"任务已存在:{name}")
|
||||
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
|
||||
return None
|
||||
raise RuntimeError(f"The task {task_id} is already running.")
|
||||
|
||||
logger.info(f"任务开始:{name},模式:{mode}", module="业务调度")
|
||||
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
||||
SoundPlayer.play("任务开始")
|
||||
|
||||
# 标记任务为运行中
|
||||
Config.running_list.append(name)
|
||||
logger.info(f"创建任务:{task_id},模式:{mode}")
|
||||
|
||||
# 创建任务实例并连接信号
|
||||
self.task_dict[name] = Task(mode, name, info)
|
||||
self.task_dict[name].check_maa_version.connect(self.check_maa_version)
|
||||
self.task_dict[name].question.connect(
|
||||
lambda title, content: self.push_dialog(name, title, content)
|
||||
if task_id in self.connection_events:
|
||||
self.connection_events[task_id].clear()
|
||||
else:
|
||||
self.connection_events[task_id] = asyncio.Event()
|
||||
|
||||
await self.connection_events[task_id].wait()
|
||||
|
||||
if task_id not in self.websocket_dict:
|
||||
raise RuntimeError(f"The task {task_id} is not connected to a WebSocket.")
|
||||
|
||||
logger.info(f"开始运行任务:{task_id},模式:{mode}")
|
||||
|
||||
self.task_dict[task_id] = asyncio.create_task(
|
||||
self.run_task(mode, task_id, actual_id)
|
||||
)
|
||||
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||
self.task_dict[name].play_sound.connect(SoundPlayer.play)
|
||||
self.task_dict[name].update_maa_user_info.connect(Config.change_maa_user_info)
|
||||
self.task_dict[name].update_general_sub_info.connect(
|
||||
Config.change_general_sub_info
|
||||
)
|
||||
self.task_dict[name].accomplish.connect(
|
||||
lambda logs: self.remove_task(mode, name, logs)
|
||||
self.task_dict[task_id].add_done_callback(
|
||||
lambda t: asyncio.create_task(self.remove_task(t, mode, task_id))
|
||||
)
|
||||
|
||||
# 向UI发送信号以创建或连接GUI
|
||||
if "新调度台" in mode:
|
||||
self.create_gui.emit(self.task_dict[name])
|
||||
return task_id
|
||||
|
||||
elif "主调度台" in mode:
|
||||
self.connect_gui.emit(self.task_dict[name])
|
||||
@logger.catch
|
||||
async def run_task(
|
||||
self, mode: str, task_id: uuid.UUID, actual_id: Optional[uuid.UUID]
|
||||
):
|
||||
|
||||
# 启动任务线程
|
||||
self.task_dict[name].start()
|
||||
websocket = self.websocket_dict[task_id]
|
||||
|
||||
def stop_task(self, name: str) -> None:
|
||||
if mode == "设置脚本":
|
||||
|
||||
if isinstance(Config.ScriptConfig[task_id], MaaConfig):
|
||||
task_item = MaaManager(mode, task_id, actual_id, websocket)
|
||||
# elif isinstance(Config.ScriptConfig[task_id], GeneralConfig):
|
||||
# task_item = GeneralManager(mode, task_id, actual_id, websocket)
|
||||
else:
|
||||
logger.error(
|
||||
f"不支持的脚本类型:{Config.ScriptConfig[task_id].__class__.__name__}"
|
||||
)
|
||||
await websocket.send_json(
|
||||
TaskMessage(
|
||||
type="Info", data={"Error": "脚本类型不支持"}
|
||||
).model_dump()
|
||||
)
|
||||
return
|
||||
|
||||
task = asyncio.create_task(task_item.run())
|
||||
task.add_done_callback(
|
||||
lambda t: asyncio.create_task(task_item.final_task(t))
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
if task_id in Config.QueueConfig:
|
||||
|
||||
queue = Config.QueueConfig[task_id]
|
||||
if not isinstance(queue, QueueConfig):
|
||||
logger.error(
|
||||
f"不支持的队列类型:{Config.QueueConfig[task_id].__class__.__name__}"
|
||||
)
|
||||
await websocket.send_json(
|
||||
TaskMessage(
|
||||
type="Info", data={"Error": "队列类型不支持"}
|
||||
).model_dump()
|
||||
)
|
||||
return
|
||||
|
||||
task_list = []
|
||||
for queue_item in queue.QueueItem.values():
|
||||
uid = uuid.UUID(queue_item.get("Info", "ScriptId"))
|
||||
task_list.append(
|
||||
{
|
||||
"script_id": str(uid),
|
||||
"status": "等待",
|
||||
"name": Config.ScriptConfig[uid].get("Info", "Name"),
|
||||
}
|
||||
)
|
||||
|
||||
elif actual_id is not None and actual_id in Config.ScriptConfig:
|
||||
|
||||
task_list = [{"script_id": str(actual_id), "status": "等待"}]
|
||||
|
||||
for task in task_list:
|
||||
|
||||
script_id = uuid.UUID(task["script_id"])
|
||||
|
||||
# 检查任务是否在运行列表中
|
||||
if script_id in self.task_dict:
|
||||
|
||||
task["status"] = "跳过"
|
||||
await websocket.send_json(
|
||||
TaskMessage(
|
||||
type="Update", data={"task_list": task_list}
|
||||
).model_dump()
|
||||
)
|
||||
logger.info(f"跳过任务:{script_id},该任务已在运行列表中")
|
||||
continue
|
||||
|
||||
# 标记为运行中
|
||||
task["status"] = "运行"
|
||||
await websocket.send_json(
|
||||
TaskMessage(
|
||||
type="Update", data={"task_list": task_list}
|
||||
).model_dump()
|
||||
)
|
||||
logger.info(f"任务开始:{script_id}")
|
||||
|
||||
if isinstance(Config.ScriptConfig[script_id], MaaConfig):
|
||||
task_item = MaaManager(mode, script_id, None, websocket)
|
||||
# elif isinstance(Config.ScriptConfig[task_id], GeneralConfig):
|
||||
# task_item = GeneralManager(mode, task_id, actual_id, websocket)
|
||||
else:
|
||||
logger.error(
|
||||
f"不支持的脚本类型:{Config.ScriptConfig[script_id].__class__.__name__}"
|
||||
)
|
||||
await websocket.send_json(
|
||||
TaskMessage(
|
||||
type="Info", data={"Error": "脚本类型不支持"}
|
||||
).model_dump()
|
||||
)
|
||||
continue
|
||||
|
||||
task = asyncio.create_task(task_item.run())
|
||||
task.add_done_callback(
|
||||
lambda t: asyncio.create_task(task_item.final_task(t))
|
||||
)
|
||||
|
||||
async def stop_task(self, task_id: uuid.UUID) -> None:
|
||||
"""
|
||||
中止任务
|
||||
|
||||
:param name: 任务名称
|
||||
:param task_id: 任务ID
|
||||
"""
|
||||
|
||||
logger.info(f"中止任务:{name}", module="业务调度")
|
||||
MainInfoBar.push_info_bar("info", "中止任务", name, 3000)
|
||||
logger.info(f"中止任务:{task_id}")
|
||||
|
||||
if name == "ALL":
|
||||
if task_id not in self.task_dict:
|
||||
raise ValueError(f"The task {task_id} is not running.")
|
||||
|
||||
for name in self.task_dict:
|
||||
self.task_dict[task_id].cancel()
|
||||
|
||||
self.task_dict[name].task.requestInterruption()
|
||||
self.task_dict[name].requestInterruption()
|
||||
self.task_dict[name].quit()
|
||||
self.task_dict[name].wait()
|
||||
|
||||
elif name in self.task_dict:
|
||||
|
||||
self.task_dict[name].task.requestInterruption()
|
||||
self.task_dict[name].requestInterruption()
|
||||
self.task_dict[name].quit()
|
||||
self.task_dict[name].wait()
|
||||
|
||||
def remove_task(self, mode: str, name: str, logs: list) -> None:
|
||||
async def remove_task(
|
||||
self, task: asyncio.Task, mode: str, task_id: uuid.UUID
|
||||
) -> None:
|
||||
"""
|
||||
处理任务结束后的收尾工作
|
||||
|
||||
:param mode: 任务模式
|
||||
:param name: 任务名称
|
||||
:param logs: 任务日志
|
||||
Parameters
|
||||
----------
|
||||
task : asyncio.Task
|
||||
任务对象
|
||||
mode : str
|
||||
任务模式
|
||||
task_id : uuid.UUID
|
||||
任务ID
|
||||
"""
|
||||
|
||||
logger.info(f"任务结束:{name}", module="业务调度")
|
||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
||||
SoundPlayer.play("任务结束")
|
||||
logger.info(f"任务结束:{task_id}")
|
||||
|
||||
# 删除任务线程,移除运行中标记
|
||||
self.task_dict[name].deleteLater()
|
||||
self.task_dict.pop(name)
|
||||
Config.running_list.remove(name)
|
||||
# 从任务字典中移除任务
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"任务 {task_id} 已结束")
|
||||
self.task_dict.pop(task_id)
|
||||
|
||||
if "调度队列" in name and "人工排查" not in mode:
|
||||
|
||||
# 保存调度队列历史记录
|
||||
if len(logs) > 0:
|
||||
time = logs[0][1]["Time"]
|
||||
history = ""
|
||||
for log in logs:
|
||||
history += f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
||||
Config.save_history(name, {"Time": time, "History": history})
|
||||
else:
|
||||
Config.save_history(
|
||||
name,
|
||||
{
|
||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"History": "没有任务被执行",
|
||||
},
|
||||
)
|
||||
|
||||
# 根据调度队列情况设置电源状态
|
||||
if (
|
||||
Config.queue_dict[name]["Config"].get(
|
||||
Config.queue_dict[name]["Config"].QueueSet_AfterAccomplish
|
||||
)
|
||||
!= "NoAction"
|
||||
and Config.power_sign == "NoAction"
|
||||
):
|
||||
Config.set_power_sign(
|
||||
Config.queue_dict[name]["Config"].get(
|
||||
Config.queue_dict[name]["Config"].QueueSet_AfterAccomplish
|
||||
)
|
||||
)
|
||||
|
||||
if Config.args.mode == "cli" and Config.power_sign == "NoAction":
|
||||
Config.set_power_sign("KillSelf")
|
||||
websocket = self.websocket_dict.pop(task_id, None)
|
||||
if websocket:
|
||||
await websocket.send_json(
|
||||
TaskMessage(type="Signal", data={"Accomplish": "无描述"}).model_dump()
|
||||
)
|
||||
await websocket.close()
|
||||
|
||||
|
||||
TaskManager = _TaskManager()
|
||||
|
||||
@@ -193,6 +193,9 @@ class ConfigItem:
|
||||
self.name = name
|
||||
self.value: Any = default
|
||||
self.validator = validator or ConfigValidator()
|
||||
self.is_locked = False
|
||||
|
||||
self.setValue(default)
|
||||
|
||||
def setValue(self, value: Any):
|
||||
"""
|
||||
@@ -211,6 +214,11 @@ class ConfigItem:
|
||||
) == value:
|
||||
return
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError(
|
||||
f"Config item '{self.group}.{self.name}' is locked and cannot be modified."
|
||||
)
|
||||
|
||||
# deepcopy new value
|
||||
try:
|
||||
self.value = deepcopy(value)
|
||||
@@ -232,6 +240,18 @@ class ConfigItem:
|
||||
return dpapi_decrypt(self.value)
|
||||
return self.value
|
||||
|
||||
def lock(self):
|
||||
"""
|
||||
锁定配置项,锁定后无法修改配置项值
|
||||
"""
|
||||
self.is_locked = True
|
||||
|
||||
def unlock(self):
|
||||
"""
|
||||
解锁配置项,解锁后可以修改配置项值
|
||||
"""
|
||||
self.is_locked = False
|
||||
|
||||
|
||||
class ConfigBase:
|
||||
"""
|
||||
@@ -249,6 +269,7 @@ class ConfigBase:
|
||||
|
||||
self.file: None | Path = None
|
||||
self.if_save_multi_config = if_save_multi_config
|
||||
self.is_locked = False
|
||||
|
||||
async def connect(self, path: Path):
|
||||
"""
|
||||
@@ -265,6 +286,9 @@ class ConfigBase:
|
||||
"The config file must be a JSON file with '.json' extension."
|
||||
)
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError("Config is locked and cannot be modified.")
|
||||
|
||||
self.file = path
|
||||
|
||||
if not self.file.exists():
|
||||
@@ -292,6 +316,9 @@ class ConfigBase:
|
||||
配置数据字典
|
||||
"""
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError("Config is locked and cannot be modified.")
|
||||
|
||||
# update the value of config item
|
||||
if data.get("SubConfigsInfo"):
|
||||
for k, v in data["SubConfigsInfo"].items():
|
||||
@@ -395,6 +422,32 @@ class ConfigBase:
|
||||
indent=4,
|
||||
)
|
||||
|
||||
async def lock(self):
|
||||
"""
|
||||
锁定配置项,锁定后无法修改配置项值
|
||||
"""
|
||||
|
||||
self.is_locked = True
|
||||
|
||||
for name in dir(self):
|
||||
item = getattr(self, name)
|
||||
if isinstance(item, ConfigItem | MultipleConfig):
|
||||
item.lock()
|
||||
|
||||
async def unlock(self):
|
||||
"""
|
||||
解锁配置项,解锁后可以修改配置项值
|
||||
"""
|
||||
|
||||
self.is_locked = False
|
||||
|
||||
for name in dir(self):
|
||||
item = getattr(self, name)
|
||||
if isinstance(item, ConfigItem):
|
||||
item.unlock()
|
||||
elif isinstance(item, MultipleConfig):
|
||||
await item.unlock()
|
||||
|
||||
|
||||
class MultipleConfig:
|
||||
"""
|
||||
@@ -424,6 +477,7 @@ class MultipleConfig:
|
||||
self.file: None | Path = None
|
||||
self.order: List[uuid.UUID] = []
|
||||
self.data: Dict[uuid.UUID, ConfigBase] = {}
|
||||
self.is_locked = False
|
||||
|
||||
def __getitem__(self, key: uuid.UUID) -> ConfigBase:
|
||||
"""允许通过 config[uuid] 访问配置项"""
|
||||
@@ -462,6 +516,9 @@ class MultipleConfig:
|
||||
"The config file must be a JSON file with '.json' extension."
|
||||
)
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError("Config is locked and cannot be modified.")
|
||||
|
||||
self.file = path
|
||||
|
||||
if not self.file.exists():
|
||||
@@ -490,6 +547,9 @@ class MultipleConfig:
|
||||
配置数据字典
|
||||
"""
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError("Config is locked and cannot be modified.")
|
||||
|
||||
if not data.get("instances"):
|
||||
self.order = []
|
||||
self.data = {}
|
||||
@@ -614,9 +674,17 @@ class MultipleConfig:
|
||||
要移除的配置项的唯一标识符
|
||||
"""
|
||||
|
||||
if self.is_locked:
|
||||
raise ValueError("Config is locked and cannot be modified.")
|
||||
|
||||
if uid not in self.data:
|
||||
raise ValueError(f"Config item with uid {uid} does not exist.")
|
||||
|
||||
if self.data[uid].is_locked:
|
||||
raise ValueError(
|
||||
f"Config item with uid {uid} is locked and cannot be removed."
|
||||
)
|
||||
|
||||
self.data.pop(uid)
|
||||
self.order.remove(uid)
|
||||
|
||||
@@ -641,6 +709,26 @@ class MultipleConfig:
|
||||
if self.file:
|
||||
await self.save()
|
||||
|
||||
async def lock(self):
|
||||
"""
|
||||
锁定配置项,锁定后无法修改配置项值
|
||||
"""
|
||||
|
||||
self.is_locked = True
|
||||
|
||||
for item in self.values():
|
||||
await item.lock()
|
||||
|
||||
async def unlock(self):
|
||||
"""
|
||||
解锁配置项,解锁后可以修改配置项值
|
||||
"""
|
||||
|
||||
self.is_locked = False
|
||||
|
||||
for item in self.values():
|
||||
await item.unlock()
|
||||
|
||||
def keys(self):
|
||||
"""返回配置项的所有唯一标识符"""
|
||||
|
||||
|
||||
@@ -192,12 +192,24 @@ class DispatchIn(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class DispatchCreateIn(DispatchIn):
|
||||
class TaskCreateIn(DispatchIn):
|
||||
mode: Literal["自动代理", "人工排查", "设置脚本"] = Field(
|
||||
..., description="任务模式"
|
||||
)
|
||||
|
||||
|
||||
class TaskCreateOut(OutBase):
|
||||
taskId: str = Field(..., description="新创建的任务ID")
|
||||
|
||||
|
||||
class TaskMessage(BaseModel):
|
||||
type: Literal["Update", "Message", "Info", "Signal"] = Field(
|
||||
...,
|
||||
description="消息类型 Update: 更新数据, Message: 请求弹出对话框, Info: 需要在UI显示的消息, Signal: 程序信号",
|
||||
)
|
||||
data: Dict[str, Any] = Field(..., description="消息数据,具体内容根据type类型而定")
|
||||
|
||||
|
||||
class SettingGetOut(OutBase):
|
||||
data: Dict[str, Dict[str, Any]] = Field(..., description="全局设置数据")
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ class _SystemHandler:
|
||||
win32gui.EnumWindows(callback, window_info)
|
||||
return window_info
|
||||
|
||||
def kill_process(self, path: Path) -> None:
|
||||
async def kill_process(self, path: Path) -> None:
|
||||
"""
|
||||
根据路径中止进程
|
||||
|
||||
@@ -312,7 +312,7 @@ class _SystemHandler:
|
||||
|
||||
logger.info(f"开始中止进程: {path}")
|
||||
|
||||
for pid in self.search_pids(path):
|
||||
for pid in await self.search_pids(path):
|
||||
killprocess = subprocess.Popen(
|
||||
f"taskkill /F /T /PID {pid}",
|
||||
shell=True,
|
||||
@@ -322,7 +322,7 @@ class _SystemHandler:
|
||||
|
||||
logger.success(f"进程已中止: {path}")
|
||||
|
||||
def search_pids(self, path: Path) -> list:
|
||||
async def search_pids(self, path: Path) -> list:
|
||||
"""
|
||||
根据路径查找进程PID
|
||||
|
||||
|
||||
1998
app/task/MAA.py
Normal file
1998
app/task/MAA.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,5 +25,6 @@ __license__ = "GPL-3.0 license"
|
||||
|
||||
|
||||
from .skland import skland_sign_in
|
||||
from .MAA import MaaManager
|
||||
|
||||
__all__ = ["skland_sign_in"]
|
||||
__all__ = ["skland_sign_in", "MaaManager"]
|
||||
|
||||
@@ -34,8 +34,8 @@ import hashlib
|
||||
import requests
|
||||
from urllib import parse
|
||||
|
||||
from core import Config
|
||||
from utils.logger import get_logger
|
||||
from app.core import Config
|
||||
from app.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("森空岛签到任务")
|
||||
|
||||
|
||||
89
app/utils/LogMonitor.py
Normal file
89
app/utils/LogMonitor.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, List, Awaitable
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class LogMonitor:
|
||||
def __init__(
|
||||
self,
|
||||
time_stamp_range: tuple[int, int],
|
||||
time_format: str,
|
||||
callback: Callable[[List[str]], Awaitable[None]],
|
||||
encoding: str = "utf-8",
|
||||
):
|
||||
self.time_stamp_range = time_stamp_range
|
||||
self.time_format = time_format
|
||||
self.callback = callback
|
||||
self.encoding = encoding
|
||||
self.log_file_path: Optional[Path] = None
|
||||
self.log_start_time: datetime = datetime.now()
|
||||
self.log_contents: List[str] = []
|
||||
self.task: Optional[asyncio.Task] = None
|
||||
|
||||
async def monitor_log(self):
|
||||
|
||||
if self.log_file_path is None or not self.log_file_path.exists():
|
||||
raise ValueError("Log file path is not set or does not exist.")
|
||||
|
||||
while True:
|
||||
|
||||
log_contents = []
|
||||
if_log_start = False
|
||||
|
||||
async with aiofiles.open(
|
||||
self.log_file_path, "r", encoding=self.encoding
|
||||
) as f:
|
||||
|
||||
async for line in f:
|
||||
if not if_log_start:
|
||||
try:
|
||||
entry_time = datetime.strptime(
|
||||
line[
|
||||
self.time_stamp_range[0] : self.time_stamp_range[1]
|
||||
],
|
||||
self.time_format,
|
||||
)
|
||||
if entry_time > self.log_start_time:
|
||||
if_log_start = True
|
||||
log_contents.append(line)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
log_contents.append(line)
|
||||
|
||||
# 调用回调
|
||||
if log_contents != self.log_contents:
|
||||
self.log_contents = log_contents
|
||||
await self.callback(log_contents)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def start(self, log_file_path: Path, start_time: datetime) -> None:
|
||||
"""启动监控"""
|
||||
|
||||
if log_file_path.is_dir():
|
||||
raise ValueError(f"Log file cannot be a directory: {log_file_path}")
|
||||
|
||||
if self.task is not None and not self.task.done():
|
||||
await self.stop()
|
||||
|
||||
self.log_file_path = log_file_path
|
||||
self.log_start_time = start_time
|
||||
self.task = asyncio.create_task(self.monitor_log())
|
||||
|
||||
logger.info(f"开始监控文件: {self.log_file_path}")
|
||||
|
||||
async def stop(self):
|
||||
"""停止监控"""
|
||||
|
||||
if self.task is not None and not self.task.done():
|
||||
self.task.cancel()
|
||||
|
||||
self.log_contents = []
|
||||
logger.info(f"停止监控文件: {self.log_file_path}")
|
||||
self.log_file_path = None
|
||||
@@ -26,12 +26,14 @@ __license__ = "GPL-3.0 license"
|
||||
|
||||
from .logger import get_logger
|
||||
from .ImageUtils import ImageUtils
|
||||
from .LogMonitor import LogMonitor
|
||||
from .ProcessManager import ProcessManager
|
||||
from .security import dpapi_encrypt, dpapi_decrypt
|
||||
|
||||
__all__ = [
|
||||
"get_logger",
|
||||
"ImageUtils",
|
||||
"LogMonitor",
|
||||
"ProcessManager",
|
||||
"dpapi_encrypt",
|
||||
"dpapi_decrypt",
|
||||
|
||||
Reference in New Issue
Block a user