mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-22 09:23:03 +08:00
refactor(theme): 将重复的主题逻辑下沉至 ThemeUtils,消除 validateTheme 职责过重
This commit is contained in:
@@ -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:
|
||||
|
||||
+60
-69
@@ -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"))
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user