mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 07:23: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:
@@ -34,6 +34,7 @@ import managers.config.ConfigManager as ConfigManager
|
|||||||
from managers.log.LogManager import instance as logInstance
|
from managers.log.LogManager import instance as logInstance
|
||||||
from managers.theme.ThemeManager import(
|
from managers.theme.ThemeManager import(
|
||||||
getActiveStyle,
|
getActiveStyle,
|
||||||
|
setActiveStyle,
|
||||||
instance as themeInstance
|
instance as themeInstance
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,18 +48,20 @@ from interfaces.ConfigProvider import (
|
|||||||
def _applyCustomTheme(
|
def _applyCustomTheme(
|
||||||
name: str,
|
name: str,
|
||||||
fallback_theme: str = "system"
|
fallback_theme: str = "system"
|
||||||
):
|
) -> bool:
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
themeInstance().clearTheme(fallback_theme)
|
themeInstance().clearTheme(fallback_theme)
|
||||||
return
|
return True
|
||||||
try:
|
try:
|
||||||
themeInstance().applyTheme(name)
|
themeInstance().applyTheme(name)
|
||||||
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logInstance().getLogger("ALSettingsWidget").warning(
|
logInstance().getLogger("ALSettingsWidget").warning(
|
||||||
f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}"
|
f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}"
|
||||||
)
|
)
|
||||||
themeInstance().clearTheme(fallback_theme)
|
themeInstance().clearTheme(fallback_theme)
|
||||||
|
return False
|
||||||
|
|
||||||
def _themeToReadable(
|
def _themeToReadable(
|
||||||
need_theme: str
|
need_theme: str
|
||||||
@@ -266,7 +269,9 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
|||||||
theme, style, custom_theme = self.collectSettings()
|
theme, style, custom_theme = self.collectSettings()
|
||||||
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)
|
||||||
_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)
|
self.syncRadioFromNeedTheme(custom_theme)
|
||||||
# Re-read theme after syncRadioFromNeedTheme — the radio may have
|
# Re-read theme after syncRadioFromNeedTheme — the radio may have
|
||||||
# changed to match the custom theme's need_theme
|
# changed to match the custom theme's need_theme
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
|
|||||||
QStyleFactory
|
QStyleFactory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from interfaces.ConfigProvider import CfgKey
|
||||||
from managers.config.ConfigManager import instance as configInstance
|
from managers.config.ConfigManager import instance as configInstance
|
||||||
from managers.log.LogManager import instance as logInstance
|
from managers.log.LogManager import instance as logInstance
|
||||||
from utils.ThemeUtils import (
|
from utils.ThemeUtils import (
|
||||||
@@ -119,35 +120,36 @@ class ThemeManager:
|
|||||||
if not os.path.isfile(source_path):
|
if not os.path.isfile(source_path):
|
||||||
raise FileNotFoundError(source_path)
|
raise FileNotFoundError(source_path)
|
||||||
ext = os.path.splitext(source_path)[1].lower()
|
ext = os.path.splitext(source_path)[1].lower()
|
||||||
if ext == ".qss":
|
with self.__lock:
|
||||||
name = os.path.splitext(os.path.basename(source_path))[0]
|
if ext == ".qss":
|
||||||
dest_path = os.path.join(self.__themes_dir, name + ".altheme")
|
name = os.path.splitext(os.path.basename(source_path))[0]
|
||||||
if os.path.exists(dest_path):
|
dest_path = os.path.join(self.__themes_dir, name + ".altheme")
|
||||||
raise ValueError(f"主题 '{name}' 已存在")
|
if os.path.exists(dest_path):
|
||||||
wrapQssToAtheme(source_path, dest_path, "both")
|
raise ValueError(f"主题 '{name}' 已存在")
|
||||||
return name
|
wrapQssToAtheme(source_path, dest_path, "both")
|
||||||
elif ext == ".altheme":
|
return name
|
||||||
with zipfile.ZipFile(source_path, "r") as zf:
|
elif ext == ".altheme":
|
||||||
if "theme.qss" not in zf.namelist():
|
with zipfile.ZipFile(source_path, "r") as zf:
|
||||||
raise ValueError("无效的 .altheme: 缺少 theme.qss")
|
if "theme.qss" not in zf.namelist():
|
||||||
info = readThemeInfo(source_path)
|
raise ValueError("无效的 .altheme: 缺少 theme.qss")
|
||||||
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
|
info = readThemeInfo(source_path)
|
||||||
safe_name = os.path.basename(name)
|
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
|
||||||
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
|
safe_name = os.path.basename(name)
|
||||||
if os.path.exists(dest_path):
|
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
|
||||||
raise ValueError(f"主题 '{safe_name}' 已存在")
|
if os.path.exists(dest_path):
|
||||||
# Check for name collision with existing themes by the same author
|
raise ValueError(f"主题 '{safe_name}' 已存在")
|
||||||
new_author = info.get("author", "")
|
# Check for name collision with existing themes by the same author
|
||||||
for existing in self.listThemes():
|
new_author = info.get("author", "")
|
||||||
if (existing.get("name", "") == safe_name
|
for existing in self.listThemes():
|
||||||
and existing.get("author", "") == new_author):
|
if (existing.get("name", "") == safe_name
|
||||||
raise ValueError(
|
and existing.get("author", "") == new_author):
|
||||||
f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在"
|
raise ValueError(
|
||||||
)
|
f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在"
|
||||||
shutil.copy2(source_path, dest_path)
|
)
|
||||||
return safe_name
|
shutil.copy2(source_path, dest_path)
|
||||||
else:
|
return safe_name
|
||||||
raise ValueError(f"不支持的文件类型: {ext}")
|
else:
|
||||||
|
raise ValueError(f"不支持的文件类型: {ext}")
|
||||||
|
|
||||||
def listThemes(
|
def listThemes(
|
||||||
self
|
self
|
||||||
@@ -214,7 +216,10 @@ class ThemeManager:
|
|||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
if self.__current_theme_name == name:
|
if self.__current_theme_name == name:
|
||||||
self.__current_theme_name = ""
|
self.__current_theme_name = ""
|
||||||
self.clearTheme("system")
|
saved_theme = configInstance().get(
|
||||||
|
CfgKey.GLOBAL.APPEARANCE.THEME, "system"
|
||||||
|
)
|
||||||
|
self.clearTheme(saved_theme)
|
||||||
|
|
||||||
def applyTheme(
|
def applyTheme(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user