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

Compare commits

..

11 Commits

Author SHA1 Message Date
KenanZhu e11f696b76 style(*): 添加缺失的版权信息,并同一版权年份为文件创建时间的年份 2026-05-06 01:01:52 +08:00
KenanZhu ffae43d5bd fix(ConfigUtils): 添加未导入的 os 模块 2026-03-24 21:49:52 +08:00
Gogs baa4f23136 refactor(config): 新增 ConfigUtils 工具类并优化配置管理逻辑
- 新增 ConfigUtils 工具类,提供配置路径获取等工具方法
- 将 ConfigManager.getValidateAutomationConfigPaths() 重构为 ConfigUtils.getAutomationConfigPaths()
- 优化 MsgBase 中 LogManager 的导入方式,使用模块导入替代函数导入
- 规范化 TimerUtils.py 中 calculate_next_repeat_time() 的文档字符串格式
2026-03-23 13:31:06 +08:00
KenanZhu 1c88d3db7b chore(requirement): 移除 opencv-python 和 pywin32 冗余依赖 2026-03-22 22:56:43 +08:00
github-actions[bot] 3880f90916 chore(release): merge release/v1.2.1 to main [auto release commit] 2026-03-22 14:17:40 +00:00
github-actions[bot] d3d146b1b3 chore(release): v1.2.1 [auto release commit] 2026-03-22 14:14:27 +00:00
KenanZhu 0f74a3b0ec chore(requirement): 将 installed-browsers 替换为 pybrowsers 依赖 2026-03-22 22:05:52 +08:00
KenanZhu 9305c559cd refactor(WebBrowserDetector): 切换浏览器检测库为 browsers 并添加检测结果去重 2026-03-22 22:04:31 +08:00
KenanZhu f56945f29e fix(AppInitializer): 优化驱动目录初始化日志逻辑,仅在目录不存在时输出日志 2026-03-22 21:43:23 +08:00
KenanZhu 37132de4fc fix(ALTimerTaskManageWidget): 修复重复性定时任务删除时因 history 字段不存在导致 len(int) 异常 2026-03-22 21:34:08 +08:00
github-actions[bot] ac5385bcfe chore(release): merge release/v1.2.0 to main [auto release commit] 2026-03-21 10:58:40 +00:00
32 changed files with 176 additions and 127 deletions
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2026 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+3 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -11,7 +11,7 @@ import logging
import queue import queue
import datetime import datetime
from managers.log.LogManager import getLogger import managers.log.LogManager as LogManager
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 = getLogger(self._class_name) self._logger = LogManager.getLogger(self._class_name)
except RuntimeError: except RuntimeError:
self._logger = None self._logger = None
+2 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2026 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -56,9 +56,8 @@ def initializeWebDriverManager(
app_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation) app_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation)
driver_dir = os.path.join(app_dir, "drivers") driver_dir = os.path.join(app_dir, "drivers")
logger.info("初始化驱动目录 %s", driver_dir)
if not QDir(driver_dir).exists(): if not QDir(driver_dir).exists():
logger.error("创建驱动目录 %s 失败", driver_dir) logger.info("初始化驱动目录 %s", driver_dir)
if not QDir().mkpath(driver_dir): if not QDir().mkpath(driver_dir):
logger.error("创建驱动目录 %s 失败", driver_dir) logger.error("创建驱动目录 %s 失败", driver_dir)
return False return False
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+3 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -24,6 +24,7 @@ import managers.config.ConfigManager as ConfigManager
from utils.JSONReader import JSONReader from utils.JSONReader import JSONReader
from utils.JSONWriter import JSONWriter from utils.JSONWriter import JSONWriter
from utils.ConfigUtils import ConfigUtils
from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget
from gui.ALSeatMapSelectDialog import ALSeatMapSelectDialog from gui.ALSeatMapSelectDialog import ALSeatMapSelectDialog
@@ -43,7 +44,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
super().__init__(parent) super().__init__(parent)
self.__cfg_mgr = ConfigManager.instance() self.__cfg_mgr = ConfigManager.instance()
self.__config_paths = ConfigManager.getValidateAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__config_data = {"run": {}, "user": {}} self.__config_data = {"run": {}, "user": {}}
self.setupUi(self) self.setupUi(self)
+4 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -19,9 +19,8 @@ from PySide6.QtGui import (
QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices
) )
import managers.config.ConfigManager as ConfigManager
from base.MsgBase import MsgBase from base.MsgBase import MsgBase
from utils.ConfigUtils import ConfigUtils
from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow
from gui.resources import ALResource from gui.resources import ALResource
@@ -44,9 +43,8 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
MsgBase.__init__(self, queue.Queue(), queue.Queue()) MsgBase.__init__(self, queue.Queue(), queue.Queue())
QMainWindow.__init__(self) QMainWindow.__init__(self)
self.__cfg_mgr = ConfigManager.instance()
self.__timer_task_queue = queue.Queue() self.__timer_task_queue = queue.Queue()
self.__config_paths = ConfigManager.getValidateAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__alTimerTaskManageWidget = None self.__alTimerTaskManageWidget = None
self.__alConfigWidget = None self.__alConfigWidget = None
self.__auto_lib_thread = None self.__auto_lib_thread = None
@@ -300,7 +298,7 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.__alConfigWidget.configWidgetIsClosed.disconnect(self.onConfigWidgetClosed) self.__alConfigWidget.configWidgetIsClosed.disconnect(self.onConfigWidgetClosed)
self.__alConfigWidget.deleteLater() self.__alConfigWidget.deleteLater()
self.__alConfigWidget = None self.__alConfigWidget = None
self.__config_paths = ConfigManager.getValidateAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.setControlButtons(True, None, None) self.setControlButtons(True, None, None)
self._showLog("配置窗口已关闭,配置文件路径已更新") self._showLog("配置窗口已关闭,配置文件路径已更新")
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2026 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2026 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+8
View File
@@ -1,4 +1,12 @@
# -*- 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 enum import Enum from enum import Enum
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
+4 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -16,7 +16,7 @@ from PySide6.QtCore import Slot, QDateTime
from PySide6.QtWidgets import QLabel, QDialog, QWidget, QSpinBox, QHBoxLayout, QGridLayout, QDateTimeEdit from PySide6.QtWidgets import QLabel, QDialog, QWidget, QSpinBox, QHBoxLayout, QGridLayout, QDateTimeEdit
from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog
import utils.TimerUtils as TimerUtils from utils.TimerUtils import TimerUtils
class ALTimerTaskStatus(Enum): class ALTimerTaskStatus(Enum):
@@ -131,6 +131,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
"repeat": self.RepeatCheckBox.isChecked(), "repeat": self.RepeatCheckBox.isChecked(),
} }
if task_data["repeat"]: if task_data["repeat"]:
task_data["history"] = [] # repeat history
repeat_days = [] repeat_days = []
if self.MonCheckBox.isChecked(): if self.MonCheckBox.isChecked():
repeat_days.append(0) repeat_days.append(0)
@@ -152,7 +153,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
task_data["repeat_hour"] = execute_time.hour task_data["repeat_hour"] = execute_time.hour
task_data["repeat_minute"] = execute_time.minute task_data["repeat_minute"] = execute_time.minute
task_data["repeat_second"] = execute_time.second task_data["repeat_second"] = execute_time.second
task_data["execute_time"] = TimerUtils.calculateNextRepeatTime( task_data["execute_time"] = TimerUtils.getNextTimerRepeatTime(
task_data["repeat_days"], task_data["repeat_days"],
task_data["repeat_hour"], task_data["repeat_hour"],
task_data["repeat_minute"], task_data["repeat_minute"],
+9 -4
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -26,7 +26,7 @@ from PySide6.QtGui import (
) )
import managers.config.ConfigManager as ConfigManager import managers.config.ConfigManager as ConfigManager
import utils.TimerUtils as TimerUtils from utils.TimerUtils import TimerUtils
from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget
from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus
@@ -383,12 +383,17 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
timer_task: dict timer_task: dict
): ):
if "history" not in timer_task:
history = []
else:
history = timer_task["history"]
history_count = len(history)
return ( return (
f"任务名称:{timer_task["name"]}\n" f"任务名称:{timer_task["name"]}\n"
f"添加时间:{timer_task["added_time"]}\n" f"添加时间:{timer_task["added_time"]}\n"
f"当前状态:{timer_task["status"].value}\n" f"当前状态:{timer_task["status"].value}\n"
f"下次执行时间:{datetime.strftime(timer_task["execute_time"], "%Y-%m-%d %H:%M:%S")}\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)}" f"已记录次数:{history_count}"
) )
@@ -605,7 +610,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
"duration": 0, "duration": 0,
"uuid": timer_task["uuid"] "uuid": timer_task["uuid"]
}) })
next_time = TimerUtils.calculateNextRepeatTime( next_time = TimerUtils.getNextTimerRepeatTime(
timer_task["repeat_days"], timer_task["repeat_days"],
timer_task["repeat_hour"], timer_task["repeat_hour"],
timer_task["repeat_minute"], timer_task["repeat_minute"],
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+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-03-21 10:54:51 UTC Last updated: 2026-03-22 14:14:19 UTC
""" """
AL_VERSION = "1.2.0" AL_VERSION = "1.2.1"
AL_TAG = "v1.2.0" AL_TAG = "v1.2.1"
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
+2 -44
View File
@@ -176,49 +176,7 @@ class ConfigManager:
# ConfigManager singleton instance. # ConfigManager singleton instance.
_config_manager_instance = None _config_manager_instance : ConfigManager | None = None
# Utility functions.
#
# Utility function to get validated automation config paths.
def getValidateAutomationConfigPaths(
) -> dict:
"""
Get validated automation config paths from ConfigManager instance.
These function will validate the config paths and return the validated paths in a dict.
Returns:
dict: Validated automation config paths.
"""
config_paths = {"run": "", "user": ""}
auto_config = _config_manager_instance.get(ConfigType.GLOBAL, "automation", {})
for cfg_type in ["run", "user"]:
paths = auto_config.get(f"{cfg_type}_path", {}).get("paths", [])
index = auto_config.get(f"{cfg_type}_path", {}).get("current", 0)
if paths == []:
paths.append(os.path.join(_config_manager_instance.configDir(), f"{cfg_type}.json"))
if index < 0:
index = 0
if index >= len(paths):
index = len(paths) - 1
config_paths[cfg_type] = paths[index]
data = {"current": index, "paths": paths}
auto_config[f"{cfg_type}_path"] = data
_config_manager_instance.set(ConfigType.GLOBAL, "automation", auto_config)
return config_paths
# Utility function to get base config directory.
def getBaseConfigDir(
) -> str:
"""
Get base config directory, on Windows, it is usually at :
'C:\\Users\\<username>\\AppData\\Local\\AutoLibrary\\config'.
Returns:
str: Base config directory.
"""
return _config_manager_instance.configDir()
# Singleton instance of ConfigManager. # Singleton instance of ConfigManager.
_instance_lock = threading.Lock() _instance_lock = threading.Lock()
@@ -240,6 +198,6 @@ def instance(
else: else:
if config_dir == "": if config_dir == "":
return _config_manager_instance return _config_manager_instance
if getBaseConfigDir() != config_dir: if _config_manager_instance.configDir() != config_dir:
raise ValueError("ConfigManager 的实例已初始化,不能使用不同的配置目录。") raise ValueError("ConfigManager 的实例已初始化,不能使用不同的配置目录。")
return _config_manager_instance return _config_manager_instance
+23 -5
View File
@@ -1,5 +1,14 @@
# -*- 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 platform import platform
import installed_browsers import browsers
from pathlib import Path from pathlib import Path
from enum import Enum from enum import Enum
@@ -128,7 +137,7 @@ class WebBrowserDetector:
self.browser_infos = [] self.browser_infos = []
try: try:
all_browsers = installed_browsers.browsers() all_browsers = list(browsers.browsers())
except Exception as e: except Exception as e:
self.browser_infos = [] self.browser_infos = []
return self.browser_infos return self.browser_infos
@@ -140,14 +149,14 @@ class WebBrowserDetector:
'msedge': WebBrowserType.EDGE, 'msedge': WebBrowserType.EDGE,
} }
for browser in all_browsers: for browser in all_browsers:
internal_name = browser.get('name', '').lower() internal_name = browser.get("browser_type", "").lower()
if internal_name not in type_map: if internal_name not in type_map:
continue # Not one of the browsers we care about continue # Not one of the browsers we care about
version = browser.get('version') version = browser.get("version", "")
if not version: if not version:
# Skip browsers with no version info (unlikely, but defensive) # Skip browsers with no version info (unlikely, but defensive)
continue continue
exe_path = browser.get('location') exe_path = browser.get("path", "")
if not exe_path: if not exe_path:
continue continue
try: try:
@@ -163,4 +172,13 @@ class WebBrowserDetector:
browser_path=path, browser_path=path,
) )
self.browser_infos.append(info) self.browser_infos.append(info)
# Deduplicate: keep only one entry per (type, version)
seen = set()
unique = []
for info in self.browser_infos:
key = (info.browser_type, info.browser_version)
if key not in seen:
seen.add(key)
unique.append(info)
self.browser_infos = unique
return self.browser_infos return self.browser_infos
@@ -1,3 +1,12 @@
# -*- 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 os import os
import time import time
import shutil import shutil
+1 -1
View File
@@ -186,7 +186,7 @@ def instance(
raise ValueError("LogManager 的实例已初始化, 不能使用不同的日志目录") raise ValueError("LogManager 的实例已初始化, 不能使用不同的日志目录")
return _log_manager_instance return _log_manager_instance
# export function to get logger
def getLogger( def getLogger(
name: Optional[str] = None name: Optional[str] = None
) -> logging.Logger: ) -> logging.Logger:
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2025 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
+46
View File
@@ -0,0 +1,46 @@
# -*- 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 os
import managers.config.ConfigManager as ConfigManager
class ConfigUtils:
"""
Config utilities class.
"""
@staticmethod
def getAutomationConfigPaths(
) -> dict[str]:
"""
Get validated automation config paths from ConfigManager instance.
These function will validate the config paths and return the validated paths in a dict.
Returns:
dict[str]: Validated automation config paths (include user and run config paths).
"""
cfg_mgr = ConfigManager.instance() # config manager instance
config_paths = {"run": "", "user": ""}
auto_config = cfg_mgr.get(ConfigManager.ConfigType.GLOBAL, "automation", {})
for cfg_type in ["run", "user"]:
paths = auto_config.get(f"{cfg_type}_path", {}).get("paths", [])
index = auto_config.get(f"{cfg_type}_path", {}).get("current", 0)
if paths == []:
paths.append(os.path.join(cfg_mgr.configDir(), f"{cfg_type}.json"))
if index < 0:
index = 0
if index >= len(paths):
index = len(paths) - 1
config_paths[cfg_type] = paths[index]
data = {"current": index, "paths": paths}
auto_config[f"{cfg_type}_path"] = data
cfg_mgr.set(ConfigManager.ConfigType.GLOBAL, "automation", auto_config)
return config_paths
+42 -36
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Copyright (c) 2025 - 2026 KenanZhu. Copyright (c) 2026 KenanZhu.
All rights reserved. All rights reserved.
This software is provided "as is", without any warranty of any kind. This software is provided "as is", without any warranty of any kind.
@@ -10,41 +10,47 @@ See the LICENSE file for details.
from datetime import datetime, timedelta from datetime import datetime, timedelta
def calculateNextRepeatTime( class TimerUtils:
repeat_days: list,
hour: int,
minute: int,
second: int
) -> datetime:
""" """
Calculate the next repeat time based on repeat days and target time. Timer utilities class.
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() @staticmethod
current_weekday = current_time.weekday() def getNextTimerRepeatTime(
target_time = current_time.replace(hour=hour, minute=minute, second=second, microsecond=0) repeat_days: list[int],
if current_weekday in repeat_days: hour: int,
if target_time > current_time: minute: int,
return target_time second: int
repeat_days_sorted = sorted(repeat_days) ) -> datetime:
for day in repeat_days_sorted: """
if day > current_weekday: Calculate the next repeat time based on repeat days and target time.
days_until = day - current_weekday
next_time = target_time + timedelta(days=days_until) This function calculates the next execution time for a repeatable task.
return next_time If the current day is in repeat_days and the target time has not passed,
days_until = 7 - current_weekday + repeat_days_sorted[0] it returns today's target time. Otherwise, it finds the next matching day.
next_time = target_time + timedelta(days=days_until)
return next_time Args:
repeat_days (list[int]): 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