mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 07:23:03 +08:00
ci(workflows): 修复 Release 工作流的触发条件
- 当创建 'release/v*' 分支时,自动进行 Release 构建 /! Release 流程必须手动创建分支,工作流结束后会将对应分支提交合并 /! 到 main 分支上,且对应分支会被删除
This commit is contained in:
@@ -40,9 +40,9 @@ name: Release
|
||||
# and create a new release branch.
|
||||
|
||||
on:
|
||||
create:
|
||||
push:
|
||||
branches:
|
||||
- 'release/**'
|
||||
- 'release/v*'
|
||||
|
||||
jobs:
|
||||
#
|
||||
|
||||
@@ -12,15 +12,11 @@ import uuid
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from PySide6.QtCore import (
|
||||
Slot, QDateTime
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QLabel, QDialog, QWidget, QSpinBox,
|
||||
QHBoxLayout, QGridLayout, QDateTimeEdit
|
||||
)
|
||||
from PySide6.QtCore import Slot, QDateTime
|
||||
from PySide6.QtWidgets import QLabel, QDialog, QWidget, QSpinBox, QHBoxLayout, QGridLayout, QDateTimeEdit
|
||||
|
||||
from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog
|
||||
import utils.TimerUtils as TimerUtils
|
||||
|
||||
|
||||
class ALTimerTaskStatus(Enum):
|
||||
@@ -31,6 +27,7 @@ class ALTimerTaskStatus(Enum):
|
||||
EXECUTED = "已执行"
|
||||
ERROR = "执行失败"
|
||||
OUTDATED = "已过期"
|
||||
UNKNOWN = "未知"
|
||||
|
||||
|
||||
class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
@@ -43,8 +40,8 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setupUi(self)
|
||||
self.connectSignals()
|
||||
self.modifyUi()
|
||||
self.connectSignals()
|
||||
|
||||
|
||||
def modifyUi(
|
||||
@@ -64,28 +61,28 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
self.TimerConfigLayout.addWidget(self.SpecificTimerWidget)
|
||||
|
||||
self.RelativeTimerWidget = QWidget()
|
||||
self.RelativeTimerLayout = QGridLayout(self.RelativeTimerWidget)
|
||||
self.RelativeTimerLayout.addWidget(QLabel("相对时间:"), 0, 0)
|
||||
self.RelativeTimerLayout = QHBoxLayout(self.RelativeTimerWidget)
|
||||
self.RelativeTimerLayout.addWidget(QLabel("相对时间:"))
|
||||
self.RelativeDaySpinBox = QSpinBox()
|
||||
self.RelativeDaySpinBox.setMinimum(0)
|
||||
self.RelativeDaySpinBox.setMaximum(365)
|
||||
self.RelativeDaySpinBox.setMaximum(364)
|
||||
self.RelativeDaySpinBox.setSuffix("天")
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeDaySpinBox, 1, 0)
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeDaySpinBox)
|
||||
self.RelativeHourSpinBox = QSpinBox()
|
||||
self.RelativeHourSpinBox.setMinimum(0)
|
||||
self.RelativeHourSpinBox.setMaximum(23)
|
||||
self.RelativeHourSpinBox.setSuffix("时")
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeHourSpinBox, 1, 1)
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeHourSpinBox)
|
||||
self.RelativeMinuteSpinBox = QSpinBox()
|
||||
self.RelativeMinuteSpinBox.setMinimum(0)
|
||||
self.RelativeMinuteSpinBox.setMaximum(59)
|
||||
self.RelativeMinuteSpinBox.setSuffix("分")
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeMinuteSpinBox, 1, 2)
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeMinuteSpinBox)
|
||||
self.RelativeSecondSpinBox = QSpinBox()
|
||||
self.RelativeSecondSpinBox.setMinimum(0)
|
||||
self.RelativeSecondSpinBox.setMaximum(59)
|
||||
self.RelativeSecondSpinBox.setSuffix("秒")
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox, 1, 3)
|
||||
self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox)
|
||||
self.TimerConfigLayout.addWidget(self.RelativeTimerWidget)
|
||||
self.RelativeTimerWidget.setVisible(False)
|
||||
|
||||
@@ -97,6 +94,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
self.CancelButton.clicked.connect(self.reject)
|
||||
self.ConfirmButton.clicked.connect(self.accept)
|
||||
self.TimerTypeComboBox.currentIndexChanged.connect(self.onTimerTypeComboBoxIndexChanged)
|
||||
self.RepeatCheckBox.toggled.connect(self.onRepeatCheckBoxToggled)
|
||||
|
||||
|
||||
def getTimerTask(
|
||||
@@ -121,7 +119,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
minutes = self.RelativeMinuteSpinBox.value(),
|
||||
seconds = self.RelativeSecondSpinBox.value()
|
||||
)
|
||||
return {
|
||||
task_data = {
|
||||
"name": name,
|
||||
"task_uuid": uuid.uuid4().hex.upper() + f"-{added_time.strftime("%Y%m%d%H%M%S")}",
|
||||
"time_type": self.TimerTypeComboBox.currentText(),
|
||||
@@ -129,9 +127,39 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
"silent": silent,
|
||||
"add_time": added_time,
|
||||
"status": ALTimerTaskStatus.PENDING,
|
||||
"executed": False
|
||||
"executed": False,
|
||||
"repeat": self.RepeatCheckBox.isChecked(),
|
||||
}
|
||||
if task_data["repeat"]:
|
||||
repeat_days = []
|
||||
if self.MonCheckBox.isChecked():
|
||||
repeat_days.append(0)
|
||||
if self.TueCheckBox.isChecked():
|
||||
repeat_days.append(1)
|
||||
if self.WedCheckBox.isChecked():
|
||||
repeat_days.append(2)
|
||||
if self.ThuCheckBox.isChecked():
|
||||
repeat_days.append(3)
|
||||
if self.FriCheckBox.isChecked():
|
||||
repeat_days.append(4)
|
||||
if self.SatCheckBox.isChecked():
|
||||
repeat_days.append(5)
|
||||
if self.SunCheckBox.isChecked():
|
||||
repeat_days.append(6)
|
||||
if not repeat_days:
|
||||
repeat_days = [0, 1, 2, 3, 4, 5, 6]
|
||||
task_data["repeat_days"] = repeat_days
|
||||
task_data["repeat_hour"] = execute_time.hour
|
||||
task_data["repeat_minute"] = execute_time.minute
|
||||
task_data["repeat_second"] = execute_time.second
|
||||
task_data["execute_time"] = TimerUtils.calculateNextRepeatTime(
|
||||
task_data["repeat_days"],
|
||||
task_data["repeat_hour"],
|
||||
task_data["repeat_minute"],
|
||||
task_data["repeat_second"]
|
||||
)
|
||||
|
||||
return task_data
|
||||
|
||||
@Slot(int)
|
||||
def onTimerTypeComboBoxIndexChanged(
|
||||
@@ -141,3 +169,17 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||
|
||||
self.SpecificTimerWidget.setVisible(index == 0)
|
||||
self.RelativeTimerWidget.setVisible(index == 1)
|
||||
|
||||
@Slot(bool)
|
||||
def onRepeatCheckBoxToggled(
|
||||
self,
|
||||
checked: bool
|
||||
):
|
||||
|
||||
self.MonCheckBox.setEnabled(checked)
|
||||
self.TueCheckBox.setEnabled(checked)
|
||||
self.WedCheckBox.setEnabled(checked)
|
||||
self.ThuCheckBox.setEnabled(checked)
|
||||
self.FriCheckBox.setEnabled(checked)
|
||||
self.SatCheckBox.setEnabled(checked)
|
||||
self.SunCheckBox.setEnabled(checked)
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2026 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 datetime import datetime
|
||||
|
||||
from PySide6.QtCore import Slot, Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QTableWidget, QTableWidgetItem,
|
||||
QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QPushButton, QLabel, QHeaderView
|
||||
)
|
||||
|
||||
from gui.ALTimerTaskAddDialog import ALTimerTaskStatus
|
||||
|
||||
|
||||
class ALTimerTaskHistoryDialog(QDialog):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent = None,
|
||||
task_data: dict = None
|
||||
):
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
self.__task_data = task_data
|
||||
self.__history = task_data.get("history", [])
|
||||
|
||||
self.modifyUi()
|
||||
self.connectSignals()
|
||||
|
||||
|
||||
def modifyUi(
|
||||
self
|
||||
):
|
||||
|
||||
self.setWindowTitle("定时任务执行历史 - AutoLibrary")
|
||||
self.setMinimumSize(300, 300)
|
||||
self.setMaximumSize(500, 400)
|
||||
|
||||
MainLayout = QVBoxLayout(self)
|
||||
InfoLayout = QGridLayout()
|
||||
TaskNameLabel = QLabel(f"任务: {self.__task_data.get('name', '未命名')}")
|
||||
TaskNameLabel.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||
InfoLayout.addWidget(TaskNameLabel, 0, 0)
|
||||
TaskUUIDLabel = QLabel(f"UUID: {self.__task_data.get('task_uuid', '未命名')}")
|
||||
TaskUUIDLabel.setStyleSheet("font-size: 10px;")
|
||||
InfoLayout.addWidget(TaskUUIDLabel, 1, 0)
|
||||
InfoLayout.setColumnStretch(0, 1)
|
||||
|
||||
if self.__task_data.get("repeat", False):
|
||||
RepeatLabel = QLabel("重复任务")
|
||||
RepeatLabel.setStyleSheet("color: #2294FF; font-weight: bold; font-size: 12px;")
|
||||
InfoLayout.addWidget(RepeatLabel, 0, 1)
|
||||
MainLayout.addLayout(InfoLayout)
|
||||
self.HistoryTableWidget = QTableWidget()
|
||||
self.HistoryTableWidget.setColumnCount(3)
|
||||
self.HistoryTableWidget.setHorizontalHeaderLabels(["执行时间", "结果", "耗时(秒/s)"])
|
||||
self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||
self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
||||
self.HistoryTableWidget.verticalHeader().setVisible(False)
|
||||
self.HistoryTableWidget.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
||||
self.HistoryTableWidget.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
||||
self.loadHistory()
|
||||
MainLayout.addWidget(self.HistoryTableWidget)
|
||||
|
||||
ButtonLayout = QHBoxLayout()
|
||||
ButtonLayout.addStretch()
|
||||
self.CloseButton = QPushButton("关闭")
|
||||
self.CloseButton.setFixedSize(80, 25)
|
||||
self.CloseButton.setDefault(True)
|
||||
self.ClearHistoryButton = QPushButton("清空历史")
|
||||
self.ClearHistoryButton.setFixedSize(80, 25)
|
||||
self.ClearHistoryButton.setStyleSheet("color: #DC0000;")
|
||||
ButtonLayout.addWidget(self.ClearHistoryButton)
|
||||
ButtonLayout.addWidget(self.CloseButton)
|
||||
MainLayout.addLayout(ButtonLayout)
|
||||
|
||||
|
||||
def connectSignals(
|
||||
self
|
||||
):
|
||||
|
||||
self.CloseButton.clicked.connect(self.accept)
|
||||
self.ClearHistoryButton.clicked.connect(self.onClearHistoryButtonClicked)
|
||||
|
||||
|
||||
def loadHistory(
|
||||
self
|
||||
):
|
||||
|
||||
self.HistoryTableWidget.setRowCount(len(self.__history))
|
||||
for row, record in enumerate(self.__history):
|
||||
self.addHistoryRow(row, record)
|
||||
|
||||
|
||||
def addHistoryRow(
|
||||
self,
|
||||
row: int,
|
||||
record: dict
|
||||
):
|
||||
|
||||
execute_time = record.get("execute_time", "")
|
||||
result = record.get("result", ALTimerTaskStatus.UNKNOWN)
|
||||
duration = record.get("duration", 0)
|
||||
ExecuteTimeItem = QTableWidgetItem(execute_time)
|
||||
ExecuteTimeItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.HistoryTableWidget.setItem(row, 0, ExecuteTimeItem)
|
||||
ResultItem = QTableWidgetItem(result.value)
|
||||
ResultItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
match result:
|
||||
case ALTimerTaskStatus.EXECUTED:
|
||||
ResultItem.setForeground(Qt.GlobalColor.green)
|
||||
case ALTimerTaskStatus.ERROR:
|
||||
ResultItem.setForeground(Qt.GlobalColor.red)
|
||||
case ALTimerTaskStatus.OUTDATED:
|
||||
ResultItem.setForeground(Qt.GlobalColor.red)
|
||||
case _:
|
||||
ResultItem.setForeground(Qt.GlobalColor.black)
|
||||
self.HistoryTableWidget.setItem(row, 1, ResultItem)
|
||||
DurationItem = QTableWidgetItem(f"{duration:.2f}")
|
||||
DurationItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.HistoryTableWidget.setItem(row, 2, DurationItem)
|
||||
self.HistoryTableWidget.setRowHeight(row, 25)
|
||||
|
||||
@Slot()
|
||||
def onClearHistoryButtonClicked(
|
||||
self
|
||||
):
|
||||
|
||||
self.__history.clear()
|
||||
self.HistoryTableWidget.setRowCount(0)
|
||||
self.__task_data["history"] = self.__history
|
||||
|
||||
|
||||
def getHistory(
|
||||
self
|
||||
) -> list:
|
||||
|
||||
return self.__history
|
||||
@@ -26,9 +26,11 @@ from PySide6.QtGui import (
|
||||
)
|
||||
|
||||
import utils.ConfigManager as ConfigManager
|
||||
import utils.TimerUtils as TimerUtils
|
||||
|
||||
from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget
|
||||
from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus
|
||||
from gui.ALTimerTaskHistoryDialog import ALTimerTaskHistoryDialog
|
||||
|
||||
|
||||
class ALTimerTaskItemWidget(QWidget):
|
||||
@@ -61,13 +63,25 @@ class ALTimerTaskItemWidget(QWidget):
|
||||
TaskNameLabel.setFont(TaskNameLabelFont)
|
||||
TaskNameLabel.setFixedHeight(25)
|
||||
self.TaskInfoLayout.addWidget(TaskNameLabel)
|
||||
|
||||
ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
if self.__timer_task.get("repeat", False):
|
||||
repeat_days = self.__timer_task.get("repeat_days", [])
|
||||
repeat_hour = self.__timer_task.get("repeat_hour", 0)
|
||||
repeat_minute = self.__timer_task.get("repeat_minute", 0)
|
||||
repeat_second = self.__timer_task.get("repeat_second", 0)
|
||||
if len(repeat_days) == 7:
|
||||
time_str = f"{repeat_hour:02d}:{repeat_minute:02d}:{repeat_second:02d}"
|
||||
ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每日 {time_str})")
|
||||
else:
|
||||
day_names = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
selected_days = [day_names[d] for d in repeat_days]
|
||||
time_str = f"{repeat_hour:02d}:{repeat_minute:02d}:{repeat_second:02d}"
|
||||
ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每{','.join(selected_days)} {time_str})")
|
||||
else:
|
||||
ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}")
|
||||
ExecuteTimeLabel.setStyleSheet("color: #969696;")
|
||||
ExecuteTimeLabel.setFixedHeight(20)
|
||||
self.TaskInfoLayout.addWidget(ExecuteTimeLabel)
|
||||
|
||||
self.ItemWidgetLayout.addLayout(self.TaskInfoLayout)
|
||||
self.ItemWidgetLayout.addStretch()
|
||||
|
||||
@@ -118,8 +132,13 @@ class ALTimerTaskItemWidget(QWidget):
|
||||
TaskModeLabel.setFixedSize(60, 25)
|
||||
self.ItemWidgetLayout.addWidget(TaskModeLabel)
|
||||
|
||||
if self.__timer_task.get("repeat", False):
|
||||
self.HistoryButton = QPushButton("历史")
|
||||
self.HistoryButton.setFixedSize(80, 25)
|
||||
self.ItemWidgetLayout.addWidget(self.HistoryButton)
|
||||
self.DeleteButton = QPushButton("删除")
|
||||
self.DeleteButton.setFixedSize(80, 25)
|
||||
self.DeleteButton.setStyleSheet("color: #DC0000;")
|
||||
self.ItemWidgetLayout.addWidget(self.DeleteButton)
|
||||
if self.__timer_task["status"] == ALTimerTaskStatus.READY\
|
||||
or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING:
|
||||
@@ -205,6 +224,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
task["add_time"] = datetime.strptime(task["add_time"], "%Y-%m-%d %H:%M:%S")
|
||||
task["execute_time"] = datetime.strptime(task["execute_time"], "%Y-%m-%d %H:%M:%S")
|
||||
task["status"] = ALTimerTaskStatus(task["status"])
|
||||
if "history" in task:
|
||||
for item in task["history"]:
|
||||
item["result"] = ALTimerTaskStatus(item["result"])
|
||||
return timer_tasks["timer_tasks"]
|
||||
raise Exception("定时任务配置文件格式错误")
|
||||
except Exception as e:
|
||||
@@ -226,6 +248,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
task["add_time"] = task["add_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
task["execute_time"] = task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
task["status"] = task["status"].value
|
||||
if "history" in task:
|
||||
for item in task["history"]:
|
||||
item["result"] = item["result"].value
|
||||
self.__cfg_mgr.set(ConfigManager.ConfigType.TIMERTASK, "", { "timer_tasks": timer_tasks })
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -332,7 +357,11 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
item.setData(Qt.UserRole, timer_task)
|
||||
widget = ALTimerTaskItemWidget(self, timer_task)
|
||||
widget.DeleteButton.clicked.connect(
|
||||
lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid)
|
||||
lambda _, task = timer_task: self.deleteTask(task)
|
||||
)
|
||||
if timer_task.get("repeat", False) and hasattr(widget, "HistoryButton"):
|
||||
widget.HistoryButton.clicked.connect(
|
||||
lambda _, task = timer_task: self.showTaskHistory(task)
|
||||
)
|
||||
item.setSizeHint(widget.size())
|
||||
self.TimerTasksListWidget.addItem(item)
|
||||
@@ -350,11 +379,42 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
|
||||
def deleteTask(
|
||||
self,
|
||||
task_uuid: str
|
||||
@staticmethod
|
||||
def getTimerTaskDetailMessage(
|
||||
timer_task: dict
|
||||
):
|
||||
|
||||
return (
|
||||
f"任务名称:{timer_task["name"]}\n"
|
||||
f"添加时间:{timer_task["add_time"]}\n"
|
||||
f"当前状态:{timer_task["status"].value}\n"
|
||||
f"下次执行时间:{datetime.strftime(timer_task["execute_time"], "%Y-%m-%d %H:%M:%S")}\n"
|
||||
f"已执行次数:{len(timer_task['history'] if 'history' in timer_task else 0)}"
|
||||
)
|
||||
|
||||
|
||||
def deleteTask(
|
||||
self,
|
||||
timer_task: dict
|
||||
):
|
||||
|
||||
if timer_task["repeat"]: # when delete a repeat task
|
||||
msgbox = QMessageBox(self)
|
||||
msgbox.setIcon(QMessageBox.Icon.Question)
|
||||
msgbox.setWindowTitle("警告 - AutoLibrary")
|
||||
msgbox.setStandardButtons(
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
msgbox.setText("删除可重复性任务将同时删除所有已执行的记录 !\n是否继续 ?")
|
||||
msgbox.setDetailedText(
|
||||
"以下可重复性任务将被删除:\n"\
|
||||
"\n"
|
||||
f"{self.getTimerTaskDetailMessage(timer_task)}"
|
||||
)
|
||||
result = msgbox.exec()
|
||||
if result != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
task_uuid = timer_task["task_uuid"]
|
||||
self.__timer_tasks = [
|
||||
x for x in self.__timer_tasks
|
||||
if x["task_uuid"] != task_uuid
|
||||
@@ -374,8 +434,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
"是否要清除所有定时任务 ?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
if result is QMessageBox.StandardButton.No:
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return
|
||||
# READY and RUNNING tasks cannot be cleared
|
||||
in_queue_tasks = [
|
||||
x for x in self.__timer_tasks
|
||||
if x["status"] == ALTimerTaskStatus.READY
|
||||
@@ -386,9 +447,50 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"警告 - AutoLibrary",
|
||||
"存在正在执行或已就绪的队列任务,无法清除所有定时任务 !"
|
||||
f"存在 {in_queue_count} 个正在执行或已就绪的队列任务,无法清除所有定时任务 !"
|
||||
)
|
||||
self.__timer_tasks = in_queue_tasks
|
||||
return
|
||||
# repeat tasks ask before clear
|
||||
repeat_tasks = [
|
||||
x for x in self.__timer_tasks
|
||||
if x.get("repeat", False)
|
||||
]
|
||||
repeat_tasks_count = len(repeat_tasks)
|
||||
if repeat_tasks_count > 0:
|
||||
msgbox = QMessageBox(self)
|
||||
msgbox.setIcon(QMessageBox.Icon.Question)
|
||||
msgbox.setWindowTitle("警告 - AutoLibrary")
|
||||
msgbox.setStandardButtons(
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
msgbox.setText(
|
||||
f"存在 {repeat_tasks_count} 个可重复性任务,\n"
|
||||
"删除可重复性任务将同时删除所有已执行的记录 !\n"
|
||||
"是否继续 ?"
|
||||
)
|
||||
delete_msgs = [
|
||||
self.getTimerTaskDetailMessage(x) for x in repeat_tasks
|
||||
]
|
||||
msgbox.setDetailedText(
|
||||
"以下可重复性任务将被删除:\n"\
|
||||
"\n"
|
||||
f"{"\n\n".join(delete_msgs)}"
|
||||
)
|
||||
result = msgbox.exec()
|
||||
if result != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
# clear all tasks
|
||||
self.__timer_tasks.clear()
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
|
||||
def showTaskHistory(
|
||||
self,
|
||||
task: dict
|
||||
):
|
||||
|
||||
dialog = ALTimerTaskHistoryDialog(self, task)
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
|
||||
@@ -405,6 +507,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
if timer_task["status"] is not ALTimerTaskStatus.PENDING:
|
||||
continue
|
||||
if timer_task["execute_time"] <= now + timedelta(seconds = -5):
|
||||
if timer_task.get("repeat", False):
|
||||
self.onRepeatTimerTaskIs(ALTimerTaskStatus.OUTDATED, timer_task)
|
||||
else:
|
||||
timer_task["status"] = ALTimerTaskStatus.OUTDATED
|
||||
need_update = True
|
||||
else:
|
||||
@@ -460,9 +565,40 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
for task in self.__timer_tasks:
|
||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||
task["status"] = ALTimerTaskStatus.RUNNING
|
||||
break
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
|
||||
def onRepeatTimerTaskIs(
|
||||
self,
|
||||
status: ALTimerTaskStatus,
|
||||
timer_task: dict
|
||||
) -> dict:
|
||||
|
||||
if "history" not in timer_task:
|
||||
timer_task["history"] = []
|
||||
executed_time = datetime.now()
|
||||
duration = (executed_time - timer_task["execute_time"]).total_seconds()
|
||||
timer_task["history"].append({
|
||||
"execute_time": timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"result": status,
|
||||
"duration": duration if status is ALTimerTaskStatus.EXECUTED else 0,
|
||||
"uuid": timer_task["task_uuid"]
|
||||
})
|
||||
next_time = TimerUtils.calculateNextRepeatTime(
|
||||
timer_task["repeat_days"],
|
||||
timer_task["repeat_hour"],
|
||||
timer_task["repeat_minute"],
|
||||
timer_task["repeat_second"]
|
||||
)
|
||||
if next_time:
|
||||
timer_task["execute_time"] = next_time
|
||||
timer_task["status"] = ALTimerTaskStatus.PENDING
|
||||
timer_task["executed"] = False
|
||||
else:
|
||||
timer_task["status"] = status
|
||||
|
||||
@Slot(dict)
|
||||
def onTimerTaskIsExecuted(
|
||||
self,
|
||||
@@ -471,7 +607,11 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
|
||||
for task in self.__timer_tasks:
|
||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||
if task.get("repeat", False):
|
||||
self.onRepeatTimerTaskIs(ALTimerTaskStatus.EXECUTED, task)
|
||||
else:
|
||||
task["status"] = ALTimerTaskStatus.EXECUTED
|
||||
break
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
@Slot(dict)
|
||||
@@ -482,5 +622,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||
|
||||
for task in self.__timer_tasks:
|
||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||
if task.get("repeat", False):
|
||||
self.onRepeatTimerTaskIs(ALTimerTaskStatus.ERROR, task)
|
||||
else:
|
||||
task["status"] = ALTimerTaskStatus.ERROR
|
||||
break
|
||||
self.timerTasksChanged.emit()
|
||||
|
||||
@@ -195,6 +195,11 @@
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #DC0000;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>删除用户</string>
|
||||
</property>
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
<width>350</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>300</height>
|
||||
<width>350</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>300</height>
|
||||
<width>350</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -149,8 +149,20 @@
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="SilentlyRunRadioButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>静默运行</string>
|
||||
</property>
|
||||
@@ -168,13 +180,248 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="ShowBeforeRunRadioButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>运行前提示</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="RepeatConfigGroupBox">
|
||||
<property name="title">
|
||||
<string>重复运行</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="RepeatConfigLayout" stretch="1,1,1">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="RepeatCheckBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>启用重复执行</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>重复周期(全选或全不选都为每日运行):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="RepeatCheckBoxLayout" rowstretch="10,10" columnstretch="0,0,0,0" rowminimumheight="25,25">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="ThuCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周四</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="WedCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周三</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="MonCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周一</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="TueCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周二</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="FriCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周五</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="SatCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周六</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="SunCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>周日</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>500</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<width>500</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<width>800</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
@@ -306,6 +306,11 @@
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #DC0000;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>清除全部</string>
|
||||
</property>
|
||||
|
||||
@@ -88,6 +88,31 @@ class LibCheckin(LibOperator):
|
||||
return False
|
||||
|
||||
|
||||
def __enableCheckinBtn(
|
||||
self
|
||||
) -> bool:
|
||||
|
||||
script = """
|
||||
try {
|
||||
var checkin_btn = document.getElementById('btnCheckIn');
|
||||
if (checkin_btn) {
|
||||
checkin_btn.classList.remove('disabled');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
"""
|
||||
result = self.__driver.execute_script(script)
|
||||
time.sleep(0.1)
|
||||
if result:
|
||||
self._showTrace("签到按钮已启用")
|
||||
else:
|
||||
self._showTrace("签到按钮启用失败")
|
||||
return result
|
||||
|
||||
|
||||
def checkin(
|
||||
self,
|
||||
username: str
|
||||
@@ -104,7 +129,9 @@ class LibCheckin(LibOperator):
|
||||
self._showTrace(f"用户 {username} 签到界面加载失败 !")
|
||||
return False
|
||||
if "disabled" in checkin_btn.get_attribute("class"):
|
||||
self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试")
|
||||
self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......")
|
||||
if not self.__enableCheckinBtn():
|
||||
self._showTrace(f"签到按钮启用失败 !")
|
||||
return False
|
||||
checkin_btn.click()
|
||||
if self._waitResponseLoad():
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2025 - 2026 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 datetime import datetime, timedelta
|
||||
|
||||
|
||||
def calculateNextRepeatTime(
|
||||
repeat_days: list,
|
||||
hour: int,
|
||||
minute: int,
|
||||
second: int
|
||||
) -> datetime:
|
||||
"""
|
||||
Calculate the next repeat time based on repeat days and target time.
|
||||
|
||||
This function calculates the next execution time for a repeatable task.
|
||||
If the current day is in repeat_days and the target time has not passed,
|
||||
it returns today's target time. Otherwise, it finds the next matching day.
|
||||
|
||||
Args:
|
||||
repeat_days (list): List of weekdays to repeat (0=Monday, 6=Sunday).
|
||||
hour (int): Target hour (0-23).
|
||||
minute (int): Target minute (0-59).
|
||||
second (int): Target second (0-59).
|
||||
|
||||
Returns:
|
||||
datetime: The next repeat execution time.
|
||||
"""
|
||||
|
||||
current_time = datetime.now()
|
||||
current_weekday = current_time.weekday()
|
||||
target_time = current_time.replace(hour=hour, minute=minute, second=second, microsecond=0)
|
||||
if current_weekday in repeat_days:
|
||||
if target_time > current_time:
|
||||
return target_time
|
||||
repeat_days_sorted = sorted(repeat_days)
|
||||
for day in repeat_days_sorted:
|
||||
if day > current_weekday:
|
||||
days_until = day - current_weekday
|
||||
next_time = target_time + timedelta(days=days_until)
|
||||
return next_time
|
||||
days_until = 7 - current_weekday + repeat_days_sorted[0]
|
||||
next_time = target_time + timedelta(days=days_until)
|
||||
return next_time
|
||||
Reference in New Issue
Block a user