1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-08-02 06:09:36 +08:00

Compare commits

...

17 Commits

Author SHA1 Message Date
github-actions[bot] e4d838a9d0 chore(release): v1.4.0 [auto release commit] 2026-07-14 02:19:40 +00:00
Kenan Zhu 70f78c2a6f chore: 从 KenanZhu/develop 合并到主分支 (#14)
feat:    公告栏系统、定时任务重构、检查更新与多项缺陷修复
2026-07-14 08:42:35 +08:00
KenanZhu e40e7767c7 style(settings): 移除导入主题后冗余的状态刷新调用 2026-07-10 11:41:43 +08:00
KenanZhu 1b6c170d6c style(ReserveFlow): 提取容器类的UI设置到setupUi方法 2026-07-10 10:07:38 +08:00
KenanZhu 30a7a8e996 fix(settings): 修复测试公告栏连接时 QThread 未等待导致闪退 2026-07-09 09:29:45 +08:00
KenanZhu d59ecc3090 feat(update): 新增检查更新功能与菜单栏公告入口 2026-07-09 09:06:58 +08:00
KenanZhu 7685f6a616 style(ReserveFlow): 为 TimeSelectDialog 添加类型注释 2026-07-09 08:16:41 +08:00
KenanZhu ed8dec70a6 fix: 恢复 deleteLater() 替换错误的 delete() 调用 2026-06-26 11:16:09 +08:00
KenanZhu dfa9ae1a0c fix(config): ConfigManager.get() 返回深拷贝,消除跨模块字典原地修改 2026-06-26 11:16:09 +08:00
KenanZhu 06740776a9 fix(timer,bulletin): 修复定时任务与公告栏 5 项交叉审查问题 2026-06-26 11:16:08 +08:00
KenanZhu c8f9ae752f fix(bulletin): 修复托盘消息点击冲突,增加公告入口,移除托盘公告选项 2026-06-26 10:18:15 +08:00
KenanZhu 89c1e33257 chore: 更新官网手册链接 2026-06-26 10:00:02 +08:00
KenanZhu 862f86827a refactor(timer): ALMainWindow 集成 ALTimerTaskPoller 消除内联轮询 2026-06-26 09:58:39 +08:00
KenanZhu ed98623cda feat(timer): 新增 ALTimerTaskPoller 定时任务轮询器 2026-06-26 09:56:24 +08:00
KenanZhu ab51c72a11 style: 重命名 ALMainWorkers 为 ALMainWorker 遵循单数命名 2026-06-26 09:18:48 +08:00
KenanZhu 2fe4584129 refactor(bulletin): 提取 ALBulletinFetchWorker 到独立文件 2026-06-26 09:16:45 +08:00
KenanZhu 92c5fa0dfb style: 统一代码格式与导入优化 2026-06-26 09:16:23 +08:00
23 changed files with 872 additions and 329 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ import logging
import queue import queue
import datetime import datetime
import managers.log.LogManager as LogManager from managers.log.LogManager import getLogger
class MsgBase: class MsgBase:
@@ -54,7 +54,7 @@ class MsgBase:
self._input_queue = input_queue self._input_queue = input_queue
self._output_queue = output_queue self._output_queue = output_queue
try: try:
self._logger = LogManager.getLogger(self._class_name) self._logger = getLogger(self._class_name)
except RuntimeError: except RuntimeError:
self._logger = None self._logger = None
+2 -1
View File
@@ -73,6 +73,8 @@ def _initializeWebDriverManager(
def _initializeAppearance( def _initializeAppearance(
): ):
logger = logInstance().getLogger("AppInitializer")
app = QApplication.instance() app = QApplication.instance()
if not app: if not app:
return return
@@ -82,7 +84,6 @@ def _initializeAppearance(
saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "") saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
app.setStyle(saved_style) app.setStyle(saved_style)
setActiveStyle(saved_style) setActiveStyle(saved_style)
logger = logInstance().getLogger("AppInitializer")
if saved_custom_theme: if saved_custom_theme:
try: try:
themeInstance().applyTheme(saved_custom_theme) themeInstance().applyTheme(saved_custom_theme)
+22 -3
View File
@@ -104,6 +104,7 @@ class _DateInputContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.setupUi() self.setupUi()
def setupUi( def setupUi(
@@ -153,10 +154,16 @@ class _TimeInputContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.setupUi()
def setupUi(
self
):
self.TimeEdit = QTimeEdit(self) self.TimeEdit = QTimeEdit(self)
self.TimeEdit.setDisplayFormat("HH:mm") self.TimeEdit.setDisplayFormat("HH:mm")
self.TimeEdit.setFixedHeight(25) self.TimeEdit.setFixedHeight(25)
Layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
Layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
Layout.addWidget(self.TimeEdit) Layout.addWidget(self.TimeEdit)
@@ -176,6 +183,13 @@ class _DateOffsetContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.setupUi()
def setupUi(
self
):
self.SpinBox = QSpinBox(self) self.SpinBox = QSpinBox(self)
self.SpinBox.setRange(0, 99999) self.SpinBox.setRange(0, 99999)
self.SpinBox.setFixedHeight(25) self.SpinBox.setFixedHeight(25)
@@ -183,7 +197,6 @@ class _DateOffsetContainer(QWidget):
for display, data in DATE_OFFSET_OPTIONS: for display, data in DATE_OFFSET_OPTIONS:
self.UnitCombo.addItem(display, data) self.UnitCombo.addItem(display, data)
self.UnitCombo.setFixedHeight(25) self.UnitCombo.setFixedHeight(25)
Layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
Layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
Layout.setSpacing(4) Layout.setSpacing(4)
@@ -220,11 +233,17 @@ class _TimeOffsetContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self.setupUi()
def setupUi(
self
):
self.SpinBox = QSpinBox(self) self.SpinBox = QSpinBox(self)
self.SpinBox.setRange(0, 99999) self.SpinBox.setRange(0, 99999)
self.SpinBox.setSuffix(" 小时") self.SpinBox.setSuffix(" 小时")
self.SpinBox.setFixedHeight(25) self.SpinBox.setFixedHeight(25)
Layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
Layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
Layout.addWidget(self.SpinBox) Layout.addWidget(self.SpinBox)
+5 -47
View File
@@ -7,14 +7,11 @@ 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. You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details. See the LICENSE file for details.
""" """
import requests
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any
from PySide6.QtCore import ( from PySide6.QtCore import (
Qt, Qt,
QThread,
Signal, Signal,
Slot, Slot,
QTimer QTimer
@@ -32,53 +29,12 @@ from PySide6.QtWidgets import (
QWidget QWidget
) )
from gui.ALBulletinWorker import ALBulletinFetchWorker
from gui.ALStatusLabel import ALStatusLabel from gui.ALStatusLabel import ALStatusLabel
from gui.resources.ui.Ui_ALBulletinDialog import Ui_ALBulletinDialog from gui.resources.ui.Ui_ALBulletinDialog import Ui_ALBulletinDialog
from managers.bulletin.BulletinManager import instance as bulletinInstance from managers.bulletin.BulletinManager import instance as bulletinInstance
class ALBulletinFetchWorker(QThread):
"""
Worker thread for fetching bulletins from the server.
"""
fetchWorkerIsFinished = Signal(dict)
fetchWorkerFinishedWithError = Signal(str)
def __init__(
self,
parent=None,
request_url: str = "",
params: dict = None
):
super().__init__(parent)
self.__request_url = request_url.rstrip("/") if request_url else ""
self.__params = params or {}
def run(
self
):
try:
response = requests.get(
self.__request_url, params=self.__params, timeout=5
)
response.raise_for_status()
r = response.json()
if r.get("code") == 200:
data = r.get("data", {})
self.fetchWorkerIsFinished.emit(data)
else:
raise Exception(
f"服务器返回错误: [{r.get('code', '未知代码')}] {r.get('msg', '未知错误')}"
)
except requests.RequestException as e:
self.fetchWorkerFinishedWithError.emit(f"获取公告数据时发生网络错误: \n{e}")
except Exception as e:
self.fetchWorkerFinishedWithError.emit(f"获取公告数据时发生未知错误: \n{e}")
class ALBulletinItemWidget(QWidget): class ALBulletinItemWidget(QWidget):
""" """
Single bulletin item widget for the bulletin list. Single bulletin item widget for the bulletin list.
@@ -216,6 +172,9 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
self, self,
iso_str: str iso_str: str
): ):
"""
Set last sync time to update the UI status.
"""
try: try:
dt = datetime.fromisoformat(iso_str) dt = datetime.fromisoformat(iso_str)
@@ -385,10 +344,9 @@ class ALBulletinDialog(QDialog, Ui_ALBulletinDialog):
return return
bulletin = item.data(Qt.UserRole) bulletin = item.data(Qt.UserRole)
if bulletin.get("isNew", False): if bulletin.get("isNew", False):
bulletin["isNew"] = False self.__bulletin_mgr.markBulletinAsRead(bulletin["id"])
widget = self.BulletinListWidget.itemWidget(item) widget = self.BulletinListWidget.itemWidget(item)
if widget: if widget:
widget.markAsRead() widget.markAsRead()
item.setData(Qt.UserRole, bulletin) item.setData(Qt.UserRole, bulletin)
self.__bulletin_mgr.markBulletinAsRead(bulletin["id"])
self.showBulletin(bulletin) self.showBulletin(bulletin)
+5 -5
View File
@@ -16,7 +16,7 @@ from PySide6.QtCore import (
Slot Slot
) )
from gui.ALBulletinDialog import ALBulletinFetchWorker from gui.ALBulletinWorker import ALBulletinFetchWorker
from managers.bulletin.BulletinManager import instance as bulletinInstance from managers.bulletin.BulletinManager import instance as bulletinInstance
@@ -112,7 +112,7 @@ class ALBulletinPoller(QObject):
if self.__worker is None: if self.__worker is None:
return return
self.__disconnectWorker(self.__worker) self.__disconnectWorker(self.__worker)
self.__worker.wait(2000) self.__worker.wait(500)
self.__worker.deleteLater() self.__worker.deleteLater()
self.__worker = None self.__worker = None
@@ -149,7 +149,7 @@ class ALBulletinPoller(QObject):
if worker is not self.__worker: if worker is not self.__worker:
return return
self.__disconnectWorker(worker) self.__disconnectWorker(worker)
worker.wait(2000) worker.wait(500)
worker.deleteLater() worker.deleteLater()
self.__worker = None self.__worker = None
old_ids = {str(b.get("id", "")) for b in self.__mgr.bulletins()} old_ids = {str(b.get("id", "")) for b in self.__mgr.bulletins()}
@@ -164,7 +164,7 @@ class ALBulletinPoller(QObject):
if str(b.get("id", "")) and str(b.get("id", "")) not in old_ids if str(b.get("id", "")) and str(b.get("id", "")) not in old_ids
} }
new_ids -= delete_id_set new_ids -= delete_id_set
if new_ids: if new_ids and not self.__dialog_open:
self.newBulletinsDetected.emit(len(new_ids)) self.newBulletinsDetected.emit(len(new_ids))
if not self.__stopped: if not self.__stopped:
interval_ms = self.__mgr.syncInterval()*60*1000 interval_ms = self.__mgr.syncInterval()*60*1000
@@ -180,7 +180,7 @@ class ALBulletinPoller(QObject):
if worker is not self.__worker: if worker is not self.__worker:
return return
self.__disconnectWorker(worker) self.__disconnectWorker(worker)
worker.wait(2000) worker.wait(500)
worker.deleteLater() worker.deleteLater()
self.__worker = None self.__worker = None
if not self.__stopped: if not self.__stopped:
+57
View File
@@ -0,0 +1,57 @@
# -*- 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.
"""
import requests
from PySide6.QtCore import (
QThread,
Signal
)
class ALBulletinFetchWorker(QThread):
"""
Worker thread for fetching bulletins from the server.
"""
fetchWorkerIsFinished = Signal(dict)
fetchWorkerFinishedWithError = Signal(str)
def __init__(
self,
parent=None,
request_url: str = "",
params: dict = None
):
super().__init__(parent)
self.__request_url = request_url.rstrip("/") if request_url else ""
self.__params = params or {}
def run(
self
):
try:
response = requests.get(
self.__request_url, params=self.__params, timeout=5
)
response.raise_for_status()
r = response.json()
if r.get("code") == 200:
data = r.get("data", {})
self.fetchWorkerIsFinished.emit(data)
else:
raise Exception(
f"服务器返回错误: [{r.get('code', '未知代码')}] {r.get('msg', '未知错误')}"
)
except requests.RequestException as e:
self.fetchWorkerFinishedWithError.emit(f"获取公告数据时发生网络错误: \n{e}")
except Exception as e:
self.fetchWorkerFinishedWithError.emit(f"获取公告数据时发生未知错误: \n{e}")
+192
View File
@@ -0,0 +1,192 @@
# -*- 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 PySide6.QtCore import Qt
from PySide6.QtGui import QDesktopServices
from PySide6.QtCore import QUrl
from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QLabel,
QVBoxLayout
)
class ALCheckUpdateDialog(QDialog):
"""
Dialog for showing update check result.
Supports two layouts:
- has_update=True : version comparison with green text + action buttons
- has_update=False: "already up-to-date" message + close button
"""
def __init__(
self,
parent=None,
has_update: bool = False,
current_version: str = "",
latest_version: str = "",
tag_name: str = "",
html_url: str = ""
):
super().__init__(parent)
self.__has_update = has_update
self.__html_url = html_url
self.__current_version = current_version
self.__latest_version = latest_version
self.__tag_name = tag_name
self.modifyUi()
self.connectSignals()
def modifyUi(
self
):
self.setWindowTitle("检查更新 - AutoLibrary")
self.setMinimumWidth(360)
Layout = QVBoxLayout(self)
Layout.setSpacing(12)
Layout.setContentsMargins(20, 20, 20, 20)
if self.__has_update:
self.buildUpdateAvailableUi(Layout)
else:
self.buildUpToDateUi(Layout)
def buildUpdateAvailableUi(
self,
layout: QVBoxLayout
):
TitleLabel = QLabel()
TitleLabel.setStyleSheet(
"font-size: 14px;"
"font-weight: bold;"
)
TitleLabel.setTextFormat(Qt.TextFormat.RichText)
TitleLabel.setText(
f"检测到新版本: "
f"<span>{self.__current_version}</span> "
f"<span>&gt;</span> "
f"<span style='color: green; font-weight: bold;'>{self.__latest_version}</span>"
)
layout.addWidget(TitleLabel)
InfoLabel = QLabel()
InfoLabel.setTextFormat(Qt.TextFormat.RichText)
InfoLabel.setWordWrap(True)
InfoLabel.setText(
f"最新版本: <b>{self.__tag_name}</b><br>"
)
InfoLabel.setOpenExternalLinks(True)
layout.addWidget(InfoLabel)
layout.addSpacing(8)
self.__button_box = QDialogButtonBox()
self.__btn_github = self.__button_box.addButton(
"前往 GitHub",
QDialogButtonBox.ButtonRole.AcceptRole
)
self.__btn_github.setMinimumWidth(100)
self.__btn_github.setMaximumWidth(100)
self.__btn_github.setMinimumHeight(25)
self.__btn_github.setMaximumHeight(25)
self.__btn_download = self.__button_box.addButton(
"官网下载",
QDialogButtonBox.ButtonRole.ActionRole
)
self.__btn_download.setMinimumWidth(80)
self.__btn_download.setMaximumWidth(80)
self.__btn_download.setMinimumHeight(25)
self.__btn_download.setMaximumHeight(25)
self.__btn_cancel = self.__button_box.addButton(
"取消",
QDialogButtonBox.ButtonRole.RejectRole
)
self.__btn_cancel.setMinimumWidth(80)
self.__btn_cancel.setMaximumWidth(80)
self.__btn_cancel.setMinimumHeight(25)
self.__btn_cancel.setMaximumHeight(25)
layout.addWidget(self.__button_box)
def buildUpToDateUi(
self,
layout: QVBoxLayout
):
TitleLabel = QLabel()
TitleLabel.setStyleSheet(
"font-size: 14px;"
"font-weight: bold;"
)
TitleLabel.setText(f"已是最新版本 !")
layout.addWidget(TitleLabel)
InfoLabel = QLabel()
InfoLabel.setText(
f"当前版本: <b>{self.__current_version}</b><br>"
)
layout.addWidget(InfoLabel)
layout.addStretch()
self.__button_box = QDialogButtonBox()
self.__btn_close = self.__button_box.addButton(
"确定",
QDialogButtonBox.ButtonRole.AcceptRole
)
self.__btn_close.setMinimumWidth(80)
self.__btn_close.setMaximumWidth(80)
self.__btn_close.setMinimumHeight(25)
self.__btn_close.setMaximumHeight(25)
layout.addWidget(self.__button_box)
def connectSignals(
self
):
if self.__has_update:
self.__btn_github.clicked.connect(self.onGoToGitHub)
self.__btn_download.clicked.connect(self.onGoToDownload)
self.__btn_cancel.clicked.connect(self.reject)
else:
self.__btn_close.clicked.connect(self.accept)
def onGoToGitHub(
self
):
QDesktopServices.openUrl(QUrl(self.__html_url))
self.accept()
def onGoToDownload(
self
):
QDesktopServices.openUrl(
QUrl("https://www.autolibrary.kenanzhu.com/downloads")
)
self.accept()
@staticmethod
def showResult(
parent,
has_update: bool,
current_version: str,
latest_version: str = "",
tag_name: str = "",
html_url: str = ""
):
dialog = ALCheckUpdateDialog(
parent,
has_update=has_update,
current_version=current_version,
latest_version=latest_version,
tag_name=tag_name,
html_url=html_url
)
dialog.exec()
+51
View File
@@ -0,0 +1,51 @@
# -*- 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.
"""
import requests
from PySide6.QtCore import (
QThread,
Signal
)
class ALCheckUpdateWorker(QThread):
"""
Worker thread for checking latest release from GitHub API.
"""
checkUpdateWorkerIsFinished = Signal(dict)
checkUpdateWorkerFinishedWithError = Signal(str)
def __init__(
self,
parent=None
):
super().__init__(parent)
self.__api_url = "https://api.github.com/repos/KenanZhu/AutoLibrary/releases/latest"
def run(
self
):
try:
response = requests.get(self.__api_url, timeout=10)
response.raise_for_status()
data = response.json()
self.checkUpdateWorkerIsFinished.emit({
"tag_name": data.get("tag_name", ""),
"name": data.get("name", ""),
"html_url": data.get("html_url", ""),
"body": data.get("body", ""),
})
except requests.RequestException as e:
self.checkUpdateWorkerFinishedWithError.emit(f"网络请求失败: \n{e}")
except Exception as e:
self.checkUpdateWorkerFinishedWithError.emit(f"检查更新时发生未知错误: \n{e}")
+222 -167
View File
@@ -9,11 +9,12 @@ See the LICENSE file for details.
""" """
import queue import queue
import packaging.version as ver
from PySide6.QtCore import ( from PySide6.QtCore import (
QTimer, QTimer,
QUrl, QUrl,
Qt, Qt,
Signal,
Slot Slot
) )
from PySide6.QtGui import ( from PySide6.QtGui import (
@@ -33,14 +34,15 @@ from PySide6.QtWidgets import (
from base.MsgBase import MsgBase from base.MsgBase import MsgBase
from gui.ALAboutDialog import ALAboutDialog from gui.ALAboutDialog import ALAboutDialog
from gui.ALBulletinDialog import ALBulletinDialog from gui.ALBulletinDialog import ALBulletinDialog
from gui.ALBulletinPoller import ALBulletinPoller from gui.ALCheckUpdateWorker import ALCheckUpdateWorker
from gui.ALCheckUpdateDialog import ALCheckUpdateDialog
from gui.ALConfigWidget import ALConfigWidget from gui.ALConfigWidget import ALConfigWidget
from gui.ALSettingsWidget import ALSettingsWidget from gui.ALSettingsWidget import ALSettingsWidget
from gui.ALMainWorkers import (
AutoLibWorker,
TimerTaskWorker
)
from gui.ALTimerTaskManageWidget import ALTimerTaskManageWidget from gui.ALTimerTaskManageWidget import ALTimerTaskManageWidget
from gui.ALMainWorker import AutoLibWorker
from gui.ALBulletinPoller import ALBulletinPoller
from gui.ALTimerTaskPoller import ALTimerTaskPoller
from gui.ALVersionInfo import AL_VERSION
from gui.resources import ALResource from gui.resources import ALResource
from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow
from managers.bulletin.BulletinManager import instance as bulletinInstance from managers.bulletin.BulletinManager import instance as bulletinInstance
@@ -49,39 +51,36 @@ from managers.config.ConfigUtils import ConfigUtils
class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow): class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
# signal : timer task
timerTaskIsRunning = Signal(dict)
timerTaskIsExecuted = Signal(dict)
timerTaskIsError = Signal(dict)
def __init__( def __init__(
self self
): ):
MsgBase.__init__(self, queue.Queue(), queue.Queue()) MsgBase.__init__(self, queue.Queue(), queue.Queue())
QMainWindow.__init__(self) QMainWindow.__init__(self)
self.__timer_task_queue = queue.Queue()
self.__config_paths = ConfigUtils.getAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__ALTimerTaskManageWidget = None self.__ALTimerTaskManageWidget = None
self.__ALConfigWidget = None self.__ALConfigWidget = None
self.__ALSettingsWidget = None self.__ALSettingsWidget = None
self.__ALBulletinDialog = None self.__ALBulletinDialog = None
self.__bulletin_poller = ALBulletinPoller(self)
self.__auto_lib_thread = None self.__auto_lib_thread = None
self.__current_timer_task_thread = None self.__bulletin_poller = ALBulletinPoller(self)
self.__is_running_timer_task = False self.__timer_task_poller = ALTimerTaskPoller(
self,
self._input_queue,
self._output_queue,
self.__config_paths
)
self.__notification_type = ""
self.setupUi(self) self.setupUi(self)
self.modifyUi() self.modifyUi()
self.setupTray() self.setupTray()
self.connectSignals() self.connectSignals()
self.startMsgPolling() self.startMsgPolling()
self.startTimerTaskPolling() self.__timer_task_poller.start()
self.__bulletin_poller.start() self.__bulletin_poller.start()
if bulletinInstance().autoFetch(): if bulletinInstance().autoFetch():
QTimer.singleShot(1000, self.__bulletin_poller.fetchNow) QTimer.singleShot(1000, self.__bulletin_poller.fetchNow)
self._showLog("主窗口初始化完成") self._showLog("主窗口初始化完成")
def modifyUi( def modifyUi(
@@ -92,11 +91,10 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.setWindowIcon(self.Icon) self.setWindowIcon(self.Icon)
self.MessageIOTextEdit.setFont(QFont("Courier New", 10)) self.MessageIOTextEdit.setFont(QFont("Courier New", 10))
self.ManualAction.triggered.connect(self.onManualActionTriggered) self.ManualAction.triggered.connect(self.onManualActionTriggered)
self.CheckUpdateAction.triggered.connect(self.onCheckUpdateActionTriggered)
self.AboutAction.triggered.connect(self.onAboutActionTriggered) self.AboutAction.triggered.connect(self.onAboutActionTriggered)
self.SettingsAction.triggered.connect(self.onSettingsActionTriggered) self.SettingsAction.triggered.connect(self.onSettingsActionTriggered)
if hasattr(self, 'BulletinAction'): self.BulletinAction.triggered.connect(self.onBulletinActionTriggered)
self.BulletinAction.triggered.connect(self.onBulletinActionTriggered)
# initialize timer task widget, but not show it # initialize timer task widget, but not show it
try: try:
self.__ALTimerTaskManageWidget = ALTimerTaskManageWidget(self) self.__ALTimerTaskManageWidget = ALTimerTaskManageWidget(self)
@@ -110,27 +108,15 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.TimerTaskManageWidgetButton.setEnabled(False) self.TimerTaskManageWidgetButton.setEnabled(False)
self.TimerTaskManageWidgetButton.setToolTip("定时任务功能初始化失败, 请检查配置文件。") self.TimerTaskManageWidgetButton.setToolTip("定时任务功能初始化失败, 请检查配置文件。")
return return
self.timerTaskIsRunning.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsRunning) self.__timer_task_poller.taskRunning.connect(self.onTimerTaskRunning)
self.timerTaskIsExecuted.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsExecuted) self.__timer_task_poller.taskFinished.connect(self.onTimerTaskFinished)
self.timerTaskIsError.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsError) self.__timer_task_poller.taskRunning.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsRunning)
self.__ALTimerTaskManageWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady) self.__timer_task_poller.taskExecuted.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsExecuted)
self.__timer_task_poller.taskError.connect(self.__ALTimerTaskManageWidget.onTimerTaskIsError)
self.__ALTimerTaskManageWidget.timerTaskIsReady.connect(self.__timer_task_poller.enqueue)
self.__ALTimerTaskManageWidget.timerTaskManageWidgetIsClosed.connect(self.onTimerTaskManageWidgetClosed) self.__ALTimerTaskManageWidget.timerTaskManageWidgetIsClosed.connect(self.onTimerTaskManageWidgetClosed)
self.__ALTimerTaskManageWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint) self.__ALTimerTaskManageWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
def onAboutActionTriggered(
self
):
AboutDialog = ALAboutDialog(self)
AboutDialog.exec()
def onManualActionTriggered(
self
):
url = QUrl("https://www.autolibrary.kenanzhu.com/manuals")
QDesktopServices.openUrl(url)
def setupTray( def setupTray(
self self
): ):
@@ -143,13 +129,12 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.TrayMenu = QMenu() self.TrayMenu = QMenu()
self.TrayMenu.addAction("显示主窗口", self.showNormal) self.TrayMenu.addAction("显示主窗口", self.showNormal)
self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskManageWidgetButtonClicked) self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskManageWidgetButtonClicked)
self.TrayMenu.addAction("公告栏", self.onBulletinActionTriggered)
self.TrayMenu.addAction("最小化到托盘", self.hideToTray) self.TrayMenu.addAction("最小化到托盘", self.hideToTray)
self.TrayMenu.addSeparator() self.TrayMenu.addSeparator()
self.TrayMenu.addAction("退出", self.close) self.TrayMenu.addAction("退出", self.close)
self.TrayIcon.setContextMenu(self.TrayMenu) self.TrayIcon.setContextMenu(self.TrayMenu)
self.TrayIcon.activated.connect(self.onTrayIconActivated) self.TrayIcon.activated.connect(self.onTrayIconActivated)
self.TrayIcon.messageClicked.connect(self.onBulletinActionTriggered) self.TrayIcon.messageClicked.connect(self.onTrayMessageClicked)
self.TrayIcon.show() self.TrayIcon.show()
def hideToTray( def hideToTray(
@@ -157,6 +142,9 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
): ):
self.hide() self.hide()
self.__notification_type = ""
if not hasattr(self, "TrayIcon"):
return
self.TrayIcon.showMessage( self.TrayIcon.showMessage(
"AutoLibrary", "AutoLibrary",
"\n已最小化到托盘", "\n已最小化到托盘",
@@ -164,14 +152,6 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
2000 2000
) )
def onTrayIconActivated(
self,
reason: QSystemTrayIcon.ActivationReason
):
if reason == QSystemTrayIcon.DoubleClick:
self.showNormal()
def connectSignals( def connectSignals(
self self
): ):
@@ -182,9 +162,7 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.StopButton.clicked.connect(self.onStopButtonClicked) self.StopButton.clicked.connect(self.onStopButtonClicked)
self.SendButton.clicked.connect(self.onSendButtonClicked) self.SendButton.clicked.connect(self.onSendButtonClicked)
self.MessageEdit.returnPressed.connect(self.onSendButtonClicked) self.MessageEdit.returnPressed.connect(self.onSendButtonClicked)
self.__bulletin_poller.newBulletinsDetected.connect( self.__bulletin_poller.newBulletinsDetected.connect(self.onBulletinPollerNewBulletins)
self._onBulletinPollerNewBulletins
)
def closeEvent( def closeEvent(
self, self,
@@ -197,11 +175,8 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
return return
if self.__msg_queue_timer and self.__msg_queue_timer.isActive(): if self.__msg_queue_timer and self.__msg_queue_timer.isActive():
self.__msg_queue_timer.stop() self.__msg_queue_timer.stop()
if self.__timer_task_timer and self.__timer_task_timer.isActive(): if self.__timer_task_poller:
self.__timer_task_timer.stop() self.__timer_task_poller.stop()
if self.__is_running_timer_task:
self.__current_timer_task_thread.wait(2000)
self.__current_timer_task_thread.deleteLater()
if self.__ALTimerTaskManageWidget: if self.__ALTimerTaskManageWidget:
self.__ALTimerTaskManageWidget.close() self.__ALTimerTaskManageWidget.close()
self.__ALTimerTaskManageWidget.deleteLater() self.__ALTimerTaskManageWidget.deleteLater()
@@ -240,47 +215,6 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.__msg_queue_timer.timeout.connect(self.pollMsgQueue) self.__msg_queue_timer.timeout.connect(self.pollMsgQueue)
self.__msg_queue_timer.start(100) self.__msg_queue_timer.start(100)
def startTimerTaskPolling(
self
):
self.__timer_task_timer = QTimer()
self.__timer_task_timer.timeout.connect(self.pollTimerTaskQueue)
self.__timer_task_timer.start(500)
def pollTimerTaskQueue(
self
):
if self.__is_running_timer_task:
return
try:
while not self.__is_running_timer_task:
timer_task = self.__timer_task_queue.get_nowait()
self.timerTaskIsRunning.emit(timer_task)
self.__timer_task_timer.stop()
self.__is_running_timer_task = True
self.setControlButtons(None, True, False)
if not timer_task["silent"]:
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
f"\n已开始执行定时任务: \n{timer_task['name']}",
QSystemTrayIcon.MessageIcon.Information,
1000
)
self.showNormal()
self.__current_timer_task_thread = TimerTaskWorker(
timer_task,
self._input_queue,
self._output_queue,
self.__config_paths
)
self.__current_timer_task_thread.timerTaskWorkerIsFinished.connect(self.onTimerTaskFinished)
self.__current_timer_task_thread.start()
except queue.Empty:
self.__is_running_timer_task = False
pass
def setControlButtons( def setControlButtons(
self, self,
config_button_enabled: bool, config_button_enabled: bool,
@@ -308,6 +242,145 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
except queue.Empty: except queue.Empty:
pass pass
@Slot(int)
def onBulletinPollerNewBulletins(
self,
count: int
):
if not hasattr(self, "TrayIcon"):
return
self.__notification_type = "bulletin"
self.TrayIcon.showMessage(
"公告栏 - AutoLibrary",
f"{count} 条新公告,点击查看详情。",
QSystemTrayIcon.MessageIcon.Information,
3000
)
@Slot(dict)
def onTimerTaskRunning(
self,
timer_task: dict
):
self.setControlButtons(None, True, False)
self.__notification_type = ""
if not hasattr(self, "TrayIcon"):
return
if not timer_task.get("silent", False):
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
f"\n已开始执行定时任务: \n{timer_task['name']}",
QSystemTrayIcon.MessageIcon.Information,
1000
)
self.showNormal()
@Slot(bool, dict)
def onTimerTaskFinished(
self,
is_error: bool,
timer_task: dict
):
self.setControlButtons(None, False, True)
self.__notification_type = ""
if not hasattr(self, "TrayIcon"):
return
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
f"\n定时任务 '{timer_task['name']}' 执行{'失败' if is_error else '完成'}",
QSystemTrayIcon.MessageIcon.Warning if is_error else QSystemTrayIcon.MessageIcon.Information,
1000
)
self._showTrace(
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['uuid']}"
)
@Slot(dict)
def onCheckUpdateIsFinished(
self,
data: dict
):
worker = self.sender()
if worker is not self.__check_update_worker:
return
worker.checkUpdateWorkerIsFinished.disconnect(self.onCheckUpdateIsFinished)
worker.checkUpdateWorkerFinishedWithError.disconnect(self.onCheckUpdateFinishedWithError)
worker.wait(3000)
worker.deleteLater()
self.__check_update_worker = None
tag_name = data.get("tag_name", "")
html_url = data.get("html_url", "")
latest_version = tag_name.lstrip("v")
try:
local_ver = ver.Version(AL_VERSION)
remote_ver = ver.Version(latest_version)
except ver.InvalidVersion:
self._showTrace("版本号解析失败, 无法比较版本", self.TraceLevel.WARNING)
return
if remote_ver > local_ver:
ALCheckUpdateDialog.showResult(
self,
has_update=True,
current_version=AL_VERSION,
latest_version=latest_version,
tag_name=tag_name,
html_url=html_url
)
else:
ALCheckUpdateDialog.showResult(
self,
has_update=False,
current_version=AL_VERSION
)
self._showLog("检查更新完成")
@Slot(str)
def onCheckUpdateFinishedWithError(
self,
error_message: str
):
worker = self.sender()
if worker is not self.__check_update_worker:
return
worker.checkUpdateWorkerIsFinished.disconnect(self.onCheckUpdateIsFinished)
worker.checkUpdateWorkerFinishedWithError.disconnect(self.onCheckUpdateFinishedWithError)
worker.wait(3000)
worker.deleteLater()
self.__check_update_worker = None
QMessageBox.warning(
self,
"检查更新 - AutoLibrary",
f"检查更新失败: \n{error_message}",
)
self._showLog("检查更新失败")
@Slot()
def onBulletinDialogClosed(
self
):
if self.__ALBulletinDialog:
self.__ALBulletinDialog.finished.disconnect(self.onBulletinDialogClosed)
self.__ALBulletinDialog.deleteLater()
self.__ALBulletinDialog = None
self.__bulletin_poller.setDialogOpen(False)
@Slot()
def onSettingsWidgetClosed(
self
):
if self.__ALSettingsWidget:
self.__ALSettingsWidget.settingsWidgetIsClosed.disconnect(self.onSettingsWidgetClosed)
self.__ALSettingsWidget.deleteLater()
self.__ALSettingsWidget = None
self.SettingsAction.setEnabled(True)
@Slot() @Slot()
def onTimerTaskManageWidgetClosed( def onTimerTaskManageWidgetClosed(
self self
@@ -325,60 +398,24 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.__ALConfigWidget.deleteLater() self.__ALConfigWidget.deleteLater()
self.__ALConfigWidget = None self.__ALConfigWidget = None
self.__config_paths = ConfigUtils.getAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__timer_task_poller.updateConfigPaths(self.__config_paths)
self.setControlButtons(True, None, None) self.setControlButtons(True, None, None)
self._showLog("配置窗口已关闭,配置文件路径已更新") self._showLog("配置窗口已关闭,配置文件路径已更新")
@Slot()
def onSettingsWidgetClosed(
self
):
if self.__ALSettingsWidget:
self.__ALSettingsWidget.settingsWidgetIsClosed.disconnect(self.onSettingsWidgetClosed)
self.__ALSettingsWidget.deleteLater()
self.__ALSettingsWidget = None
self.SettingsAction.setEnabled(True)
@Slot() @Slot()
def onBulletinActionTriggered( def onBulletinActionTriggered(
self self
): ):
self.__bulletin_poller.setDialogOpen(True)
if self.__ALBulletinDialog is None: if self.__ALBulletinDialog is None:
self.__ALBulletinDialog = ALBulletinDialog(self) self.__ALBulletinDialog = ALBulletinDialog(self)
self.__ALBulletinDialog.finished.connect(self.onBulletinDialogClosed) self.__ALBulletinDialog.finished.connect(self.onBulletinDialogClosed)
self.__bulletin_poller.setDialogOpen(True)
self.__ALBulletinDialog.show() self.__ALBulletinDialog.show()
self.__ALBulletinDialog.raise_() self.__ALBulletinDialog.raise_()
self.__ALBulletinDialog.activateWindow() self.__ALBulletinDialog.activateWindow()
self._showLog("打开公告栏窗口") self._showLog("打开公告栏窗口")
@Slot()
def onBulletinDialogClosed(
self
):
if self.__ALBulletinDialog:
self.__ALBulletinDialog.finished.disconnect(self.onBulletinDialogClosed)
self.__ALBulletinDialog.deleteLater()
self.__ALBulletinDialog = None
self.__bulletin_poller.setDialogOpen(False)
@Slot(int)
def _onBulletinPollerNewBulletins(
self,
count: int
):
if not hasattr(self, 'TrayIcon'):
return
self.TrayIcon.showMessage(
"公告栏 - AutoLibrary",
f"{count} 条新公告,点击查看详情。",
QSystemTrayIcon.MessageIcon.Information,
3000
)
@Slot() @Slot()
def onSettingsActionTriggered( def onSettingsActionTriggered(
self self
@@ -387,54 +424,72 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
if self.__ALSettingsWidget is None: if self.__ALSettingsWidget is None:
self.__ALSettingsWidget = ALSettingsWidget(self) self.__ALSettingsWidget = ALSettingsWidget(self)
self.__ALSettingsWidget.settingsWidgetIsClosed.connect(self.onSettingsWidgetClosed) self.__ALSettingsWidget.settingsWidgetIsClosed.connect(self.onSettingsWidgetClosed)
self.__ALSettingsWidget.openBulletinRequested.connect(self.onBulletinActionTriggered)
self.__ALSettingsWidget.show() self.__ALSettingsWidget.show()
self.__ALSettingsWidget.raise_() self.__ALSettingsWidget.raise_()
self.__ALSettingsWidget.activateWindow() self.__ALSettingsWidget.activateWindow()
self.SettingsAction.setEnabled(False) self.SettingsAction.setEnabled(False)
self._showLog("打开全局设置窗口") self._showLog("打开全局设置窗口")
@Slot(dict) @Slot()
def onTimerTaskIsReady( def onAboutActionTriggered(
self, self
timer_task: dict
): ):
self.__timer_task_queue.put(timer_task) AboutDialog = ALAboutDialog(self)
AboutDialog.exec()
@Slot(dict) @Slot()
def onTimerTaskFinished( def onManualActionTriggered(
self, self
is_error: bool,
timer_task: dict
): ):
self.__current_timer_task_thread.wait(1000) url = QUrl("https://manuals.autolibrary.kenanzhu.com")
self.__current_timer_task_thread.timerTaskWorkerIsFinished.disconnect(self.onTimerTaskFinished) QDesktopServices.openUrl(url)
self.__current_timer_task_thread.deleteLater()
self.__current_timer_task_thread = None @Slot()
self.setControlButtons(None, False, True) def onCheckUpdateActionTriggered(
self.__is_running_timer_task = False self
self.__timer_task_timer.start(500) ):
timer_task["executed"] = True
self.TrayIcon.showMessage( if hasattr(self, '__check_update_worker') and self.__check_update_worker is not None:
"定时任务 - AutoLibrary", return
f"\n定时任务 '{timer_task['name']}' 执行{'失败' if is_error else '完成'}", self.__check_update_worker = ALCheckUpdateWorker(self)
QSystemTrayIcon.MessageIcon.Warning if is_error else QSystemTrayIcon.MessageIcon.Information, self.__check_update_worker.checkUpdateWorkerIsFinished.connect(self.onCheckUpdateIsFinished)
1000 self.__check_update_worker.checkUpdateWorkerFinishedWithError.connect(self.onCheckUpdateFinishedWithError)
) self.__check_update_worker.start()
self._showTrace( self._showLog("正在检查更新...")
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['uuid']}"
) @Slot()
if not is_error: def onTrayMessageClicked(
self.timerTaskIsExecuted.emit(timer_task) self
else: ):
self.timerTaskIsError.emit(timer_task)
if self.__notification_type == "bulletin":
self.__notification_type = ""
self.onBulletinActionTriggered()
@Slot(QSystemTrayIcon.ActivationReason)
def onTrayIconActivated(
self,
reason: QSystemTrayIcon.ActivationReason
):
if reason == QSystemTrayIcon.DoubleClick:
self.showNormal()
@Slot() @Slot()
def onTimerTaskManageWidgetButtonClicked( def onTimerTaskManageWidgetButtonClicked(
self self
): ):
if self.__ALTimerTaskManageWidget is None:
QMessageBox.warning(
self,
"警告 - AutoLibrary",
"定时任务功能初始化失败, 请检查配置文件。"
)
return
self.__ALTimerTaskManageWidget.show() self.__ALTimerTaskManageWidget.show()
self.__ALTimerTaskManageWidget.raise_() self.__ALTimerTaskManageWidget.raise_()
self.__ALTimerTaskManageWidget.activateWindow() self.__ALTimerTaskManageWidget.activateWindow()
+77 -59
View File
@@ -41,6 +41,7 @@ from managers.theme.ThemeManager import(
instance as themeInstance instance as themeInstance
) )
from gui.ALBulletinWorker import ALBulletinFetchWorker
from gui.ALWidgetMixin import CenterOnParentMixin from gui.ALWidgetMixin import CenterOnParentMixin
from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget
from interfaces.ConfigProvider import ( from interfaces.ConfigProvider import (
@@ -90,6 +91,7 @@ def _restartApp(
class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget): class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
settingsWidgetIsClosed = Signal() settingsWidgetIsClosed = Signal()
openBulletinRequested = Signal()
def __init__( def __init__(
self, self,
@@ -114,6 +116,8 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
event: QCloseEvent event: QCloseEvent
): ):
if hasattr(self, '__bulletin_test_worker') and self.__bulletin_test_worker is not None:
self.__bulletin_test_worker.wait(3000)
self.settingsWidgetIsClosed.emit() self.settingsWidgetIsClosed.emit()
super().closeEvent(event) super().closeEvent(event)
@@ -188,6 +192,7 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self.CustomThemeComboBox.currentIndexChanged.connect(self.onCustomThemeComboBoxChanged) self.CustomThemeComboBox.currentIndexChanged.connect(self.onCustomThemeComboBoxChanged)
self.ResetCustomThemeButton.clicked.connect(self.onResetCustomThemeButtonClicked) self.ResetCustomThemeButton.clicked.connect(self.onResetCustomThemeButtonClicked)
self.BulletinTestButton.clicked.connect(self.onBulletinTestButtonClicked) self.BulletinTestButton.clicked.connect(self.onBulletinTestButtonClicked)
self.BulletinOpenButton.clicked.connect(self.openBulletinRequested.emit)
self.CancelButton.clicked.connect(self.onCancelButtonClicked) self.CancelButton.clicked.connect(self.onCancelButtonClicked)
self.ApplyButton.clicked.connect(self.onApplyButtonClicked) self.ApplyButton.clicked.connect(self.onApplyButtonClicked)
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked) self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
@@ -196,6 +201,7 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self self
): ):
# appearance settings
theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system") theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system")
style = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.STYLE, "Fusion") style = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.STYLE, "Fusion")
custom_theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "") custom_theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
@@ -218,7 +224,7 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self.CustomThemeComboBox.setCurrentIndex(idx) self.CustomThemeComboBox.setCurrentIndex(idx)
self.updateCustomThemeInfo() self.updateCustomThemeInfo()
self.updateCustomThemeStatus() self.updateCustomThemeStatus()
# bulletin settings
bulletin_mgr = bulletinInstance() bulletin_mgr = bulletinInstance()
self.__original_bulletin_url = bulletin_mgr.serverUrl() self.__original_bulletin_url = bulletin_mgr.serverUrl()
self.__original_bulletin_auto_fetch = bulletin_mgr.autoFetch() self.__original_bulletin_auto_fetch = bulletin_mgr.autoFetch()
@@ -274,10 +280,17 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
elif need_theme == "dark": elif need_theme == "dark":
self.DarkThemeRadio.setChecked(True) self.DarkThemeRadio.setChecked(True)
def collectSettings( def clearBulletinTestStatus(
self self
): ):
self.BulletinTestStatusLabel.setText("")
self.BulletinTestStatusLabel.setStyleSheet("")
def collectAppearanceSettings(
self
) -> tuple[str, str, str]:
if self.LightThemeRadio.isChecked(): if self.LightThemeRadio.isChecked():
theme = "light" theme = "light"
elif self.DarkThemeRadio.isChecked(): elif self.DarkThemeRadio.isChecked():
@@ -290,41 +303,29 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
custom_theme = "" custom_theme = ""
return theme, style, custom_theme return theme, style, custom_theme
def clearBulletinTestStatus( def collectBulletinSettings(
self self
): ) -> tuple[str, bool, int]:
self.BulletinTestStatusLabel.setText("")
self.BulletinTestStatusLabel.setStyleSheet("")
def saveBulletinSettings(
self
):
url = self.BulletinServerUrlEdit.text().strip() url = self.BulletinServerUrlEdit.text().strip()
auto_fetch = self.BulletinAutoFetchCheck.isChecked() auto_fetch = self.BulletinAutoFetchCheck.isChecked()
sync_interval = self.BulletinSyncIntervalSpin.value() sync_interval = self.BulletinSyncIntervalSpin.value()
if sync_interval < 1: if sync_interval < 1:
sync_interval = 5 sync_interval = 5
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.SERVER_URL, url) return url, auto_fetch, sync_interval
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.AUTO_FETCH, auto_fetch)
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.SYNC_INTERVAL, sync_interval)
self.__original_bulletin_url = url
self.__original_bulletin_auto_fetch = auto_fetch
self.__original_bulletin_sync_interval = sync_interval
def saveAndApply( def saveAppearanceSettings(
self self
): ):
theme, style, custom_theme = self.collectSettings() theme, style, custom_theme = self.collectAppearanceSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme)
setActiveStyle(style) setActiveStyle(style)
if not _applyCustomTheme(custom_theme, theme): if not _applyCustomTheme(custom_theme, theme):
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "") self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
self.syncRadioFromNeedTheme(custom_theme) self.syncRadioFromNeedTheme(custom_theme)
theme, _, _ = self.collectSettings() theme, _, _ = self.collectAppearanceSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme)
self.setNavigationIcons() self.setNavigationIcons()
self.updateCustomThemeStatus() self.updateCustomThemeStatus()
@@ -333,7 +334,24 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self.__original_custom_theme = custom_theme if custom_theme else "" self.__original_custom_theme = custom_theme if custom_theme else ""
self.__original_style = getActiveStyle() self.__original_style = getActiveStyle()
def saveBulletinSettings(
self
):
url, auto_fetch, sync_interval = self.collectBulletinSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.SERVER_URL, url)
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.AUTO_FETCH, auto_fetch)
self.__cfg_mgr.set(CfgKey.GLOBAL.BULLETIN.SYNC_INTERVAL, sync_interval)
self.__original_bulletin_url = url
self.__original_bulletin_auto_fetch = auto_fetch
self.__original_bulletin_sync_interval = sync_interval
def saveSettings(
self
):
self.saveBulletinSettings() self.saveBulletinSettings()
self.saveAppearanceSettings()
def maybeRestart( def maybeRestart(
self self
@@ -351,6 +369,33 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
return True return True
return False return False
@Slot()
def onImportCustomThemeButtonClicked(
self
):
file_path, _ = QFileDialog.getOpenFileName(
self,
"导入主题 - AutoLibrary",
"",
"主题文件 (*.altheme *.qss);;所有文件 (*)"
)
if not file_path:
return
try:
file_id = themeInstance().importTheme(file_path)
self.populateCustomThemes()
idx = self.CustomThemeComboBox.findData(file_id)
if idx >= 0:
self.CustomThemeComboBox.setCurrentIndex(idx)
self.updateCustomThemeInfo()
except Exception as e:
QMessageBox.warning(
self,
"导入失败 - AutoLibrary",
f"无法导入主题文件:{e}"
)
@Slot() @Slot()
def onRemoveCustomThemeButtonClicked( def onRemoveCustomThemeButtonClicked(
self self
@@ -388,34 +433,6 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
f"无法删除主题:{e}" f"无法删除主题:{e}"
) )
@Slot()
def onImportCustomThemeButtonClicked(
self
):
file_path, _ = QFileDialog.getOpenFileName(
self,
"导入主题 - AutoLibrary",
"",
"主题文件 (*.altheme *.qss);;所有文件 (*)"
)
if not file_path:
return
try:
file_id = themeInstance().importTheme(file_path)
self.populateCustomThemes()
idx = self.CustomThemeComboBox.findData(file_id)
if idx >= 0:
self.CustomThemeComboBox.setCurrentIndex(idx)
self.updateCustomThemeStatus()
self.updateCustomThemeInfo()
except Exception as e:
QMessageBox.warning(
self,
"导入失败 - AutoLibrary",
f"无法导入主题文件:{e}"
)
@Slot() @Slot()
def onCustomThemeComboBoxChanged( def onCustomThemeComboBoxChanged(
self, self,
@@ -464,31 +481,31 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self.BulletinTestStatusLabel.setText("正在测试连接...") self.BulletinTestStatusLabel.setText("正在测试连接...")
self.BulletinTestStatusLabel.setStyleSheet("") self.BulletinTestStatusLabel.setStyleSheet("")
self.__bulletin_test_t0 = time.monotonic() self.__bulletin_test_t0 = time.monotonic()
from gui.ALBulletinDialog import ALBulletinFetchWorker
api_url = url.rstrip("/") + "/bulletins" api_url = url.rstrip("/") + "/bulletins"
self.__bulletin_test_worker = ALBulletinFetchWorker( self.__bulletin_test_worker = ALBulletinFetchWorker(
self, api_url, {"date": "", "time": "", "range_hour": "1"} self, api_url, {"date": "", "time": "", "range_hour": "1"}
) )
self.__bulletin_test_worker.fetchWorkerIsFinished.connect( self.__bulletin_test_worker.fetchWorkerIsFinished.connect(
self.__onBulletinTestFetched self.onBulletinTestIsFinished
) )
self.__bulletin_test_worker.fetchWorkerFinishedWithError.connect( self.__bulletin_test_worker.fetchWorkerFinishedWithError.connect(
self.__onBulletinTestError self.onBulletinTestFinishedWithError
) )
self.__bulletin_test_worker.start() self.__bulletin_test_worker.start()
@Slot(dict) @Slot(dict)
def __onBulletinTestFetched( def onBulletinTestIsFinished(
self, self,
data: dict data: dict
): ):
self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect( self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect(
self.__onBulletinTestFetched self.onBulletinTestIsFinished
) )
self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect( self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect(
self.__onBulletinTestError self.onBulletinTestFinishedWithError
) )
self.__bulletin_test_worker.wait(3000)
self.__bulletin_test_worker.deleteLater() self.__bulletin_test_worker.deleteLater()
self.__bulletin_test_worker = None self.__bulletin_test_worker = None
elapsed_ms = (time.monotonic() - self.__bulletin_test_t0) * 1000 elapsed_ms = (time.monotonic() - self.__bulletin_test_t0) * 1000
@@ -500,17 +517,18 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
QTimer.singleShot(3000, self, self.clearBulletinTestStatus) QTimer.singleShot(3000, self, self.clearBulletinTestStatus)
@Slot(str) @Slot(str)
def __onBulletinTestError( def onBulletinTestFinishedWithError(
self, self,
error_message: str error_message: str
): ):
self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect( self.__bulletin_test_worker.fetchWorkerIsFinished.disconnect(
self.__onBulletinTestFetched self.onBulletinTestIsFinished
) )
self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect( self.__bulletin_test_worker.fetchWorkerFinishedWithError.disconnect(
self.__onBulletinTestError self.onBulletinTestFinishedWithError
) )
self.__bulletin_test_worker.wait(3000)
self.__bulletin_test_worker.deleteLater() self.__bulletin_test_worker.deleteLater()
self.__bulletin_test_worker = None self.__bulletin_test_worker = None
self.BulletinTestStatusLabel.setText(f"连接失败:{error_message}") self.BulletinTestStatusLabel.setText(f"连接失败:{error_message}")
@@ -529,9 +547,9 @@ class ALSettingsWidget(CenterOnParentMixin, QWidget, Ui_ALSettingsWidget):
self self
): ):
_, style, _ = self.collectSettings() _, style, _ = self.collectAppearanceSettings()
style_changed = self.__original_style != style style_changed = self.__original_style != style
self.saveAndApply() self.saveSettings()
if style_changed: if style_changed:
self.maybeRestart() self.maybeRestart()
+1 -1
View File
@@ -322,5 +322,5 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
): ):
QDesktopServices.openUrl( QDesktopServices.openUrl(
QUrl("https://www.autolibrary.kenanzhu.com/manuals/autoscript") QUrl("https://manuals.autolibrary.kenanzhu.com/zh/autoscript")
) )
+1 -1
View File
@@ -135,4 +135,4 @@ class ALTimerTaskHistoryDialog(QDialog):
self.__history.clear() self.__history.clear()
self.HistoryTableWidget.setRowCount(0) self.HistoryTableWidget.setRowCount(0)
self.__task_data["repeat_history"] = self.__history self.__task_data["repeat_history"] = self.__history # = []
+5
View File
@@ -515,6 +515,11 @@ class ALTimerTaskManageWidget(CenterOnParentMixin, QWidget, Ui_ALTimerTaskManage
task: dict task: dict
): ):
# Here we use reference to task data, not copy.
# So any change of task data in history dialog will be
# reflected in the timer task list
# Thus we can emit timerTasksChanged signal to
# update config file
Dialog = ALTimerTaskHistoryDialog(self, task) Dialog = ALTimerTaskHistoryDialog(self, task)
if Dialog.exec() == QDialog.DialogCode.Accepted: if Dialog.exec() == QDialog.DialogCode.Accepted:
self.timerTasksChanged.emit() self.timerTasksChanged.emit()
+142
View File
@@ -0,0 +1,142 @@
# -*- 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.
"""
import queue
from PySide6.QtCore import (
QObject,
QTimer,
Signal,
Slot
)
from gui.ALMainWorker import TimerTaskWorker
class ALTimerTaskPoller(QObject):
taskRunning = Signal(dict)
taskFinished = Signal(bool, dict)
taskExecuted = Signal(dict)
taskError = Signal(dict)
def __init__(
self,
parent=None,
input_queue: queue.Queue = None,
output_queue: queue.Queue = None,
config_paths: list = None
):
super().__init__(parent)
self.__input_queue = input_queue or queue.Queue()
self.__output_queue = output_queue or queue.Queue()
self.__config_paths = config_paths or []
self.__task_queue = queue.Queue()
self.__timer = QTimer(self)
self.__timer.timeout.connect(self.__poll)
self.__worker = None
self.__stopped = False
def start(
self
):
self.__stopped = False
self.__timer.start(500)
def stop(
self
):
self.__stopped = True
self.__timer.stop()
self.__cleanupWorker()
def enqueue(
self,
task: dict
):
self.__task_queue.put(task)
def isRunning(
self
) -> bool:
return self.__worker is not None
def updateConfigPaths(
self,
config_paths: list
):
self.__config_paths = config_paths
@Slot()
def __poll(
self
):
if self.__worker is not None:
return
try:
task = self.__task_queue.get_nowait()
except queue.Empty:
return
self.__timer.stop()
self.taskRunning.emit(task)
try:
self.__worker = TimerTaskWorker(
task,
self.__input_queue,
self.__output_queue,
self.__config_paths
)
self.__worker.timerTaskWorkerIsFinished.connect(self.__onFinished)
self.__worker.start()
except Exception:
self.__worker = None
if not self.__stopped:
self.__timer.start(500)
@Slot(bool, dict)
def __onFinished(
self,
is_error: bool,
task: dict
):
self.__worker.timerTaskWorkerIsFinished.disconnect(self.__onFinished)
self.__worker.wait(1000)
self.__worker.deleteLater()
self.__worker = None
try:
self.taskFinished.emit(is_error, task)
if not is_error:
self.taskExecuted.emit(task)
else:
self.taskError.emit(task)
finally:
if not self.__stopped:
self.__timer.start(500)
def __cleanupWorker(
self
):
if self.__worker is None:
return
try:
self.__worker.timerTaskWorkerIsFinished.disconnect(self.__onFinished)
except (TypeError, RuntimeError):
pass
self.__worker.wait(500)
self.__worker.deleteLater()
self.__worker = None
+3 -3
View File
@@ -5,11 +5,11 @@
workflow process. Do not edit manually. workflow process. Do not edit manually.
This file is auto-generated during the workflow process. This file is auto-generated during the workflow process.
Last updated: 2026-05-09 06:05:13 UTC Last updated: 2026-07-14 02:19:31 UTC
""" """
AL_VERSION = "1.3.0" AL_VERSION = "1.4.0"
AL_TAG = "v1.3.0" AL_TAG = "v1.4.0"
AL_COMMIT_SHA = "local" AL_COMMIT_SHA = "local"
AL_COMMIT_DATE = "null" # time zone : UTC AL_COMMIT_DATE = "null" # time zone : UTC
AL_BUILD_DATE = "null" # time zone : UTC AL_BUILD_DATE = "null" # time zone : UTC
+12
View File
@@ -286,6 +286,7 @@ font: 700 9pt;</string>
<string>工具</string> <string>工具</string>
</property> </property>
<addaction name="SettingsAction"/> <addaction name="SettingsAction"/>
<addaction name="BulletinAction"/>
</widget> </widget>
<widget class="QMenu" name="HelpMenu"> <widget class="QMenu" name="HelpMenu">
<property name="mouseTracking"> <property name="mouseTracking">
@@ -295,6 +296,7 @@ font: 700 9pt;</string>
<string>帮助</string> <string>帮助</string>
</property> </property>
<addaction name="ManualAction"/> <addaction name="ManualAction"/>
<addaction name="CheckUpdateAction"/>
<addaction name="AboutAction"/> <addaction name="AboutAction"/>
</widget> </widget>
<addaction name="ToolsMenu"/> <addaction name="ToolsMenu"/>
@@ -310,6 +312,11 @@ font: 700 9pt;</string>
<string>在线手册</string> <string>在线手册</string>
</property> </property>
</action> </action>
<action name="CheckUpdateAction">
<property name="text">
<string>检查更新</string>
</property>
</action>
<action name="AboutAction"> <action name="AboutAction">
<property name="text"> <property name="text">
<string>关于</string> <string>关于</string>
@@ -320,6 +327,11 @@ font: 700 9pt;</string>
<string>全局设置</string> <string>全局设置</string>
</property> </property>
</action> </action>
<action name="BulletinAction">
<property name="text">
<string>公告栏</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
+39 -5
View File
@@ -113,6 +113,9 @@
</item> </item>
<item> <item>
<widget class="QStackedWidget" name="PageStack"> <widget class="QStackedWidget" name="PageStack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QScrollArea" name="AppearanceScrollArea"> <widget class="QScrollArea" name="AppearanceScrollArea">
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum> <enum>QFrame::Shape::NoFrame</enum>
@@ -458,8 +461,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>397</width> <width>409</width>
<height>434</height> <height>383</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="BulletinPageLayout"> <layout class="QVBoxLayout" name="BulletinPageLayout">
@@ -577,15 +580,15 @@
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="suffix">
<string> 分钟</string>
</property>
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>1440</number> <number>1440</number>
</property> </property>
<property name="suffix">
<string> 分钟</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@@ -624,6 +627,18 @@
</item> </item>
<item> <item>
<widget class="QLabel" name="BulletinTestStatusLabel"> <widget class="QLabel" name="BulletinTestStatusLabel">
<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"> <property name="text">
<string/> <string/>
</property> </property>
@@ -632,6 +647,25 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="BulletinOpenButton">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>查看公告栏</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
+8 -10
View File
@@ -223,24 +223,24 @@ class BulletinManager:
try: try:
if self.isFirstSync(): if self.isFirstSync():
start_date = now - timedelta(days=7) start_date = now - timedelta(days=7)
range_hour = str(24 * 8) range_hour = str(24*8)
elif self.shouldFullSync(): elif self.shouldFullSync():
with self.__lock: with self.__lock:
bulletins = self.bulletins() bulletins = self.bulletins()
earliest = min(bulletins, key=self._parseBulletinDateTime) earliest = min(bulletins, key=self._parseBulletinDateTime)
start_date = self._parseBulletinDateTime(earliest) start_date = self._parseBulletinDateTime(earliest)
diff = now - start_date diff = now - start_date
range_hour = str(int(diff.total_seconds() / 3600) + 1) range_hour = str(int(diff.total_seconds()/3600) + 1)
else: else:
with self.__lock: with self.__lock:
bulletins = self.bulletins() bulletins = self.bulletins()
latest = max(bulletins, key=self._parseBulletinDateTime) latest = max(bulletins, key=self._parseBulletinDateTime)
start_date = self._parseBulletinDateTime(latest) start_date = self._parseBulletinDateTime(latest)
diff = now - start_date diff = now - start_date
range_hour = str(int(diff.total_seconds() / 3600) + 1) range_hour = str(int(diff.total_seconds()/3600) + 1)
except (ValueError, TypeError, KeyError): except (ValueError, TypeError, KeyError):
start_date = now - timedelta(days=7) start_date = now - timedelta(days=7)
range_hour = str(24 * 8) range_hour = str(24*8)
return { return {
"date": start_date.strftime("%Y-%m-%d"), "date": start_date.strftime("%Y-%m-%d"),
@@ -256,9 +256,10 @@ class BulletinManager:
""" """
Merge incoming bulletins into the cache. Merge incoming bulletins into the cache.
New bulletins are added with isNew=True. Existing bulletins - New bulletins are added with isNew=True.
keep their current isNew state. Entries listed in delete_ids - Existing bulletins keep their current isNew state.
are removed. Bulletins missing an "id" field are skipped. - Entries listed in delete_ids are removed.
- Bulletins missing an "id" field are skipped.
Args: Args:
new_bulletins (list[dict]): Incoming bulletin list. new_bulletins (list[dict]): Incoming bulletin list.
@@ -275,7 +276,6 @@ class BulletinManager:
bid = b.get("id") bid = b.get("id")
if bid is not None: if bid is not None:
bulletins_dict[str(bid)] = b bulletins_dict[str(bid)] = b
for bulletin in new_bulletins: for bulletin in new_bulletins:
bid = bulletin.get("id") bid = bulletin.get("id")
if bid is None: if bid is None:
@@ -290,10 +290,8 @@ class BulletinManager:
else bulletins_dict[bid].get("isNew", True) else bulletins_dict[bid].get("isNew", True)
) )
bulletins_dict[bid] = bulletin bulletins_dict[bid] = bulletin
for bid in delete_set: for bid in delete_set:
bulletins_dict.pop(bid, None) bulletins_dict.pop(bid, None)
result = list(bulletins_dict.values()) result = list(bulletins_dict.values())
result.sort(key=lambda x: str(x.get("id", ""))) result.sort(key=lambda x: str(x.get("id", "")))
self.setBulletins(result) self.setBulletins(result)
+7 -6
View File
@@ -7,6 +7,7 @@ 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. You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details. See the LICENSE file for details.
""" """
import copy
import os import os
import threading import threading
@@ -18,7 +19,7 @@ from interfaces.ConfigProvider import ConfigType, ConfigPath
# This config manager class only responsible for global and other # This config manager class only responsible for global and other
# unconfigurable config files. # config files. NOT include run and user config files.
class ConfigTemplate: class ConfigTemplate:
@@ -87,8 +88,8 @@ class ConfigManager:
): ):
self.__config_dir = os.path.abspath(config_dir) self.__config_dir = os.path.abspath(config_dir)
self.__config_lock = threading.Lock()
self.__config_data = {} self.__config_data = {}
self.__lock = threading.Lock()
self.initialize() self.initialize()
@@ -121,16 +122,16 @@ class ConfigManager:
default: Optional[Any] = None default: Optional[Any] = None
) -> Any: ) -> Any:
with self.__config_lock: with self.__lock:
config_data = self.__config_data[key.config_type.value] config_data = self.__config_data[key.config_type.value]
if key.key == "": if key.key == "":
return config_data return copy.deepcopy(config_data)
keys = key.key.split('.') keys = key.key.split('.')
for k in keys[:-1]: for k in keys[:-1]:
config_data = config_data.get(k, None) config_data = config_data.get(k, None)
if config_data is None: if config_data is None:
return default return default
return config_data.get(keys[-1], default) return copy.deepcopy(config_data.get(keys[-1], default))
def set( def set(
self, self,
@@ -138,7 +139,7 @@ class ConfigManager:
value: Any = None value: Any = None
): ):
with self.__config_lock: with self.__lock:
root_data = self.__config_data[key.config_type.value] root_data = self.__config_data[key.config_type.value]
if key.key == "": if key.key == "":
self.__config_data[key.config_type.value] = value self.__config_data[key.config_type.value] = value
-5
View File
@@ -99,17 +99,14 @@ class LogManager:
self.__logger = logging.getLogger("AutoLibrary") self.__logger = logging.getLogger("AutoLibrary")
self.__logger.setLevel(logging.DEBUG) self.__logger.setLevel(logging.DEBUG)
self.__logger.handlers.clear() self.__logger.handlers.clear()
formatter = CallerInfoFormatter( formatter = CallerInfoFormatter(
'[%(asctime)s] - [%(name)s] - [%(levelname)s] - [%(filename)s:%(lineno)s] - %(message)s', '[%(asctime)s] - [%(name)s] - [%(levelname)s] - [%(filename)s:%(lineno)s] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S' datefmt='%Y-%m-%d %H:%M:%S'
) )
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter) console_handler.setFormatter(formatter)
self.__logger.addHandler(console_handler) self.__logger.addHandler(console_handler)
all_log_file = os.path.join(self.__log_dir, "all.log") all_log_file = os.path.join(self.__log_dir, "all.log")
file_handler_all = TimedRotatingFileHandler( file_handler_all = TimedRotatingFileHandler(
all_log_file, all_log_file,
@@ -122,7 +119,6 @@ class LogManager:
file_handler_all.setLevel(logging.DEBUG) file_handler_all.setLevel(logging.DEBUG)
file_handler_all.setFormatter(formatter) file_handler_all.setFormatter(formatter)
self.__logger.addHandler(file_handler_all) self.__logger.addHandler(file_handler_all)
error_log_file = os.path.join(self.__log_dir, "error.log") error_log_file = os.path.join(self.__log_dir, "error.log")
file_handler_error = TimedRotatingFileHandler( file_handler_error = TimedRotatingFileHandler(
error_log_file, error_log_file,
@@ -135,7 +131,6 @@ class LogManager:
file_handler_error.setLevel(logging.ERROR) file_handler_error.setLevel(logging.ERROR)
file_handler_error.setFormatter(formatter) file_handler_error.setFormatter(formatter)
self.__logger.addHandler(file_handler_error) self.__logger.addHandler(file_handler_error)
self.__initialized = True self.__initialized = True
def getLogger( def getLogger(
+17 -13
View File
@@ -28,6 +28,8 @@ from utils.ThemeUtils import (
) )
# we use Fusion as the default active style name
# because it's cross-platform.
_active_style_name = "Fusion" _active_style_name = "Fusion"
@@ -61,8 +63,10 @@ class ThemeManager:
): ):
self.__themes_dir = os.path.abspath(themes_dir) self.__themes_dir = os.path.abspath(themes_dir)
self.__lock = threading.Lock()
self.__current_theme_name = "" self.__current_theme_name = ""
self.__lock = threading.Lock()
# directory must exist
os.makedirs(self.__themes_dir, exist_ok=True) os.makedirs(self.__themes_dir, exist_ok=True)
@staticmethod @staticmethod
@@ -144,18 +148,6 @@ class ThemeManager:
) )
return alt_path return alt_path
def themesDir(
self
) -> str:
"""
Get the themes directory path.
Returns:
str: The absolute path to the themes storage directory.
"""
return self.__themes_dir
def importTheme( def importTheme(
self, self,
source_path: str source_path: str
@@ -321,6 +313,18 @@ class ThemeManager:
) )
app.setStyle(QStyleFactory.create(_active_style_name)) app.setStyle(QStyleFactory.create(_active_style_name))
def themesDir(
self
) -> str:
"""
Get the themes directory path.
Returns:
str: The absolute path to the themes storage directory.
"""
return self.__themes_dir
# ThemeManager singleton instance. # ThemeManager singleton instance.
_theme_manager_instance = None _theme_manager_instance = None
+2 -1
View File
@@ -22,6 +22,7 @@ from pages.strategies.TimeSelectMaker import TimeSelectMaker
from pages.ReserveView import ReserveView from pages.ReserveView import ReserveView
from pages.components.ReserveResultDialog import ReserveResultDialog from pages.components.ReserveResultDialog import ReserveResultDialog
from pages.components.TimeSelectDialog import TimeSelectDialog from pages.components.TimeSelectDialog import TimeSelectDialog
from pages.components.SeatMapDialog import SeatMapDialog
@dataclass @dataclass
@@ -150,7 +151,7 @@ class ReserveFlow(MsgBase):
def _selectSeatAndSubmit( def _selectSeatAndSubmit(
self, self,
view: ReserveView, view: ReserveView,
seat_map, seat_map: SeatMapDialog,
ctx: ReserveContext, ctx: ReserveContext,
) -> tuple[bool, bool]: ) -> tuple[bool, bool]: