1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-17 23:13:03 +08:00

fix(theme): 修复主题管理系统逻辑缺陷

- removeTheme() 删除当前活动主题后从 ConfigManager 读取已保存的主题偏好作为回退方案,不再硬编码 'system'
- saveAndApply() 在调用 _applyCustomTheme 前先 setActiveStyle(style),确保主题应用时使用最新选择的内置样式
- _applyCustomTheme() 返回 bool 表示成败,失败时调用方清除配置中的 custom_theme 避免下次启动循环失败
- importTheme() 增加 self.__lock 保护,消除 TOCTOU 竞态条件
- ThemeManager 新增 CfgKey 导入以支持 removeTheme 读取配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 18:58:07 +08:00
parent b56d2c203e
commit 79e5b43498
2 changed files with 43 additions and 33 deletions
+8 -3
View File
@@ -34,6 +34,7 @@ import managers.config.ConfigManager as ConfigManager
from managers.log.LogManager import instance as logInstance
from managers.theme.ThemeManager import(
getActiveStyle,
setActiveStyle,
instance as themeInstance
)
@@ -47,18 +48,20 @@ from interfaces.ConfigProvider import (
def _applyCustomTheme(
name: str,
fallback_theme: str = "system"
):
) -> bool:
if not name:
themeInstance().clearTheme(fallback_theme)
return
return True
try:
themeInstance().applyTheme(name)
return True
except Exception as e:
logInstance().getLogger("ALSettingsWidget").warning(
f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}"
)
themeInstance().clearTheme(fallback_theme)
return False
def _themeToReadable(
need_theme: str
@@ -266,7 +269,9 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
theme, style, custom_theme = self.collectSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme)
_applyCustomTheme(custom_theme, theme)
setActiveStyle(style)
if not _applyCustomTheme(custom_theme, theme):
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
self.syncRadioFromNeedTheme(custom_theme)
# Re-read theme after syncRadioFromNeedTheme — the radio may have
# changed to match the custom theme's need_theme
+35 -30
View File
@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
QStyleFactory
)
from interfaces.ConfigProvider import CfgKey
from managers.config.ConfigManager import instance as configInstance
from managers.log.LogManager import instance as logInstance
from utils.ThemeUtils import (
@@ -119,35 +120,36 @@ class ThemeManager:
if not os.path.isfile(source_path):
raise FileNotFoundError(source_path)
ext = os.path.splitext(source_path)[1].lower()
if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0]
dest_path = os.path.join(self.__themes_dir, name + ".altheme")
if os.path.exists(dest_path):
raise ValueError(f"主题 '{name}' 已存在")
wrapQssToAtheme(source_path, dest_path, "both")
return name
elif ext == ".altheme":
with zipfile.ZipFile(source_path, "r") as zf:
if "theme.qss" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 theme.qss")
info = readThemeInfo(source_path)
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
safe_name = os.path.basename(name)
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
if os.path.exists(dest_path):
raise ValueError(f"主题 '{safe_name}' 已存在")
# Check for name collision with existing themes by the same author
new_author = info.get("author", "")
for existing in self.listThemes():
if (existing.get("name", "") == safe_name
and existing.get("author", "") == new_author):
raise ValueError(
f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在"
)
shutil.copy2(source_path, dest_path)
return safe_name
else:
raise ValueError(f"不支持的文件类型: {ext}")
with self.__lock:
if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0]
dest_path = os.path.join(self.__themes_dir, name + ".altheme")
if os.path.exists(dest_path):
raise ValueError(f"主题 '{name}' 已存在")
wrapQssToAtheme(source_path, dest_path, "both")
return name
elif ext == ".altheme":
with zipfile.ZipFile(source_path, "r") as zf:
if "theme.qss" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 theme.qss")
info = readThemeInfo(source_path)
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
safe_name = os.path.basename(name)
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
if os.path.exists(dest_path):
raise ValueError(f"主题 '{safe_name}' 已存在")
# Check for name collision with existing themes by the same author
new_author = info.get("author", "")
for existing in self.listThemes():
if (existing.get("name", "") == safe_name
and existing.get("author", "") == new_author):
raise ValueError(
f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在"
)
shutil.copy2(source_path, dest_path)
return safe_name
else:
raise ValueError(f"不支持的文件类型: {ext}")
def listThemes(
self
@@ -214,7 +216,10 @@ class ThemeManager:
os.remove(filepath)
if self.__current_theme_name == name:
self.__current_theme_name = ""
self.clearTheme("system")
saved_theme = configInstance().get(
CfgKey.GLOBAL.APPEARANCE.THEME, "system"
)
self.clearTheme(saved_theme)
def applyTheme(
self,