1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 15:33:03 +08:00

Compare commits

...

10 Commits

Author SHA1 Message Date
KenanZhu 577c651ef8 feat(ALMainWindow): 引入对新增定时器任务状态 - 执行失败的处理支持 (#18ae949)
同时,为了统一消息处理,我们将 ALMainWorkers 中的原信号
槽处理的消息逻辑更改为使用继承的 MsgBase 类的 showTrace 方法
2025-12-13 14:27:46 +08:00
KenanZhu 18ae949900 feat(ALTimerTaskWidget): 新增定时器任务状态 - 执行失败 2025-12-13 14:22:28 +08:00
KenanZhu ca9059d1db refactor(AutoLib): 初始化 AutoLib 时,发生错误则抛出异常 2025-12-13 14:21:26 +08:00
KenanZhu ad4deae0c6 fix(ALMainWindow): 修复停止时的按钮状态重置问题
函数更改于(#9255eec)
2025-12-13 14:15:28 +08:00
KenanZhu 55ae4d0d96 feat(ALConfigWidget): 大更新 - 用户树状列表和其它
1. 在这个 commit 中,我们思考了许久,最终决定将现有的
用户管理列表转为树状列表,以解决用户数量增多时,用户的
选择性管理,分组等问题。
2. 同时因为该更改需要重构很多内容,我们也在该 commit
中决定将所有‘系统配置’更换为‘运行配置’,同时文件名称和
内容变量也相应变为‘run’和‘user’。
3. 重构 AutoLib 和 ALMainWorkers 中的配置相关代码,
以适应新的用户树状列表。

当前迭代更新至 v1.0.0-beta.4, 同时,在该版本的 rc
阶段前,我们计划不再发布 beta 阶段相关的 release
2025-12-13 00:07:33 +08:00
KenanZhu 7dcd72939b fix(ALMainWindow): fix the wrong use of function 'setControlButtons' 2025-12-12 23:51:54 +08:00
KenanZhu bfce61f4b4 fix(ALTimerTaskWidget): fix timer tasks list is 'NoneType' when init config file 2025-12-12 23:41:30 +08:00
KenanZhu 60a5699822 refactor(ALConfigWidget): ALConfigWidget is changed into non-modal dialog 2025-12-12 18:59:25 +08:00
KenanZhu aab9565012 fix(*): always show the child window on the center of the parent window and do not overflow the screen 2025-12-09 08:54:45 +08:00
KenanZhu 9255eec9f1 style(ALMainWindow): rename some variables and functions 2025-12-09 08:51:14 +08:00
11 changed files with 1007 additions and 405 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ class MsgBase:
msg: str
):
self._output_queue.put(f"[{self._class_name:<12}] >>> : {msg}")
self._output_queue.put(f"[{self._class_name:<15}] >>> : {msg}")
def _showTrace(
@@ -38,7 +38,7 @@ class MsgBase:
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self._output_queue.put(f"{timestamp}-[{self._class_name:<12}] : {msg}")
self._output_queue.put(f"{timestamp}-[{self._class_name:<15}] : {msg}")
def _waitMsg(
+1
View File
@@ -36,6 +36,7 @@ class TimerTaskStatus(Enum):
READY = "已就绪"
RUNNING = "执行中"
EXECUTED = "已执行"
ERROR = "执行失败"
OUTDATED = "已过期"
+505 -253
View File
File diff suppressed because it is too large Load Diff
+95 -37
View File
@@ -93,11 +93,26 @@
<string>用户列表</string>
</property>
<layout class="QVBoxLayout" name="UserListLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QListWidget" name="UserListWidget">
<widget class="QTreeWidget" name="UserTreeWidget">
<property name="minimumSize">
<size>
<width>100</width>
<width>230</width>
<height>0</height>
</size>
</property>
@@ -107,18 +122,58 @@
<height>16777215</height>
</size>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="viewMode">
<enum>QListView::ViewMode::ListMode</enum>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="currentRow">
<number>-1</number>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDropMode::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::DropAction::MoveAction</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>false</bool>
</property>
<property name="headerHidden">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">分组/用户</string>
</property>
</column>
<column>
<property name="text">
<string>状态</string>
</property>
</column>
</widget>
</item>
<item>
@@ -236,7 +291,7 @@
<widget class="QLabel" name="PasswordLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -325,7 +380,7 @@
<widget class="QLabel" name="UsernameKabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -416,7 +471,7 @@
<widget class="QLabel" name="RoomLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -435,7 +490,7 @@
<widget class="QLabel" name="FloorLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -454,7 +509,7 @@
<widget class="QLabel" name="ExpectRenewDurationLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -473,7 +528,7 @@
<widget class="QLabel" name="EndTimeLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>30</height>
</size>
</property>
@@ -556,7 +611,7 @@
<widget class="QLabel" name="SeatIDLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -575,7 +630,7 @@
<widget class="QLabel" name="ExpectDurationLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -594,7 +649,7 @@
<widget class="QLabel" name="DateLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -738,7 +793,7 @@
</time>
</property>
<property name="displayFormat">
<string>H:mm</string>
<string>HH:mm</string>
</property>
</widget>
</item>
@@ -746,7 +801,7 @@
<widget class="QLabel" name="PlaceLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -869,6 +924,9 @@
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
@@ -902,7 +960,7 @@
<widget class="QLabel" name="MaxBeginTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -921,7 +979,7 @@
<widget class="QLabel" name="MaxEndTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -965,7 +1023,7 @@
</time>
</property>
<property name="displayFormat">
<string>H:mm</string>
<string>HH:mm</string>
</property>
</widget>
</item>
@@ -973,7 +1031,7 @@
<widget class="QLabel" name="BeginTimeLabel">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -1024,7 +1082,7 @@
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>100</width>
<width>80</width>
<height>25</height>
</size>
</property>
@@ -1047,9 +1105,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="SystemConfigWidget">
<widget class="QWidget" name="RunConfigWidget">
<attribute name="title">
<string>系统设置</string>
<string>运行设置</string>
</attribute>
<layout class="QGridLayout" name="SystemConfigWidgetLayout">
<property name="leftMargin">
@@ -1579,7 +1637,7 @@
</property>
<layout class="QGridLayout" name="CurrentConfigLayout">
<item row="1" column="0">
<widget class="QLineEdit" name="CurrentSystemConfigEdit">
<widget class="QLineEdit" name="CurrentRunConfigEdit">
<property name="minimumSize">
<size>
<width>0</width>
@@ -1620,7 +1678,7 @@
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="CurrentSystemConfigLabel">
<widget class="QLabel" name="CurrentRunConfigLabel">
<property name="minimumSize">
<size>
<width>0</width>
@@ -1634,7 +1692,7 @@
</size>
</property>
<property name="text">
<string>当前系统配置路径:</string>
<string>当前运行配置路径:</string>
</property>
</widget>
</item>
@@ -1658,7 +1716,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="BrowseCurrentSystemConfigButton">
<widget class="QPushButton" name="BrowseCurrentRunConfigButton">
<property name="minimumSize">
<size>
<width>35</width>
@@ -1721,7 +1779,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="ExportSystemConfigEdit">
<widget class="QLineEdit" name="ExportRunConfigEdit">
<property name="minimumSize">
<size>
<width>0</width>
@@ -1737,7 +1795,7 @@
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="ExportSystemConfigLabel">
<widget class="QLabel" name="ExportRunConfigLabel">
<property name="minimumSize">
<size>
<width>0</width>
@@ -1751,7 +1809,7 @@
</size>
</property>
<property name="text">
<string>系统配置导出路径:</string>
<string>运行配置导出路径:</string>
</property>
</widget>
</item>
@@ -1794,7 +1852,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="BrowseExportSystemConfigButton">
<widget class="QPushButton" name="BrowseExportRunConfigButton">
<property name="minimumSize">
<size>
<width>35</width>
@@ -1836,12 +1894,12 @@
</item>
</layout>
<zorder>ExportUserConfigEdit</zorder>
<zorder>ExportSystemConfigLabel</zorder>
<zorder>ExportRunConfigLabel</zorder>
<zorder>BrowseExportUserConfigButton</zorder>
<zorder>ExportUserConfigLabel</zorder>
<zorder>BrowseExportSystemConfigButton</zorder>
<zorder>BrowseExportRunConfigButton</zorder>
<zorder>ExportConfigButton</zorder>
<zorder>ExportSystemConfigEdit</zorder>
<zorder>ExportRunConfigEdit</zorder>
</widget>
</item>
<item>
+32 -36
View File
@@ -38,6 +38,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
timerTaskIsRunning = Signal(dict)
timerTaskIsExecuted = Signal(dict)
timerTaskIsError = Signal(dict)
def __init__(
self
@@ -53,9 +54,9 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir()
self.__config_paths = {
"system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json")),
"users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")),
"timer_tasks": QDir.toNativeSeparators(script_dir.absoluteFilePath("timer_tasks.json")),
"run": QDir.toNativeSeparators(script_dir.absoluteFilePath("run.json")),
"user": QDir.toNativeSeparators(script_dir.absoluteFilePath("user.json")),
"timer_task": QDir.toNativeSeparators(script_dir.absoluteFilePath("timer_task.json")),
}
self.__alTimerTaskWidget = None
self.__alConfigWidget = None
@@ -81,9 +82,10 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.AboutAction.triggered.connect(self.onAboutActionTriggered)
# initialize timer task widget, but not show it
self.__alTimerTaskWidget = ALTimerTaskWidget(self, self.__config_paths["timer_tasks"])
self.__alTimerTaskWidget = ALTimerTaskWidget(self, self.__config_paths["timer_task"])
self.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
self.timerTaskIsError.connect(self.__alTimerTaskWidget.onTimerTaskIsError)
self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
@@ -169,8 +171,8 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
event: QCloseEvent
):
if self.__timer and self.__timer.isActive():
self.__timer.stop()
if self.__msg_queue_timer and self.__msg_queue_timer.isActive():
self.__msg_queue_timer.stop()
if self.__timer_task_timer and self.__timer_task_timer.isActive():
self.__timer_task_timer.stop()
if self.__is_running_timer_task:
@@ -181,7 +183,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__alTimerTaskWidget.deleteLater()
if self.__alConfigWidget:
self.__alConfigWidget.close()
self.__alConfigWidget.deleteLater()
# the config widget is already deleted in the 'self.onConfigWidgetClosed'
super().closeEvent(event)
@@ -203,9 +205,9 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
self.__timer = QTimer()
self.__timer.timeout.connect(self.pollMsgQueue)
self.__timer.start(100)
self.__msg_queue_timer = QTimer()
self.__msg_queue_timer.timeout.connect(self.pollMsgQueue)
self.__msg_queue_timer.start(100)
def startTimerTaskPolling(
@@ -229,7 +231,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.timerTaskIsRunning.emit(timer_task)
self.__timer_task_timer.stop()
self.__is_running_timer_task = True
self.setControlButtons(False, False, True)
self.setControlButtons(True, True, False)
if not timer_task["silent"]:
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
@@ -245,8 +247,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths
)
self.__current_timer_task_thread.finishedSignal_TimerWorker.connect(self.onTimerTaskFinished)
self.__current_timer_task_thread.showTraceSignal.connect(self.showTrace)
self.__current_timer_task_thread.showMsgSignal.connect(self.showMsg)
self.__current_timer_task_thread.start()
except queue.Empty:
self.__is_running_timer_task = False
@@ -256,13 +256,13 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
def setControlButtons(
self,
config_button_enabled: bool,
start_button_enabled: bool,
stop_button_enabled: bool
stop_button_enabled: bool,
start_button_enabled: bool
):
self.ConfigButton.setEnabled(config_button_enabled)
self.StartButton.setEnabled(start_button_enabled)
self.StopButton.setEnabled(stop_button_enabled)
self.StartButton.setEnabled(start_button_enabled)
@Slot()
def showMsg(
@@ -270,7 +270,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
msg: str
):
self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}")
self.__output_queue.put(f"[{self.__class_name:<15}] >>> : {msg}")
@Slot()
def showTrace(
@@ -279,7 +279,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.appendToTextEdit(f"{timestamp}-[{self.__class_name:<12}] : {msg}")
self.__output_queue.put(f"{timestamp}-[{self.__class_name:<15}] : {msg}")
@Slot()
def pollMsgQueue(
@@ -293,7 +293,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
except queue.Empty:
pass
@Slot()
def onTimerTaskWidgetClosed(
self
@@ -301,7 +300,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.TimerTaskWidgetButton.setEnabled(True)
@Slot(dict)
def onConfigWidgetClosed(
self,
@@ -312,9 +310,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__alConfigWidget.configWidgetCloseSingal.disconnect(self.onConfigWidgetClosed)
self.__alConfigWidget.deleteLater()
self.__alConfigWidget = None
self.ConfigButton.setEnabled(True)
self.StartButton.setEnabled(True)
self.StopButton.setEnabled(False)
self.setControlButtons(True, False, True)
self.__config_paths = config_paths
@Slot(dict)
@@ -328,27 +324,31 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot(dict)
def onTimerTaskFinished(
self,
is_error: bool,
timer_task: dict
):
self.__current_timer_task_thread.wait(1000)
self.__current_timer_task_thread.finishedSignal_TimerWorker.disconnect(self.onTimerTaskFinished)
self.__current_timer_task_thread.showTraceSignal.disconnect(self.showTrace)
self.__current_timer_task_thread.showMsgSignal.disconnect(self.showMsg)
self.__current_timer_task_thread.deleteLater()
self.__current_timer_task_thread = None
self.setControlButtons(True, True, False)
self.setControlButtons(True, False, True)
self.__is_running_timer_task = False
self.__timer_task_timer.start(500)
timer_task["executed"] = True
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
f"\n定时任务 '{timer_task['name']}' 执行完成",
f"\n定时任务 '{timer_task['name']}' 执行{'失败' if is_error else '完成'}",
QSystemTrayIcon.MessageIcon.Information,
1000
)
self.showTrace(f"定时任务 {timer_task['name']} 执行完成, uuid: {timer_task['task_uuid']}")
self.showTrace(
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['task_uuid']}"
)
if not is_error:
self.timerTaskIsExecuted.emit(timer_task)
else:
self.timerTaskIsError.emit(timer_task)
@Slot()
def onTimerTaskWidgetButtonClicked(
@@ -371,8 +371,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths
)
self.__alConfigWidget.configWidgetCloseSingal.connect(self.onConfigWidgetClosed)
self.__alConfigWidget.setWindowFlags(Qt.Window)
self.__alConfigWidget.setWindowModality(Qt.ApplicationModal)
self.__alConfigWidget.show()
self.__alConfigWidget.raise_()
self.__alConfigWidget.activateWindow()
@@ -383,7 +381,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
self.setControlButtons(False, False, True)
self.setControlButtons(True, True, False)
if self.__auto_lib_thread is None:
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
@@ -391,8 +389,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths
)
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.connect(self.showTrace)
self.__auto_lib_thread.finishedWithErrorSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.start()
@Slot()
@@ -404,12 +401,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.showTrace("正在停止操作......")
self.__auto_lib_thread.wait(2000)
self.showTrace("操作已停止")
self.__auto_lib_thread.showMsgSignal.disconnect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.disconnect(self.showTrace)
self.__auto_lib_thread.finishedSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.finishedWithErrorSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.deleteLater()
self.__auto_lib_thread = None
self.setControlButtons(True, True, False)
self.setControlButtons(True, False, True)
@Slot()
def onSendButtonClicked(
+75 -37
View File
@@ -12,18 +12,18 @@ import time
import queue
from PySide6.QtCore import (
Signal, QThread
Slot, Signal, QThread
)
from base.MsgBase import MsgBase
from operators.AutoLib import AutoLib
from utils.ConfigReader import ConfigReader
class AutoLibWorker(QThread):
class AutoLibWorker(QThread, MsgBase):
finishedSignal = Signal()
showTraceSignal = Signal(str)
showMsgSignal = Signal(str)
finishedWithErrorSignal = Signal()
def __init__(
self,
@@ -32,10 +32,8 @@ class AutoLibWorker(QThread):
config_paths: dict
):
super().__init__()
super().__init__(input_queue = input_queue, output_queue = output_queue)
self.__input_queue = input_queue
self.__output_queue = output_queue
self.__config_paths = config_paths
@@ -45,6 +43,10 @@ class AutoLibWorker(QThread):
current_time = time.strftime("%H:%M", time.localtime())
if current_time >= "23:30" or current_time <= "07:30":
self._showTrace(
"当前时间不在图书馆开放时间内\n"\
" 请在 07:30 - 23:30 之间尝试"
)
return False
return True
@@ -56,8 +58,34 @@ class AutoLibWorker(QThread):
if not all(
os.path.exists(path) for path in self.__config_paths.values()
):
self.showTraceSignal.emit(
"配置文件路径不存在, 请检查配置文件路径是否正确。"
self._showTrace("配置文件路径不存在, 请检查配置文件路径是否正确。")
return False
return True
def loadConfigs(
self
) -> bool:
self._showTrace(
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
)
self.__run_config = ConfigReader(
self.__config_paths["run"]
).getConfigs()
self._showTrace(
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
)
self.__user_config = ConfigReader(
self.__config_paths["user"]
).getConfigs()
if self.__run_config is None or self.__user_config is None:
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
return False
if not self.__user_config.get("groups"):
self._showTrace(
"用户配置文件中无有效任务组, 请检查用户配置文件是否正确"
)
return False
return True
@@ -70,36 +98,39 @@ class AutoLibWorker(QThread):
auto_lib = None
try:
if not self.checkTimeAvailable():
self.showTraceSignal.emit(
"当前时间不在图书馆开放时间内。\n"\
" 请在 07:30 - 23:30 之间尝试"
)
return
if not self.checkConfigPaths():
return
self.showTraceSignal.emit("AutoLibrary 开始运行")
self._showTrace("AutoLibrary 开始运行")
if not self.loadConfigs():
raise Exception("配置文件加载失败")
auto_lib = AutoLib(
self.__input_queue,
self.__output_queue,
self._input_queue,
self._output_queue,
self.__run_config
)
groups = self.__user_config.get("groups")
for group in groups:
if not group["enabled"]:
self._showTrace(f"任务组 {group["name"]} 已跳过")
continue
self._showTrace(f"正在运行任务组 {group["name"]}")
auto_lib.run(
ConfigReader(self.__config_paths["system"]),
ConfigReader(self.__config_paths["users"]),
{ "users": group.get("users", []) }
)
except Exception as e:
self.showTraceSignal.emit(
f"AutoLibrary 运行时发生异常 : {e}"
)
finally:
self._showTrace(f"AutoLibrary 运行时发生异常 : {e}")
self.finishedWithErrorSignal.emit()
return
if auto_lib:
auto_lib.close()
self.showTraceSignal.emit("AutoLibrary 运行结束")
self._showTrace("AutoLibrary 运行结束")
self.finishedSignal.emit()
class TimerTaskWorker(AutoLibWorker):
finishedSignal_TimerWorker = Signal(dict)
finishedSignal_TimerWorker = Signal(bool, dict)
def __init__(
self,
@@ -109,24 +140,31 @@ class TimerTaskWorker(AutoLibWorker):
config_paths: dict
):
super().__init__(
input_queue,
output_queue,
config_paths,
)
super().__init__(input_queue, output_queue, config_paths)
self.__timer_task = timer_task
self.__stopped = False
self.finishedSignal.connect(self.onTimerTaskIsFinished)
self.finishedWithErrorSignal.connect(self.onTimerTaskIsError)
def run(
self
):
self.showTraceSignal.emit(
f"定时任务 {self.__timer_task['name']} 开始运行"
)
self._showTrace(f"定时任务 {self.__timer_task['name']} 开始运行")
super().run()
self.showTraceSignal.emit(
f"定时任务 {self.__timer_task['name']} 运行结束"
)
self.finishedSignal_TimerWorker.emit(self.__timer_task)
@Slot(dict)
def onTimerTaskIsError(
self
):
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行时发生异常")
self.finishedSignal_TimerWorker.emit(True, self.__timer_task)
@Slot(dict)
def onTimerTaskIsFinished(
self
):
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行结束")
self.finishedSignal_TimerWorker.emit(False, self.__timer_task)
+24
View File
@@ -115,6 +115,30 @@ class ALSeatMapWidget(QWidget):
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
def showEvent(
self,
event
):
result = super().showEvent(event)
screen_rect = self.screen().geometry()
target_pos = self.parent().geometry().center()
target_pos.setX(target_pos.x() - self.width()//2)
target_pos.setY(target_pos.y() - self.height()//2)
if target_pos.x() < 0:
target_pos.setX(0)
if target_pos.x() + self.width() > screen_rect.width():
target_pos.setX(screen_rect.width() - self.width())
if target_pos.y() < 0:
target_pos.setY(0)
if target_pos.y() + self.height() > screen_rect.height():
target_pos.setY(screen_rect.height() - self.height())
self.move(target_pos)
return result
def closeEvent(
self,
event: QCloseEvent
+44
View File
@@ -94,6 +94,9 @@ class TimerTaskItemWidget(QWidget):
case TimerTaskStatus.EXECUTED:
TaskStatusText = "已执行"
TaskStatusColor = "#4CAF50"
case TimerTaskStatus.ERROR:
TaskStatusText = "执行失败"
TaskStatusColor = "#FF5722"
case TimerTaskStatus.OUTDATED:
TaskStatusText = "已过期"
TaskStatusColor = "#FF5722"
@@ -188,6 +191,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.__timer_tasks = timer_tasks
self.timerTasksChanged.emit()
return True
timer_tasks = []
if self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(timer_tasks)):
QMessageBox.information(
self,
@@ -254,6 +258,30 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
return False
def showEvent(
self,
event
):
result = super().showEvent(event)
screen_rect = self.screen().geometry()
target_pos = self.parent().geometry().center()
target_pos.setX(target_pos.x() - self.width()//2)
target_pos.setY(target_pos.y() - self.height()//2)
if target_pos.x() < 0:
target_pos.setX(0)
if target_pos.x() + self.width() > screen_rect.width():
target_pos.setX(screen_rect.width() - self.width())
if target_pos.y() < 0:
target_pos.setY(0)
if target_pos.y() + self.height() > screen_rect.height():
target_pos.setY(screen_rect.height() - self.height())
self.move(target_pos)
return result
def closeEvent(
self,
event: QCloseEvent
@@ -290,6 +318,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
pending = 0
in_queue = 0
executed = 0
invalid = 0
total = len(self.__timer_tasks)
for timer_task in self.__timer_tasks:
if timer_task["status"] == TimerTaskStatus.PENDING:
@@ -299,10 +328,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
in_queue += 1
elif timer_task["status"] == TimerTaskStatus.EXECUTED:
executed += 1
elif timer_task["status"] == TimerTaskStatus.ERROR\
or timer_task["status"] == TimerTaskStatus.OUTDATED:
invalid += 1
self.TotalTaskLabel.setText(f"总任务:{total}")
self.PendingTaskLabel.setText(f"待执行:{pending}")
self.InQueueTaskLabel.setText(f"队列中:{in_queue}")
self.ExecutedTaskLabel.setText(f"已执行:{executed}")
self.InvalidTaskLabel.setText(f"无效的:{invalid}")
def updateTimerTaskList(
@@ -445,3 +478,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.EXECUTED
self.timerTasksChanged.emit()
@Slot(dict)
def onTimerTaskIsError(
self,
timer_task: dict
):
for task in self.__timer_tasks:
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.ERROR
self.timerTasksChanged.emit()
+24
View File
@@ -137,6 +137,30 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="InvalidTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>70</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
color: #FF5722
}</string>
</property>
<property name="text">
<string>无效的:0</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="TimerTaskSpaceFrame">
<property name="minimumSize">
+149
View File
@@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from enum import Enum
from PySide6.QtCore import (
Qt, QSize, QCoreApplication, QRect, QPoint
)
from PySide6.QtWidgets import (
QAbstractScrollArea, QAbstractItemView,
QTreeWidget, QTreeWidgetItem
)
from PySide6.QtGui import (
QDragEnterEvent, QDragMoveEvent, QDropEvent
)
class TreeItemType(Enum):
GROUP = 0
USER = 1
class ALUserTreeWidget(QTreeWidget):
def __init__(
self,
parent = None
):
super().__init__(parent)
self.setupUi()
self.translateUi()
def setupUi(
self
):
__qtreewidgetitem = QTreeWidgetItem()
__qtreewidgetitem.setText(0, u"\u5206\u7ec4/\u7528\u6237");
self.setHeaderItem(__qtreewidgetitem)
self.setObjectName(u"UserTreeWidget")
self.setMinimumSize(QSize(230, 0))
self.setMaximumSize(QSize(250, 16777215))
self.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustIgnored)
self.setTabKeyNavigation(True)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.setDefaultDropAction(Qt.DropAction.IgnoreAction)
self.setAlternatingRowColors(True)
self.setSortingEnabled(True)
self.setAnimated(True)
self.setAllColumnsShowFocus(False)
self.setHeaderHidden(False)
self.setColumnCount(2)
self.setColumnWidth(0, 150)
self.setColumnWidth(1, 20)
self.header().setCascadingSectionResizes(False)
self.header().setHighlightSections(False)
self.header().setProperty(u"showSortIndicator", True)
def translateUi(
self
):
___qtreewidgetitem = self.headerItem()
___qtreewidgetitem.setText(1, QCoreApplication.translate("ALConfigWidget", u"\u72b6\u6001", None));
@staticmethod
def isDragPositionValid(
target_rect: QRect,
drag_pos: QPoint,
) -> bool:
y_offset = drag_pos.y() - target_rect.top()
valid = (y_offset > target_rect.height()*0.2 and
y_offset < target_rect.height()*0.8)
return valid
def dragEnterEvent(
self,
event: QDragEnterEvent
):
super().dragEnterEvent(event)
def dragMoveEvent(
self,
event: QDragMoveEvent
):
super().dragMoveEvent(event)
source_item = self.currentItem()
target_item = self.itemAt(event.position().toPoint())
if source_item is None:
event.ignore()
return
if source_item.type() == TreeItemType.GROUP.value:
if target_item is not None:
event.ignore()
return
elif source_item.type() == TreeItemType.USER.value:
if target_item is None:
event.ignore()
return
if target_item.type() != TreeItemType.GROUP.value:
event.ignore()
return
if target_item.checkState(1) == Qt.CheckState.Unchecked:
event.ignore()
return
if not self.isDragPositionValid(
self.visualItemRect(target_item),
event.position().toPoint()
):
event.ignore()
return
else:
event.ignore()
return
event.acceptProposedAction()
def dropEvent(
self,
event: QDropEvent
):
super().dropEvent(event)
for item_index in range(self.topLevelItemCount()):
self.topLevelItem(item_index).setExpanded(True)
self.setCurrentItem(None)
+48 -32
View File
@@ -32,13 +32,20 @@ class AutoLib(MsgBase):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue
output_queue: queue.Queue,
run_config: dict
):
super().__init__(input_queue, output_queue)
self.__system_config_reader = None
self.__users_config_reader = None
self.__run_config = run_config
self.__user_config = None
self.__driver = None
if not self.__initBrowserDriver():
raise Exception("浏览器驱动初始化失败")
else:
if not self.__initDriverUrl():
raise Exception("浏览器驱动URL初始化失败")
self.__initLibOperators()
def __initBrowserDriver(
@@ -48,7 +55,11 @@ class AutoLib(MsgBase):
self._showTrace("正在初始化浏览器驱动......")
edge_options = webdriver.EdgeOptions()
if self.__system_config_reader.get("web_driver/headless"):
web_driver_config = self.__run_config.get("web_driver", None)
if not web_driver_config:
self._showTrace("未配置浏览器驱动参数 !")
return False
if web_driver_config.get("headless"):
edge_options.add_argument("--headless")
edge_options.add_argument("--disable-gpu")
edge_options.add_argument("--no-sandbox")
@@ -76,8 +87,8 @@ class AutoLib(MsgBase):
)
# init browser driver
self.__driver_path = self.__system_config_reader.get("web_driver/driver_path")
self.__driver_type = self.__system_config_reader.get("web_driver/driver_type")
self.__driver_path = web_driver_config.get("driver_path")
self.__driver_type = web_driver_config.get("driver_type")
self.__driver_path = os.path.abspath(self.__driver_path)
try:
service = None
@@ -149,8 +160,11 @@ class AutoLib(MsgBase):
self,
) -> bool:
url = self.__system_config_reader.get("library/host_url")
url += self.__system_config_reader.get("library/login_url")
lib_config = self.__run_config.get("library", None)
if not lib_config:
self._showError("未配置图书馆参数 !")
return False
url = lib_config.get("host_url") + lib_config.get("login_url")
self.__driver.get(url)
if not self.__waitResponseLoad():
return False
@@ -161,24 +175,26 @@ class AutoLib(MsgBase):
self,
username: str,
password: str,
login_config: dict,
run_mode_config: dict,
reserve_info: dict
) -> int:
# result : 0 - success, 1 - failed, 2 - passed
# result : -1 - terminate, 0 - success, 1 - failed, 2 - passed
result = 2
# login
if not self.__lib_login.login(
username,
password,
self.__system_config_reader.get("login/max_attempt", 5),
self.__system_config_reader.get("login/auto_captcha", True),
login_config.get("max_attempt", 3),
login_config.get("auto_captcha", True),
):
return 1
"""
Here, we collect the run mode from the config file.
Here, we collect the run mode from the run config.
"""
run_mode = self.__system_config_reader.get("mode/run_mode", 0)
run_mode = run_mode_config.get("run_mode", 0)
run_mode = {
"auto_reserve": run_mode&0x1,
"auto_checkin": run_mode&0x2,
@@ -223,43 +239,43 @@ class AutoLib(MsgBase):
):
# if logout is failed, we must make sure the host to be reloaded
# otherwise, the next login may fail
self.__driver.get(self.__system_config_reader.get("library/host_url"))
return 1
if not self.__initDriverUrl():
return -1
return result
def run(
self,
system_config_reader: ConfigReader,
users_config_reader: ConfigReader
user_config: dict
):
self.__system_config_reader = system_config_reader
self.__users_config_reader = users_config_reader
if not self.__initBrowserDriver():
return
else:
if not self.__initDriverUrl():
return
self.__initLibOperators()
self.__user_config = user_config
user_counter = {"current": 0, "success": 0, "failed": 0, "passed": 0}
users = self.__users_config_reader.get("users")
self._showTrace(
f"共发现 {len(users)} 个用户, "\
f"用户配置文件路径: {self.__users_config_reader.configPath()}"
)
users = self.__user_config["users"]
self._showTrace(f"共发现 {len(users)} 个用户")
for user in users:
user_counter["current"] += 1
self._showTrace(
f"正在处理第 {user_counter["current"]}/{len(users)} 个用户: {user['username']}......"
f"正在处理第 {user_counter["current"]}/{len(users)} 个用户: {user["username"]}......"
)
if not user["enabled"]:
self._showTrace(f"用户 {user["username"]} 已跳过")
user_counter["passed"] += 1
continue
r = self.__run(
username=user["username"],
password=user["password"],
login_config=self.__run_config["login"],
run_mode_config=self.__run_config["mode"],
reserve_info=user["reserve_info"],
)
if r == 0:
if r == -1:
self._showTrace(
f"用户 {user["username"]} 处理过程中页面发生异常,无法继续操作, 任务已终止 !"
)
break
elif r == 0:
user_counter["success"] += 1
elif r == 1:
user_counter["failed"] += 1