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

Compare commits

..

8 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
10 changed files with 925 additions and 393 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ class MsgBase:
msg: str msg: str
): ):
self._output_queue.put(f"[{self._class_name:<12}] >>> : {msg}") self._output_queue.put(f"[{self._class_name:<15}] >>> : {msg}")
def _showTrace( def _showTrace(
@@ -38,7 +38,7 @@ class MsgBase:
): ):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 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( def _waitMsg(
+1
View File
@@ -36,6 +36,7 @@ class TimerTaskStatus(Enum):
READY = "已就绪" READY = "已就绪"
RUNNING = "执行中" RUNNING = "执行中"
EXECUTED = "已执行" EXECUTED = "已执行"
ERROR = "执行失败"
OUTDATED = "已过期" OUTDATED = "已过期"
+481 -253
View File
File diff suppressed because it is too large Load Diff
+95 -37
View File
@@ -93,11 +93,26 @@
<string>用户列表</string> <string>用户列表</string>
</property> </property>
<layout class="QVBoxLayout" name="UserListLayout"> <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> <item>
<widget class="QListWidget" name="UserListWidget"> <widget class="QTreeWidget" name="UserTreeWidget">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>230</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@@ -107,18 +122,58 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="isWrapping" stdset="0"> <property name="sizeAdjustPolicy">
<bool>false</bool> <enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property> </property>
<property name="viewMode"> <property name="tabKeyNavigation">
<enum>QListView::ViewMode::ListMode</enum> <bool>true</bool>
</property> </property>
<property name="currentRow"> <property name="dragEnabled">
<number>-1</number> <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>
<property name="sortingEnabled"> <property name="sortingEnabled">
<bool>false</bool> <bool>false</bool>
</property> </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> </widget>
</item> </item>
<item> <item>
@@ -236,7 +291,7 @@
<widget class="QLabel" name="PasswordLabel"> <widget class="QLabel" name="PasswordLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -325,7 +380,7 @@
<widget class="QLabel" name="UsernameKabel"> <widget class="QLabel" name="UsernameKabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -416,7 +471,7 @@
<widget class="QLabel" name="RoomLabel"> <widget class="QLabel" name="RoomLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -435,7 +490,7 @@
<widget class="QLabel" name="FloorLabel"> <widget class="QLabel" name="FloorLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -454,7 +509,7 @@
<widget class="QLabel" name="ExpectRenewDurationLabel"> <widget class="QLabel" name="ExpectRenewDurationLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -473,7 +528,7 @@
<widget class="QLabel" name="EndTimeLabel"> <widget class="QLabel" name="EndTimeLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>30</height> <height>30</height>
</size> </size>
</property> </property>
@@ -556,7 +611,7 @@
<widget class="QLabel" name="SeatIDLabel"> <widget class="QLabel" name="SeatIDLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -575,7 +630,7 @@
<widget class="QLabel" name="ExpectDurationLabel"> <widget class="QLabel" name="ExpectDurationLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -594,7 +649,7 @@
<widget class="QLabel" name="DateLabel"> <widget class="QLabel" name="DateLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -738,7 +793,7 @@
</time> </time>
</property> </property>
<property name="displayFormat"> <property name="displayFormat">
<string>H:mm</string> <string>HH:mm</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -746,7 +801,7 @@
<widget class="QLabel" name="PlaceLabel"> <widget class="QLabel" name="PlaceLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -869,6 +924,9 @@
<property name="stepType"> <property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum> <enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property> </property>
<property name="value">
<number>0</number>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@@ -902,7 +960,7 @@
<widget class="QLabel" name="MaxBeginTimeDiffLabel"> <widget class="QLabel" name="MaxBeginTimeDiffLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -921,7 +979,7 @@
<widget class="QLabel" name="MaxEndTimeDiffLabel"> <widget class="QLabel" name="MaxEndTimeDiffLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -965,7 +1023,7 @@
</time> </time>
</property> </property>
<property name="displayFormat"> <property name="displayFormat">
<string>H:mm</string> <string>HH:mm</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -973,7 +1031,7 @@
<widget class="QLabel" name="BeginTimeLabel"> <widget class="QLabel" name="BeginTimeLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -1024,7 +1082,7 @@
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>80</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
@@ -1047,9 +1105,9 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="SystemConfigWidget"> <widget class="QWidget" name="RunConfigWidget">
<attribute name="title"> <attribute name="title">
<string>系统设置</string> <string>运行设置</string>
</attribute> </attribute>
<layout class="QGridLayout" name="SystemConfigWidgetLayout"> <layout class="QGridLayout" name="SystemConfigWidgetLayout">
<property name="leftMargin"> <property name="leftMargin">
@@ -1579,7 +1637,7 @@
</property> </property>
<layout class="QGridLayout" name="CurrentConfigLayout"> <layout class="QGridLayout" name="CurrentConfigLayout">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLineEdit" name="CurrentSystemConfigEdit"> <widget class="QLineEdit" name="CurrentRunConfigEdit">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
@@ -1620,7 +1678,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="CurrentSystemConfigLabel"> <widget class="QLabel" name="CurrentRunConfigLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
@@ -1634,7 +1692,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>当前系统配置路径:</string> <string>当前运行配置路径:</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -1658,7 +1716,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="BrowseCurrentSystemConfigButton"> <widget class="QPushButton" name="BrowseCurrentRunConfigButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>35</width> <width>35</width>
@@ -1721,7 +1779,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLineEdit" name="ExportSystemConfigEdit"> <widget class="QLineEdit" name="ExportRunConfigEdit">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
@@ -1737,7 +1795,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="ExportSystemConfigLabel"> <widget class="QLabel" name="ExportRunConfigLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
@@ -1751,7 +1809,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>系统配置导出路径:</string> <string>运行配置导出路径:</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -1794,7 +1852,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="BrowseExportSystemConfigButton"> <widget class="QPushButton" name="BrowseExportRunConfigButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>35</width> <width>35</width>
@@ -1836,12 +1894,12 @@
</item> </item>
</layout> </layout>
<zorder>ExportUserConfigEdit</zorder> <zorder>ExportUserConfigEdit</zorder>
<zorder>ExportSystemConfigLabel</zorder> <zorder>ExportRunConfigLabel</zorder>
<zorder>BrowseExportUserConfigButton</zorder> <zorder>BrowseExportUserConfigButton</zorder>
<zorder>ExportUserConfigLabel</zorder> <zorder>ExportUserConfigLabel</zorder>
<zorder>BrowseExportSystemConfigButton</zorder> <zorder>BrowseExportRunConfigButton</zorder>
<zorder>ExportConfigButton</zorder> <zorder>ExportConfigButton</zorder>
<zorder>ExportSystemConfigEdit</zorder> <zorder>ExportRunConfigEdit</zorder>
</widget> </widget>
</item> </item>
<item> <item>
+22 -24
View File
@@ -38,6 +38,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
timerTaskIsRunning = Signal(dict) timerTaskIsRunning = Signal(dict)
timerTaskIsExecuted = Signal(dict) timerTaskIsExecuted = Signal(dict)
timerTaskIsError = Signal(dict)
def __init__( def __init__(
self self
@@ -53,9 +54,9 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
script_path = sys.executable script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir() script_dir = QFileInfo(script_path).absoluteDir()
self.__config_paths = { self.__config_paths = {
"system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json")), "run": QDir.toNativeSeparators(script_dir.absoluteFilePath("run.json")),
"users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")), "user": QDir.toNativeSeparators(script_dir.absoluteFilePath("user.json")),
"timer_tasks": QDir.toNativeSeparators(script_dir.absoluteFilePath("timer_tasks.json")), "timer_task": QDir.toNativeSeparators(script_dir.absoluteFilePath("timer_task.json")),
} }
self.__alTimerTaskWidget = None self.__alTimerTaskWidget = None
self.__alConfigWidget = None self.__alConfigWidget = None
@@ -81,9 +82,10 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.AboutAction.triggered.connect(self.onAboutActionTriggered) self.AboutAction.triggered.connect(self.onAboutActionTriggered)
# initialize timer task widget, but not show it # 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.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted) self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
self.timerTaskIsError.connect(self.__alTimerTaskWidget.onTimerTaskIsError)
self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady) self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed) self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint) self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
@@ -181,7 +183,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__alTimerTaskWidget.deleteLater() self.__alTimerTaskWidget.deleteLater()
if self.__alConfigWidget: if self.__alConfigWidget:
self.__alConfigWidget.close() self.__alConfigWidget.close()
self.__alConfigWidget.deleteLater() # the config widget is already deleted in the 'self.onConfigWidgetClosed'
super().closeEvent(event) super().closeEvent(event)
@@ -229,7 +231,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.timerTaskIsRunning.emit(timer_task) self.timerTaskIsRunning.emit(timer_task)
self.__timer_task_timer.stop() self.__timer_task_timer.stop()
self.__is_running_timer_task = True self.__is_running_timer_task = True
self.setControlButtons(False, False, True) self.setControlButtons(True, True, False)
if not timer_task["silent"]: if not timer_task["silent"]:
self.TrayIcon.showMessage( self.TrayIcon.showMessage(
"定时任务 - AutoLibrary", "定时任务 - AutoLibrary",
@@ -245,8 +247,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths self.__config_paths
) )
self.__current_timer_task_thread.finishedSignal_TimerWorker.connect(self.onTimerTaskFinished) 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() self.__current_timer_task_thread.start()
except queue.Empty: except queue.Empty:
self.__is_running_timer_task = False self.__is_running_timer_task = False
@@ -270,7 +270,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
msg: str msg: str
): ):
self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}") self.__output_queue.put(f"[{self.__class_name:<15}] >>> : {msg}")
@Slot() @Slot()
def showTrace( def showTrace(
@@ -279,7 +279,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
): ):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 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() @Slot()
def pollMsgQueue( def pollMsgQueue(
@@ -293,7 +293,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
except queue.Empty: except queue.Empty:
pass pass
@Slot() @Slot()
def onTimerTaskWidgetClosed( def onTimerTaskWidgetClosed(
self self
@@ -301,7 +300,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.TimerTaskWidgetButton.setEnabled(True) self.TimerTaskWidgetButton.setEnabled(True)
@Slot(dict) @Slot(dict)
def onConfigWidgetClosed( def onConfigWidgetClosed(
self, self,
@@ -326,13 +324,12 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot(dict) @Slot(dict)
def onTimerTaskFinished( def onTimerTaskFinished(
self, self,
is_error: bool,
timer_task: dict timer_task: dict
): ):
self.__current_timer_task_thread.wait(1000) self.__current_timer_task_thread.wait(1000)
self.__current_timer_task_thread.finishedSignal_TimerWorker.disconnect(self.onTimerTaskFinished) 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.deleteLater()
self.__current_timer_task_thread = None self.__current_timer_task_thread = None
self.setControlButtons(True, False, True) self.setControlButtons(True, False, True)
@@ -341,12 +338,17 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
timer_task["executed"] = True timer_task["executed"] = True
self.TrayIcon.showMessage( self.TrayIcon.showMessage(
"定时任务 - AutoLibrary", "定时任务 - AutoLibrary",
f"\n定时任务 '{timer_task['name']}' 执行完成", f"\n定时任务 '{timer_task['name']}' 执行{'失败' if is_error else '完成'}",
QSystemTrayIcon.MessageIcon.Information, QSystemTrayIcon.MessageIcon.Information,
1000 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) self.timerTaskIsExecuted.emit(timer_task)
else:
self.timerTaskIsError.emit(timer_task)
@Slot() @Slot()
def onTimerTaskWidgetButtonClicked( def onTimerTaskWidgetButtonClicked(
@@ -369,8 +371,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths self.__config_paths
) )
self.__alConfigWidget.configWidgetCloseSingal.connect(self.onConfigWidgetClosed) self.__alConfigWidget.configWidgetCloseSingal.connect(self.onConfigWidgetClosed)
self.__alConfigWidget.setWindowFlags(Qt.WindowType.Window)
self.__alConfigWidget.setWindowModality(Qt.WindowModality.ApplicationModal)
self.__alConfigWidget.show() self.__alConfigWidget.show()
self.__alConfigWidget.raise_() self.__alConfigWidget.raise_()
self.__alConfigWidget.activateWindow() self.__alConfigWidget.activateWindow()
@@ -381,7 +381,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self self
): ):
self.setControlButtons(False, False, True) self.setControlButtons(True, True, False)
if self.__auto_lib_thread is None: if self.__auto_lib_thread is None:
self.__auto_lib_thread = AutoLibWorker( self.__auto_lib_thread = AutoLibWorker(
self.__input_queue, self.__input_queue,
@@ -389,8 +389,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths self.__config_paths
) )
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked) self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg) self.__auto_lib_thread.finishedWithErrorSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showTraceSignal.connect(self.showTrace)
self.__auto_lib_thread.start() self.__auto_lib_thread.start()
@Slot() @Slot()
@@ -402,12 +401,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.showTrace("正在停止操作......") self.showTrace("正在停止操作......")
self.__auto_lib_thread.wait(2000) self.__auto_lib_thread.wait(2000)
self.showTrace("操作已停止") 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.finishedSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.finishedWithErrorSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.deleteLater() self.__auto_lib_thread.deleteLater()
self.__auto_lib_thread = None self.__auto_lib_thread = None
self.setControlButtons(True, True, False) self.setControlButtons(True, False, True)
@Slot() @Slot()
def onSendButtonClicked( def onSendButtonClicked(
+75 -37
View File
@@ -12,18 +12,18 @@ import time
import queue import queue
from PySide6.QtCore import ( from PySide6.QtCore import (
Signal, QThread Slot, Signal, QThread
) )
from base.MsgBase import MsgBase
from operators.AutoLib import AutoLib from operators.AutoLib import AutoLib
from utils.ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
class AutoLibWorker(QThread): class AutoLibWorker(QThread, MsgBase):
finishedSignal = Signal() finishedSignal = Signal()
showTraceSignal = Signal(str) finishedWithErrorSignal = Signal()
showMsgSignal = Signal(str)
def __init__( def __init__(
self, self,
@@ -32,10 +32,8 @@ class AutoLibWorker(QThread):
config_paths: dict 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 self.__config_paths = config_paths
@@ -45,6 +43,10 @@ class AutoLibWorker(QThread):
current_time = time.strftime("%H:%M", time.localtime()) current_time = time.strftime("%H:%M", time.localtime())
if current_time >= "23:30" or current_time <= "07:30": if current_time >= "23:30" or current_time <= "07:30":
self._showTrace(
"当前时间不在图书馆开放时间内\n"\
" 请在 07:30 - 23:30 之间尝试"
)
return False return False
return True return True
@@ -56,8 +58,34 @@ class AutoLibWorker(QThread):
if not all( if not all(
os.path.exists(path) for path in self.__config_paths.values() 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 False
return True return True
@@ -70,36 +98,39 @@ class AutoLibWorker(QThread):
auto_lib = None auto_lib = None
try: try:
if not self.checkTimeAvailable(): if not self.checkTimeAvailable():
self.showTraceSignal.emit(
"当前时间不在图书馆开放时间内。\n"\
" 请在 07:30 - 23:30 之间尝试"
)
return return
if not self.checkConfigPaths(): if not self.checkConfigPaths():
return return
self.showTraceSignal.emit("AutoLibrary 开始运行") self._showTrace("AutoLibrary 开始运行")
if not self.loadConfigs():
raise Exception("配置文件加载失败")
auto_lib = AutoLib( auto_lib = AutoLib(
self.__input_queue, self._input_queue,
self.__output_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( auto_lib.run(
ConfigReader(self.__config_paths["system"]), { "users": group.get("users", []) }
ConfigReader(self.__config_paths["users"]),
) )
except Exception as e: except Exception as e:
self.showTraceSignal.emit( self._showTrace(f"AutoLibrary 运行时发生异常 : {e}")
f"AutoLibrary 运行时发生异常 : {e}" self.finishedWithErrorSignal.emit()
) return
finally:
if auto_lib: if auto_lib:
auto_lib.close() auto_lib.close()
self.showTraceSignal.emit("AutoLibrary 运行结束") self._showTrace("AutoLibrary 运行结束")
self.finishedSignal.emit() self.finishedSignal.emit()
class TimerTaskWorker(AutoLibWorker): class TimerTaskWorker(AutoLibWorker):
finishedSignal_TimerWorker = Signal(dict) finishedSignal_TimerWorker = Signal(bool, dict)
def __init__( def __init__(
self, self,
@@ -109,24 +140,31 @@ class TimerTaskWorker(AutoLibWorker):
config_paths: dict config_paths: dict
): ):
super().__init__( super().__init__(input_queue, output_queue, config_paths)
input_queue,
output_queue,
config_paths,
)
self.__timer_task = timer_task self.__timer_task = timer_task
self.__stopped = False self.finishedSignal.connect(self.onTimerTaskIsFinished)
self.finishedWithErrorSignal.connect(self.onTimerTaskIsError)
def run( def run(
self self
): ):
self.showTraceSignal.emit( self._showTrace(f"定时任务 {self.__timer_task['name']} 开始运行")
f"定时任务 {self.__timer_task['name']} 开始运行"
)
super().run() super().run()
self.showTraceSignal.emit(
f"定时任务 {self.__timer_task['name']} 运行结束" @Slot(dict)
) def onTimerTaskIsError(
self.finishedSignal_TimerWorker.emit(self.__timer_task) 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)
+20
View File
@@ -94,6 +94,9 @@ class TimerTaskItemWidget(QWidget):
case TimerTaskStatus.EXECUTED: case TimerTaskStatus.EXECUTED:
TaskStatusText = "已执行" TaskStatusText = "已执行"
TaskStatusColor = "#4CAF50" TaskStatusColor = "#4CAF50"
case TimerTaskStatus.ERROR:
TaskStatusText = "执行失败"
TaskStatusColor = "#FF5722"
case TimerTaskStatus.OUTDATED: case TimerTaskStatus.OUTDATED:
TaskStatusText = "已过期" TaskStatusText = "已过期"
TaskStatusColor = "#FF5722" TaskStatusColor = "#FF5722"
@@ -188,6 +191,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.__timer_tasks = timer_tasks self.__timer_tasks = timer_tasks
self.timerTasksChanged.emit() self.timerTasksChanged.emit()
return True return True
timer_tasks = []
if self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(timer_tasks)): if self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(timer_tasks)):
QMessageBox.information( QMessageBox.information(
self, self,
@@ -314,6 +318,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
pending = 0 pending = 0
in_queue = 0 in_queue = 0
executed = 0 executed = 0
invalid = 0
total = len(self.__timer_tasks) total = len(self.__timer_tasks)
for timer_task in self.__timer_tasks: for timer_task in self.__timer_tasks:
if timer_task["status"] == TimerTaskStatus.PENDING: if timer_task["status"] == TimerTaskStatus.PENDING:
@@ -323,10 +328,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
in_queue += 1 in_queue += 1
elif timer_task["status"] == TimerTaskStatus.EXECUTED: elif timer_task["status"] == TimerTaskStatus.EXECUTED:
executed += 1 executed += 1
elif timer_task["status"] == TimerTaskStatus.ERROR\
or timer_task["status"] == TimerTaskStatus.OUTDATED:
invalid += 1
self.TotalTaskLabel.setText(f"总任务:{total}") self.TotalTaskLabel.setText(f"总任务:{total}")
self.PendingTaskLabel.setText(f"待执行:{pending}") self.PendingTaskLabel.setText(f"待执行:{pending}")
self.InQueueTaskLabel.setText(f"队列中:{in_queue}") self.InQueueTaskLabel.setText(f"队列中:{in_queue}")
self.ExecutedTaskLabel.setText(f"已执行:{executed}") self.ExecutedTaskLabel.setText(f"已执行:{executed}")
self.InvalidTaskLabel.setText(f"无效的:{invalid}")
def updateTimerTaskList( def updateTimerTaskList(
@@ -469,3 +478,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
if task["task_uuid"] == timer_task["task_uuid"]: if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.EXECUTED task["status"] = TimerTaskStatus.EXECUTED
self.timerTasksChanged.emit() 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> </property>
</widget> </widget>
</item> </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> <item>
<widget class="QFrame" name="TimerTaskSpaceFrame"> <widget class="QFrame" name="TimerTaskSpaceFrame">
<property name="minimumSize"> <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__( def __init__(
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue output_queue: queue.Queue,
run_config: dict
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
self.__system_config_reader = None self.__run_config = run_config
self.__users_config_reader = None self.__user_config = None
self.__driver = None self.__driver = None
if not self.__initBrowserDriver():
raise Exception("浏览器驱动初始化失败")
else:
if not self.__initDriverUrl():
raise Exception("浏览器驱动URL初始化失败")
self.__initLibOperators()
def __initBrowserDriver( def __initBrowserDriver(
@@ -48,7 +55,11 @@ class AutoLib(MsgBase):
self._showTrace("正在初始化浏览器驱动......") self._showTrace("正在初始化浏览器驱动......")
edge_options = webdriver.EdgeOptions() 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("--headless")
edge_options.add_argument("--disable-gpu") edge_options.add_argument("--disable-gpu")
edge_options.add_argument("--no-sandbox") edge_options.add_argument("--no-sandbox")
@@ -76,8 +87,8 @@ class AutoLib(MsgBase):
) )
# init browser driver # init browser driver
self.__driver_path = self.__system_config_reader.get("web_driver/driver_path") self.__driver_path = web_driver_config.get("driver_path")
self.__driver_type = self.__system_config_reader.get("web_driver/driver_type") self.__driver_type = web_driver_config.get("driver_type")
self.__driver_path = os.path.abspath(self.__driver_path) self.__driver_path = os.path.abspath(self.__driver_path)
try: try:
service = None service = None
@@ -149,8 +160,11 @@ class AutoLib(MsgBase):
self, self,
) -> bool: ) -> bool:
url = self.__system_config_reader.get("library/host_url") lib_config = self.__run_config.get("library", None)
url += self.__system_config_reader.get("library/login_url") if not lib_config:
self._showError("未配置图书馆参数 !")
return False
url = lib_config.get("host_url") + lib_config.get("login_url")
self.__driver.get(url) self.__driver.get(url)
if not self.__waitResponseLoad(): if not self.__waitResponseLoad():
return False return False
@@ -161,24 +175,26 @@ class AutoLib(MsgBase):
self, self,
username: str, username: str,
password: str, password: str,
login_config: dict,
run_mode_config: dict,
reserve_info: dict reserve_info: dict
) -> int: ) -> int:
# result : 0 - success, 1 - failed, 2 - passed # result : -1 - terminate, 0 - success, 1 - failed, 2 - passed
result = 2 result = 2
# login # login
if not self.__lib_login.login( if not self.__lib_login.login(
username, username,
password, password,
self.__system_config_reader.get("login/max_attempt", 5), login_config.get("max_attempt", 3),
self.__system_config_reader.get("login/auto_captcha", True), login_config.get("auto_captcha", True),
): ):
return 1 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 = { run_mode = {
"auto_reserve": run_mode&0x1, "auto_reserve": run_mode&0x1,
"auto_checkin": run_mode&0x2, "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 # if logout is failed, we must make sure the host to be reloaded
# otherwise, the next login may fail # otherwise, the next login may fail
self.__driver.get(self.__system_config_reader.get("library/host_url")) if not self.__initDriverUrl():
return 1 return -1
return result return result
def run( def run(
self, self,
system_config_reader: ConfigReader, user_config: dict
users_config_reader: ConfigReader
): ):
self.__system_config_reader = system_config_reader self.__user_config = user_config
self.__users_config_reader = users_config_reader
if not self.__initBrowserDriver():
return
else:
if not self.__initDriverUrl():
return
self.__initLibOperators()
user_counter = {"current": 0, "success": 0, "failed": 0, "passed": 0} user_counter = {"current": 0, "success": 0, "failed": 0, "passed": 0}
users = self.__users_config_reader.get("users") users = self.__user_config["users"]
self._showTrace( self._showTrace(f"共发现 {len(users)} 个用户")
f"共发现 {len(users)} 个用户, "\
f"用户配置文件路径: {self.__users_config_reader.configPath()}"
)
for user in users: for user in users:
user_counter["current"] += 1 user_counter["current"] += 1
self._showTrace( 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( r = self.__run(
username=user["username"], username=user["username"],
password=user["password"], password=user["password"],
login_config=self.__run_config["login"],
run_mode_config=self.__run_config["mode"],
reserve_info=user["reserve_info"], reserve_info=user["reserve_info"],
) )
if r == 0: if r == -1:
self._showTrace(
f"用户 {user["username"]} 处理过程中页面发生异常,无法继续操作, 任务已终止 !"
)
break
elif r == 0:
user_counter["success"] += 1 user_counter["success"] += 1
elif r == 1: elif r == 1:
user_counter["failed"] += 1 user_counter["failed"] += 1