Files
cmshoppe/app/gui/__init__.py
T
chengma df9da63841
Tests / Python 3.11 / Windows (push) Has been cancelled
feat: 接入远程订阅策略门禁 T-705
2026-07-28 15:42:19 +08:00

241 lines
8.6 KiB
Python

"""PySide6 GUI package entry point."""
from __future__ import annotations
import os
import sys
from dataclasses import replace
from .. import (
appconfig,
chrome,
client_policy,
db,
diagnostics,
image_studio_generation,
update_check,
update_health,
)
from ..version import APP_NAME, APP_VERSION, display_name
from . import widgets as _widgets
from .widgets import *
QT_IMPORT_ERROR = _widgets.QT_IMPORT_ERROR
QMessageBox = _widgets._RawQMessageBox
run_worker = _widgets._raw_run_worker if QT_IMPORT_ERROR is None else _widgets.run_worker
if QT_IMPORT_ERROR is None:
from .activation_dialog import MembershipActivationDialog
from .models import ApplyTaskTableModel, GenerateTaskTableModel, TaskTableModel
from .workers import (
AccountLoginCheckWorker,
AIModelTestWorker,
CMHubModelCatalogWorker,
CMHubSettingsWorker,
ApplyWorker,
CollectWorker,
GenerateWorker,
ImageStudioDownloadOriginalWorker,
ImageStudioExportWorker,
ImageStudioGenerateJobsWorker,
ImageStudioPullImagesWorker,
ImageStudioResumeJobsWorker,
ProductSuiteAiWriteWorker,
ProductSuiteGenerateWorker,
ProductSuiteHistoryExportWorker,
ProductSuiteImportImagesWorker,
ProductTabOpenWorker,
WriteBackWorker,
)
from .tabs.accounts import AccountDialog, AccountsTab
from .tabs.apply import ApplyTab
from .tabs.collect import CollectTab
from .tabs.generate import GenerateTab
from .tabs.image_studio import ImageStudioPreviewDialog, ImageStudioTab
from .product_suite_prompt_dialog import ProductSuitePromptDialog
from .tabs.product_suite import ProductSuitePreviewDialog, ProductSuiteTab
from .tabs.settings import SettingsTab
from .main_window import MainWindow
from .update_dialog import ForcedUpdateDialog, UpdatePreparationWorker
else:
class MainWindow(QMainWindow):
def __init__(self):
raise RuntimeError(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。")
def _ensure_offscreen_for_headless_tests():
if "PYTEST_CURRENT_TEST" in os.environ and "QT_QPA_PLATFORM" not in os.environ:
os.environ["QT_QPA_PLATFORM"] = "offscreen"
def _write_update_check_diagnostic(message, *, result=None, exc=None):
payload = None
if result is not None:
payload = {
"checked": result.checked,
"forced": result.forced,
"current_version": result.current_version,
"latest_version": result.latest_version,
"min_supported_version": result.min_supported_version,
"download_url": result.download_url,
"error": result.error,
"client_policy_mode": (
result.client_policy.mode if result.client_policy is not None else ""
),
"client_policy_error": result.client_policy_error,
}
try:
diagnostics.write_diagnostic_log(
message,
level="WARNING",
step="startup_update_check",
payload=payload,
exc=exc,
)
except Exception:
# 版本检查第一版必须失败放行,诊断日志不可写也不能阻断启动。
pass
def _show_forced_update_dialog(result, *, parent=None, dialog_factory=None) -> bool:
dialog_class = dialog_factory or ForcedUpdateDialog
dialog = dialog_class(result, parent=parent)
dialog.exec()
return False
def _run_startup_update_gate(
*,
checker=None,
dialog_factory=None,
result_callback=None,
) -> bool:
try:
result = (checker or update_check.check_for_update)()
except Exception as exc:
_write_update_check_diagnostic("启动版本检查异常,已允许继续使用", exc=exc)
return True
if result_callback is not None:
result_callback(result)
if result.error:
_write_update_check_diagnostic("启动版本检查失败,已允许继续使用", result=result)
elif result.client_policy_error:
_write_update_check_diagnostic("客户端订阅策略已按安全兼容规则处理", result=result)
if result.forced:
failed = update_health.get_failed_release(
appconfig.app_base_dir(),
result.latest_version,
result.sha256,
)
if failed:
result = replace(
result,
automatic_update_error=(
"该版本自动升级曾失败,已停止重复安装。请等待管理员发布修复版本,或手动安装新版。"
),
)
return _show_forced_update_dialog(result, dialog_factory=dialog_factory)
return True
def main() -> int:
if QT_IMPORT_ERROR is not None:
print(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。")
return 1
_ensure_offscreen_for_headless_tests()
app = QApplication.instance() or QApplication(sys.argv)
# Set on the application so the taskbar button, dialogs and message boxes
# all inherit it, not just the main window.
startup_icon = app_icon()
if startup_icon is not None:
app.setWindowIcon(startup_icon)
health_context = update_health.context_from_argv(
sys.argv,
appconfig.app_base_dir(),
APP_VERSION,
)
update_health.write_health(health_context, "process_started")
try:
appconfig.prepare_data_dir()
except appconfig.DataMigrationConflictError as exc:
update_health.write_health(health_context, "environment_blocked", str(exc))
QMessageBox.critical(None, "数据迁移冲突", str(exc))
return 1
except appconfig.DataDirectoryWriteError as exc:
update_health.write_health(health_context, "environment_blocked", str(exc))
QMessageBox.critical(None, "数据目录不可写", str(exc))
return 1
except appconfig.ConfigError as exc:
update_health.write_health(health_context, "environment_blocked", str(exc))
QMessageBox.critical(None, "启动配置错误", str(exc))
return 1
startup_update_results = []
if not _run_startup_update_gate(result_callback=startup_update_results.append):
return 1
try:
startup = chrome.ensure_configured_chrome_path()
except (OSError, appconfig.ConfigError) as exc:
update_health.write_health(health_context, "environment_blocked", str(exc))
QMessageBox.critical(None, "启动配置错误", str(exc))
return 1
runtime_lease = None
try:
database_path = appconfig.db_path(startup["config"])
db.init_db(database_path)
runtime_lease = image_studio_generation.acquire_startup_recovery_lease(
appconfig.data_dir(startup["config"])
)
recovery = image_studio_generation.recover_stale_direct_jobs_at_startup(
lease=runtime_lease,
path=database_path,
)
recovered_count = len(recovery.get("recovered") or [])
if recovered_count:
diagnostics.write_diagnostic_log(
"启动时标记未确认的自定义网关套图任务",
level="WARNING",
step="image_studio_direct_recovery",
payload={"recovered_count": recovered_count},
)
elif recovery.get("skipped"):
diagnostics.write_diagnostic_log(
"检测到另一个程序实例,跳过自定义网关套图任务恢复",
level="INFO",
step="image_studio_direct_recovery",
)
except Exception as exc:
_write_update_check_diagnostic("自定义网关套图任务恢复检查失败", exc=exc)
remote_policy = (
startup_update_results[-1].client_policy
if startup_update_results
else None
)
resolved_policy = client_policy.resolve_client_policy(
remote_policy,
config=startup["config"],
)
if resolved_policy.warning:
_write_update_check_diagnostic(
"客户端订阅策略使用兼容模式",
result=(
startup_update_results[-1]
if startup_update_results
else None
),
)
window = MainWindow(
config=startup["config"],
startup_status=startup["message"],
subscription_policy=resolved_policy,
)
if runtime_lease is not None:
app.aboutToQuit.connect(runtime_lease.release)
window.show()
QTimer.singleShot(0, window.begin_subscription_check)
QTimer.singleShot(
0,
lambda: update_health.write_health(health_context, "main_window_ready"),
)
return app.exec()