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

refactor(theme): 将重复的主题逻辑下沉至 ThemeUtils,消除 validateTheme 职责过重

This commit is contained in:
2026-06-19 11:21:50 +08:00
parent 8f8e3e4ba7
commit c250fa4a6e
2 changed files with 103 additions and 108 deletions
+40 -36
View File
@@ -21,6 +21,7 @@ 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 (
readThemeInfo,
readThemeQss,
validateTheme,
wrapQssToAtheme
@@ -79,17 +80,21 @@ class ThemeManager:
else:
return Qt.ColorScheme.Unknown
def themesDir(
self
) -> str:
def _deleteThemeFile(
self,
name: str
):
"""
Get the themes directory path.
Delete a theme file in the themes storage directory.
Returns:
str: The absolute path to the themes storage directory.
The caller must hold self.__lock before invoking this method.
**This method ONLY deletes the file**.
"""
return self.__themes_dir
filepath = os.path.join(self.__themes_dir, name + ".altheme")
if os.path.isfile(filepath):
os.remove(filepath)
def _resolveDestPath(
self,
@@ -121,7 +126,7 @@ class ThemeManager:
existing_info = validateTheme(default_path)
existing_author = existing_info.get("author", "")
except Exception:
self._removeThemeFile(theme_name) # caller holds the lock
self._deleteThemeFile(theme_name) # caller holds the lock
raise ValueError(
f"主题 '{theme_name}' 已存在但无法通过验证, 已清理该主题文件"
)
@@ -139,6 +144,18 @@ class ThemeManager:
)
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(
self,
source_path: str
@@ -164,16 +181,16 @@ class ThemeManager:
if not os.path.isfile(source_path):
raise FileNotFoundError(source_path)
ext = os.path.splitext(source_path)[1].lower()
base_name, ext = os.path.splitext(os.path.basename(source_path))
ext = ext.lower()
with self.__lock:
if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0]
dest_path = self._resolveDestPath(name, "未知作者")
dest_path = self._resolveDestPath(base_name, "未知作者")
wrapQssToAtheme(source_path, dest_path, "both")
return os.path.splitext(os.path.basename(dest_path))[0]
elif ext == ".altheme":
info = validateTheme(source_path)
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
name = info.get("name", base_name)
safe_name = os.path.basename(name)
new_author = info.get("author", "")
dest_path = self._resolveDestPath(safe_name, new_author)
@@ -203,7 +220,7 @@ class ThemeManager:
if filename.endswith(".altheme"):
filepath = os.path.join(self.__themes_dir, filename)
try:
info = validateTheme(filepath, check_qss=False) # skip QSS read for list scan
info = validateTheme(filepath)
name = info.get("name", "")
author = info.get("author", "")
key = (name, author)
@@ -225,26 +242,6 @@ class ThemeManager:
)
return themes
def _removeThemeFile(
self,
name: str
):
"""
Remove a theme file without locking.
The caller must hold self.__lock before invoking this method.
"""
filepath = os.path.join(self.__themes_dir, name + ".altheme")
if os.path.isfile(filepath):
os.remove(filepath)
if self.__current_theme_name == name:
self.__current_theme_name = ""
saved_theme = configInstance().get(
CfgKey.GLOBAL.APPEARANCE.THEME, "system"
)
self.clearTheme(saved_theme)
def removeTheme(
self,
name: str
@@ -253,14 +250,21 @@ class ThemeManager:
Remove a theme by name.
If the removed theme is currently active, clears the QSS
stylesheet from the application.
stylesheet from the application and reverts to the saved
colour scheme.
Args:
name (str): The theme name to remove.
"""
with self.__lock:
self._removeThemeFile(name)
self._deleteThemeFile(name)
if self.__current_theme_name == name:
self.__current_theme_name = ""
saved_theme = configInstance().get(
CfgKey.GLOBAL.APPEARANCE.THEME, "system"
)
self.clearTheme(saved_theme)
def applyTheme(
self,
@@ -284,7 +288,7 @@ class ThemeManager:
if not os.path.isfile(filepath):
raise FileNotFoundError(filepath)
with self.__lock:
info = validateTheme(filepath)
info = readThemeInfo(filepath)
qss = readThemeQss(filepath)
app = QApplication.instance()
if app:
+63 -72
View File
@@ -68,17 +68,20 @@ def readThemeInfo(
altheme_path: str
) -> dict:
"""
Read only the info.json metadata from a .altheme file.
Read and validate the info.json metadata from a .altheme file.
Verifies that all required fields (name, author, need_theme, brief)
are present with valid values.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
dict: The theme metadata dictionary.
dict: The validated theme metadata dictionary.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If the .altheme does not contain info.json.
ValueError: If info.json is missing or any field is invalid.
"""
if not os.path.isfile(altheme_path):
@@ -87,76 +90,14 @@ def readThemeInfo(
if "info.json" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 info.json")
with zf.open("info.json") as fh:
info = json.loads(fh.read().decode("utf-8"))
if "name" not in info:
raise ValueError("无效的 .altheme: info.json 缺少 'name' 字段")
return info
def readThemeQss(
altheme_path: str
) -> str:
"""
Read the theme.qss content directly from a .altheme archive.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
str: The QSS stylesheet content.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If theme.qss is missing from the archive.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
if "theme.qss" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 theme.qss")
return zf.read("theme.qss").decode("utf-8")
def validateTheme(
altheme_path: str,
check_qss: bool = True
) -> dict:
"""
Validate a .altheme file and return its metadata.
Checks that info.json and theme.qss both exist, info.json
contains all required fields with valid values, and theme.qss
is a non-empty entry in the archive.
Args:
altheme_path (str): Path to the .altheme file.
check_qss (bool): If False, skip theme.qss existence and
content checks (for list-only operations).
Returns:
dict: The validated theme metadata dictionary.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If validation fails for any reason.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
names = zf.namelist()
if "info.json" not in names:
raise ValueError("无效的 .altheme: 缺少 info.json")
if "theme.qss" not in names:
raise ValueError("无效的 .altheme: 缺少 theme.qss")
info_bytes = zf.read("info.json")
qss_bytes = zf.read("theme.qss") if check_qss else None # skip QSS read when only listing
try:
info = json.loads(info_bytes.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as e:
raise ValueError(f"无效的 .altheme: info.json 解析失败 — {e}")
try:
info = json.loads(fh.read().decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as e:
raise ValueError(f"无效的 .altheme: info.json 解析失败 — {e}")
if "name" not in info or not isinstance(info.get("name"), str) or not info["name"].strip():
raise ValueError("无效的 .altheme: info.json 缺少有效的 'name' 字段")
# reject blank author so info.json does not drift from the "未知作者" filename fallback
# reject blank author so that info.json does not drift from the
# "未知作者" filename fallback used by wrapQssToAtheme
if ("author" not in info or not isinstance(info.get("author"), str)
or not info["author"].strip()):
raise ValueError("无效的 .altheme: info.json 缺少有效的 'author' 字段")
@@ -168,8 +109,58 @@ def validateTheme(
)
if "brief" not in info or not isinstance(info.get("brief"), str):
raise ValueError("无效的 .altheme: info.json 缺少有效的 'brief' 字段")
if check_qss and not qss_bytes.strip():
return info
def readThemeQss(
altheme_path: str
) -> str:
"""
Read the theme.qss content from a .altheme archive.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
str: The non-empty QSS stylesheet content.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If theme.qss is missing or empty.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
if "theme.qss" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 theme.qss")
qss = zf.read("theme.qss").decode("utf-8")
if not qss.strip():
raise ValueError("无效的 .altheme: theme.qss 为空")
return qss
def validateTheme(
altheme_path: str
) -> dict:
"""
Fully validate a .altheme file and return its metadata.
Delegates info validation to readThemeInfo and QSS validation
to readThemeQss, then additionally checks that theme.qss is
non-empty.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
dict: The validated theme metadata dictionary.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If validation fails for any reason.
"""
info = readThemeInfo(altheme_path)
readThemeQss(altheme_path) # validates existence and non-empty
return info
def wrapQssToAtheme(