- 删除全部 MySQL/Redis/MongoDB 客户端代码(dbclient/api/service/storage) - 清理 4 个驱动依赖(mysql/redis/mongo/gorm-mysql),构建体积 -10MB - 前端移除 db-cli 整个目录(40 文件)+ 7 个 API/工具文件 - 版本号升级至 v0.4.0,顶部 Tab 仅保留文件管理
814 lines
22 KiB
Go
814 lines
22 KiB
Go
// [fs-only] 数据库客户端模块已移除(feature/fs-only 分支)
|
||
// 保留模块:文件系统 | Markdown编辑器 | 版本历史(抽屉) | 系统信息 | 更新检查 | PDF导出
|
||
// 顶部Tab仅:file-system(数据库 db-cli 已删除)
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
stdruntime "runtime"
|
||
"strings"
|
||
"time"
|
||
|
||
"golang.org/x/sys/windows/registry"
|
||
"u-desk/internal/api"
|
||
"u-desk/internal/common"
|
||
"u-desk/internal/filesystem"
|
||
"u-desk/internal/service"
|
||
"u-desk/internal/storage"
|
||
"u-desk/internal/system"
|
||
|
||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||
)
|
||
|
||
// App 应用结构体
|
||
type App struct {
|
||
ctx context.Context
|
||
updateAPI *api.UpdateAPI
|
||
configAPI *api.ConfigAPI
|
||
pdfAPI *api.PdfAPI
|
||
filesystem *filesystem.FileSystemService
|
||
isAlwaysOnTop bool
|
||
}
|
||
|
||
// App 方法命名约定:
|
||
// - 多参数操作 → XxxRequest 结构体(Wails 自动生成 TS 类型)
|
||
// - 单参数查询/简单操作 → 直接参数
|
||
|
||
// NewApp 创建新的应用实例
|
||
func NewApp() *App {
|
||
return &App{}
|
||
}
|
||
|
||
// Startup 应用启动时调用
|
||
func (a *App) Startup(ctx context.Context) {
|
||
a.ctx = ctx
|
||
|
||
// 1. 核心初始化:SQLite(必须同步,很快)
|
||
sqliteDB, err := storage.InitFast()
|
||
if err != nil {
|
||
panic(fmt.Sprintf("SQLite 初始化失败,应用无法启动: %v", err))
|
||
}
|
||
_ = sqliteDB // 全局 DB 已由 InitFast() 设置
|
||
|
||
// 2. 初始化配置服务
|
||
configService, err := api.NewConfigAPI()
|
||
if err != nil {
|
||
panic(fmt.Sprintf("配置服务初始化失败: %v", err))
|
||
}
|
||
a.configAPI = configService
|
||
|
||
// 2.5. 迁移旧配置
|
||
_ = a.configAPI.MigrateTabConfig()
|
||
|
||
// 2.6. 初始化PDF导出API
|
||
fmt.Println("[启动] 初始化PDF导出模块...")
|
||
pdfAPI, err := api.NewPdfAPI()
|
||
if err != nil {
|
||
fmt.Printf("[启动] PDF导出API初始化失败: %v\n", err)
|
||
// PDF导出失败不应影响应用启动,所以只警告不panic
|
||
} else {
|
||
a.pdfAPI = pdfAPI
|
||
fmt.Println("[启动] PDF导出模块初始化完成")
|
||
}
|
||
|
||
// 3. 初始化版本号(提前触发缓存,避免后续重复计算)
|
||
version := service.GetCurrentVersion()
|
||
fmt.Printf("[启动] 当前版本: %s\n", version)
|
||
|
||
// 4. 读取配置,获取可见的 Tabs
|
||
visibleTabs := a.getVisibleTabs()
|
||
fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs)
|
||
|
||
// 4. 根据配置初始化模块(条件初始化)
|
||
if err := a.initModulesByConfig(visibleTabs); err != nil {
|
||
panic(fmt.Sprintf("模块初始化失败: %v", err))
|
||
}
|
||
|
||
// 5. 异步初始化:UpdateAPI(涉及网络请求,完全异步)
|
||
go func() {
|
||
if updateAPI, err := api.NewUpdateAPI("https://c.1216.top/last-version.json"); err == nil {
|
||
a.updateAPI = updateAPI
|
||
a.updateAPI.SetContext(ctx)
|
||
a.startAutoUpdateCheck()
|
||
}
|
||
}()
|
||
|
||
}
|
||
|
||
// getVisibleTabs 获取配置中的可见 Tabs
|
||
func (a *App) getVisibleTabs() []string {
|
||
config, err := a.configAPI.GetAppConfig()
|
||
if err != nil {
|
||
fmt.Printf("[启动] 读取配置失败,使用默认配置: %v\n", err)
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
// 快速检查成功标识
|
||
success, ok := config["success"].(bool)
|
||
if !ok || !success {
|
||
fmt.Printf("[启动] 配置读取失败,使用默认配置\n")
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
// 提取 data
|
||
data, ok := config["data"].(map[string]interface{})
|
||
if !ok {
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
// 提取 visibleTabs
|
||
visibleTabsInterface, ok := data["visibleTabs"].([]interface{})
|
||
if !ok {
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
visibleTabs := common.InterfaceSliceToStringSlice(visibleTabsInterface)
|
||
|
||
if len(visibleTabs) == 0 {
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
return visibleTabs
|
||
}
|
||
|
||
// initModulesByConfig 根据配置初始化模块
|
||
func (a *App) initModulesByConfig(visibleTabs []string) error {
|
||
// 检查是否启用文件系统模块
|
||
if common.Contains(visibleTabs, common.TabFileSystem) {
|
||
fmt.Println("[启动] 初始化文件系统模块...")
|
||
|
||
// 初始化文件系统服务
|
||
fsConfig := filesystem.DefaultConfig()
|
||
var err error
|
||
a.filesystem, err = filesystem.NewFileSystemService(fsConfig)
|
||
if err != nil {
|
||
return fmt.Errorf("文件系统服务初始化失败: %w", err)
|
||
}
|
||
|
||
// 异步启动文件服务器
|
||
go a.startFileServer()
|
||
|
||
fmt.Println("[启动] 文件系统模块初始化完成")
|
||
} else {
|
||
fmt.Println("[启动] 跳过文件系统模块(未启用)")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// startFileServer 启动文件服务器
|
||
func (a *App) startFileServer() {
|
||
// 启动独立的本地文件服务器(使用 filesystem 包中的实现)
|
||
if _, err := filesystem.StartLocalFileServer(); err != nil {
|
||
fmt.Printf("[文件服务器] 启动失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
fmt.Println("[文件服务器] 启动在 http://localhost:8073")
|
||
}
|
||
|
||
// Shutdown 应用关闭时调用
|
||
func (a *App) Shutdown(ctx context.Context) {
|
||
// 创建带超时的上下文(5秒超时)
|
||
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||
defer cancel()
|
||
|
||
// 1. 关闭文件系统服务(优雅关闭,释放资源)
|
||
if a.filesystem != nil {
|
||
fmt.Println("[文件系统服务] 正在关闭...")
|
||
if err := a.filesystem.Close(shutdownCtx); err != nil {
|
||
fmt.Printf("[文件系统服务] 关闭失败: %v\n", err)
|
||
} else {
|
||
fmt.Println("[文件系统服务] 已关闭")
|
||
}
|
||
}
|
||
|
||
// 2. 停止文件服务器(使用全局服务器的关闭方法)
|
||
fmt.Println("[文件服务器] 正在关闭...")
|
||
if err := filesystem.ShutdownLocalFileServer(); err != nil {
|
||
fmt.Printf("[文件服务器] 关闭失败: %v\n", err)
|
||
} else {
|
||
fmt.Println("[文件服务器] 已关闭")
|
||
}
|
||
}
|
||
|
||
// GetSystemInfo 获取系统信息
|
||
func (a *App) GetSystemInfo() (map[string]interface{}, error) {
|
||
return system.GetSystemInfo()
|
||
}
|
||
|
||
// GetCPUInfo 获取 CPU 信息
|
||
func (a *App) GetCPUInfo() (map[string]interface{}, error) {
|
||
return system.GetCPUInfo()
|
||
}
|
||
|
||
// GetMemoryInfo 获取内存信息
|
||
func (a *App) GetMemoryInfo() (map[string]interface{}, error) {
|
||
return system.GetMemoryInfo()
|
||
}
|
||
|
||
// GetDiskInfo 获取磁盘信息
|
||
func (a *App) GetDiskInfo() ([]map[string]interface{}, error) {
|
||
return system.GetDiskInfo()
|
||
}
|
||
|
||
// ReadFile 读取文件
|
||
func (a *App) ReadFile(path string) (string, error) {
|
||
return a.filesystem.ReadFile(path)
|
||
}
|
||
|
||
// WriteFileRequest 写入文件请求结构体
|
||
type WriteFileRequest struct {
|
||
Path string `json:"path"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
// WriteFile 写入文件
|
||
func (a *App) WriteFile(req WriteFileRequest) error {
|
||
return a.filesystem.WriteFile(req.Path, req.Content)
|
||
}
|
||
|
||
// SaveBase64FileRequest 保存 Base64 编码的二进制文件
|
||
type SaveBase64FileRequest struct {
|
||
Path string `json:"path"`
|
||
Content string `json:"content"` // base64 编码的文件内容
|
||
}
|
||
|
||
// SaveBase64File 将 base64 内容解码后写入文件(用于图片等二进制数据)
|
||
func (a *App) SaveBase64File(req SaveBase64FileRequest) error {
|
||
return a.filesystem.SaveBase64File(req.Path, req.Content)
|
||
}
|
||
|
||
// ListDir 列出目录
|
||
func (a *App) ListDir(path string) ([]map[string]interface{}, error) {
|
||
return a.filesystem.ListDir(path)
|
||
}
|
||
|
||
// CreateDir 创建目录
|
||
func (a *App) CreateDir(path string) (*filesystem.FileOperationResult, error) {
|
||
return a.filesystem.CreateDir(path)
|
||
}
|
||
|
||
// CreateFile 创建文件
|
||
func (a *App) CreateFile(path string) (*filesystem.FileOperationResult, error) {
|
||
return a.filesystem.CreateFile(path)
|
||
}
|
||
|
||
// DeletePath 删除文件或目录
|
||
func (a *App) DeletePath(path string) (*filesystem.FileOperationResult, error) {
|
||
return a.filesystem.DeletePath(path)
|
||
}
|
||
|
||
// RenamePathRequest 重命名文件或目录请求结构体
|
||
type RenamePathRequest struct {
|
||
OldPath string `json:"oldPath"`
|
||
NewPath string `json:"newPath"`
|
||
}
|
||
|
||
// RenamePath 重命名文件或目录
|
||
func (a *App) RenamePath(req RenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||
return a.filesystem.RenamePath(req.OldPath, req.NewPath)
|
||
}
|
||
|
||
// GetFileInfo 获取文件信息
|
||
func (a *App) GetFileInfo(path string) (map[string]interface{}, error) {
|
||
return a.filesystem.GetFileInfo(path)
|
||
}
|
||
|
||
// GetEnvVars 获取环境变量
|
||
func (a *App) GetEnvVars() (map[string]string, error) {
|
||
envVars := make(map[string]string)
|
||
for _, env := range os.Environ() {
|
||
if key, value, found := strings.Cut(env, "="); found {
|
||
envVars[key] = value
|
||
}
|
||
}
|
||
return envVars, nil
|
||
}
|
||
|
||
// OpenPath 使用系统默认程序打开文件或目录
|
||
func (a *App) OpenPath(path string) error {
|
||
return a.filesystem.OpenPath(path)
|
||
}
|
||
|
||
// ========== Zip 文件操作接口 ==========
|
||
|
||
// ListZipContents 列出 zip 文件内容
|
||
func (a *App) ListZipContents(zipPath string) ([]map[string]interface{}, error) {
|
||
return a.filesystem.ListZipContents(zipPath)
|
||
}
|
||
|
||
// ExtractFileFromZip 从 zip 文件中提取单个文件内容
|
||
func (a *App) ExtractFileFromZip(zipPath, filePath string) (string, error) {
|
||
return a.filesystem.ExtractFileFromZip(zipPath, filePath)
|
||
}
|
||
|
||
// ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录
|
||
// 返回临时文件的完整路径,适用于图片等二进制文件
|
||
func (a *App) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) {
|
||
return a.filesystem.ExtractFileFromZipToTemp(zipPath, filePath)
|
||
}
|
||
|
||
// GetZipFileInfo 获取 zip 文件中特定文件的信息
|
||
func (a *App) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) {
|
||
return a.filesystem.GetZipFileInfo(zipPath, filePath)
|
||
}
|
||
|
||
// ResolveShortcut 解析快捷方式文件,返回目标路径信息
|
||
func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) {
|
||
targetPath, err := a.filesystem.ResolveShortcut(lnkPath)
|
||
if err != nil {
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
}, err
|
||
}
|
||
|
||
// 获取目标文件信息
|
||
fileInfo, err := a.filesystem.GetFileInfo(targetPath)
|
||
if err != nil {
|
||
// 目标文件不存在或无法访问
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"targetPath": targetPath,
|
||
"targetExists": false,
|
||
"targetAccessible": false,
|
||
}, nil
|
||
}
|
||
|
||
// 返回完整的目标信息
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"targetPath": targetPath,
|
||
"targetExists": true,
|
||
"targetAccessible": true,
|
||
"targetInfo": fileInfo,
|
||
}, nil
|
||
}
|
||
|
||
// getWindowsSpecialFolder 从注册表读取 Windows 特殊文件夹的真实路径
|
||
// 用户可通过系统设置修改下载/桌面/文档等目录位置,注册表记录实际路径
|
||
func getWindowsSpecialFolder(guid string, fallbackName string) string {
|
||
key, err := registry.OpenKey(registry.CURRENT_USER,
|
||
`Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders`,
|
||
registry.READ)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
defer key.Close()
|
||
|
||
val, _, err := key.GetStringValue(guid)
|
||
if err != nil || val == "" {
|
||
return ""
|
||
}
|
||
|
||
// 展开 %USERPROFILE% 等环境变量
|
||
path := os.ExpandEnv(val)
|
||
// 验证路径存在
|
||
if _, err := os.Stat(path); err != nil {
|
||
return ""
|
||
}
|
||
return path
|
||
}
|
||
|
||
// GetCommonPaths 获取常用系统路径
|
||
func (a *App) GetCommonPaths() (map[string]string, error) {
|
||
homeDir, err := os.UserHomeDir()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
paths := map[string]string{
|
||
"home": homeDir,
|
||
}
|
||
|
||
// Windows: 从注册表读取特殊文件夹真实路径(用户可能已修改位置)
|
||
folderGUIDs := map[string]string{
|
||
"desktop": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}",
|
||
"documents": "{D20B4C7F-5EA7-424C-B25E-039F6F1FCC8A}",
|
||
"downloads": "{374DE290-123F-4565-9164-39C4925E467B}",
|
||
}
|
||
for name, guid := range folderGUIDs {
|
||
if p := getWindowsSpecialFolder(guid, name); p != "" {
|
||
paths[name] = p
|
||
} else {
|
||
// folderGUIDs 的 key 均为 ASCII,无需 Unicode 处理
|
||
paths[name] = filepath.Join(homeDir, strings.ToUpper(name[:1])+name[1:])
|
||
}
|
||
}
|
||
|
||
// Windows: 动态添加所有盘符
|
||
if stdruntime.GOOS == "windows" {
|
||
for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
|
||
path := string(drive) + ":\\"
|
||
if _, err := os.Stat(path); err == nil {
|
||
key := fmt.Sprintf("root_%c", drive)
|
||
paths[key] = path
|
||
}
|
||
}
|
||
}
|
||
|
||
return paths, nil
|
||
}
|
||
|
||
// Reload 重新加载窗口(用于菜单项)
|
||
func (a *App) Reload() {
|
||
if a.ctx != nil {
|
||
runtime.WindowReload(a.ctx)
|
||
}
|
||
}
|
||
|
||
// ClearCache 清理本地缓存(用于菜单项)
|
||
func (a *App) ClearCache() {
|
||
if a.ctx != nil {
|
||
// 发送事件到前端,让前端清理 localStorage
|
||
runtime.EventsEmit(a.ctx, "clear-cache")
|
||
}
|
||
}
|
||
|
||
// ========== 窗口控制方法 ==========
|
||
|
||
// WindowMinimize 最小化窗口
|
||
func (a *App) WindowMinimize() {
|
||
if a.ctx != nil {
|
||
runtime.WindowMinimise(a.ctx)
|
||
}
|
||
}
|
||
|
||
// WindowMaximize 最大化/还原窗口
|
||
func (a *App) WindowMaximize() {
|
||
if a.ctx != nil {
|
||
if runtime.WindowIsMaximised(a.ctx) {
|
||
runtime.WindowUnmaximise(a.ctx)
|
||
} else {
|
||
runtime.WindowMaximise(a.ctx)
|
||
}
|
||
}
|
||
}
|
||
|
||
// WindowClose 关闭窗口
|
||
func (a *App) WindowClose() {
|
||
if a.ctx != nil {
|
||
runtime.Quit(a.ctx)
|
||
}
|
||
}
|
||
|
||
// WindowIsMaximized 检查窗口是否最大化
|
||
func (a *App) WindowIsMaximized() bool {
|
||
if a.ctx != nil {
|
||
return runtime.WindowIsMaximised(a.ctx)
|
||
}
|
||
return false
|
||
}
|
||
|
||
// WindowToggleAlwaysOnTop 切换窗口置顶
|
||
func (a *App) WindowToggleAlwaysOnTop() bool {
|
||
if a.ctx == nil {
|
||
return false
|
||
}
|
||
a.isAlwaysOnTop = !a.isAlwaysOnTop
|
||
runtime.WindowSetAlwaysOnTop(a.ctx, a.isAlwaysOnTop)
|
||
return a.isAlwaysOnTop
|
||
}
|
||
|
||
// ========== 版本更新管理接口 ==========
|
||
|
||
// requireUpdateAPI 检查 updateAPI 是否已初始化,未初始化返回统一错误
|
||
func (a *App) requireUpdateAPI() (*api.UpdateAPI, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI, nil
|
||
}
|
||
|
||
// CheckUpdate 检查更新(UpdateAPI 可能尚未初始化完成)
|
||
func (a *App) CheckUpdate() (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.CheckUpdate()
|
||
}
|
||
|
||
// GetCurrentVersion 获取当前版本号
|
||
func (a *App) GetCurrentVersion() (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.GetCurrentVersion()
|
||
}
|
||
|
||
// GetUpdateConfig 获取更新配置
|
||
func (a *App) GetUpdateConfig() (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.GetUpdateConfig()
|
||
}
|
||
|
||
// SetUpdateConfig 设置更新配置
|
||
func (a *App) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinutes int, checkURL string) (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.SetUpdateConfig(autoCheckEnabled, checkIntervalMinutes, checkURL)
|
||
}
|
||
|
||
// DownloadUpdate 下载更新包
|
||
func (a *App) DownloadUpdate(downloadURL string) (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.DownloadUpdate(downloadURL)
|
||
}
|
||
|
||
// InstallUpdate 安装更新包
|
||
func (a *App) InstallUpdate(installerPath string, autoRestart bool) (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.InstallUpdate(installerPath, autoRestart)
|
||
}
|
||
|
||
// InstallUpdateWithHash 安装更新包(带哈希验证)
|
||
func (a *App) InstallUpdateWithHash(installerPath string, autoRestart bool, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.InstallUpdateWithHash(installerPath, autoRestart, expectedHash, hashType)
|
||
}
|
||
|
||
// VerifyUpdateFile 验证更新文件哈希值
|
||
func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||
api, err := a.requireUpdateAPI()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return api.VerifyUpdateFile(filePath, expectedHash, hashType)
|
||
}
|
||
|
||
// startAutoUpdateCheck 启动自动更新检查
|
||
func (a *App) startAutoUpdateCheck() {
|
||
if a.updateAPI == nil {
|
||
return
|
||
}
|
||
|
||
config, err := a.updateAPI.GetUpdateConfig()
|
||
if err != nil {
|
||
return
|
||
}
|
||
success, ok := config["success"].(bool)
|
||
if !ok || !success {
|
||
return
|
||
}
|
||
|
||
configData, ok := config["data"].(map[string]interface{})
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
autoCheckEnabled, ok := configData["auto_check_enabled"].(bool)
|
||
if !ok || !autoCheckEnabled {
|
||
return
|
||
}
|
||
|
||
interval, ok := configData["check_interval_minutes"].(int)
|
||
if !ok || interval <= 0 {
|
||
interval = 5
|
||
}
|
||
|
||
// 立即检查一次
|
||
go a.checkUpdate()
|
||
|
||
// 启动定时器
|
||
ticker := time.NewTicker(time.Duration(interval) * time.Minute)
|
||
go func() {
|
||
for range ticker.C {
|
||
a.checkUpdate()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// checkUpdate 执行更新检查
|
||
func (a *App) checkUpdate() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
fmt.Printf("[自动检查更新] 发生错误: %v\n", r)
|
||
}
|
||
}()
|
||
|
||
if a.updateAPI == nil {
|
||
return
|
||
}
|
||
|
||
result, err := a.updateAPI.CheckUpdate()
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
success, ok := result["success"].(bool)
|
||
if !ok || !success {
|
||
return
|
||
}
|
||
|
||
data, ok := result["data"].(map[string]interface{})
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
hasUpdate, ok := data["has_update"].(bool)
|
||
if ok && hasUpdate && a.ctx != nil {
|
||
runtime.EventsEmit(a.ctx, "update-available", data)
|
||
}
|
||
}
|
||
|
||
// ========== 审计日志接口 ==========
|
||
|
||
// GetAuditLogs 获取审计日志
|
||
func (a *App) GetAuditLogs(limit int) ([]map[string]interface{}, error) {
|
||
return a.filesystem.GetAuditLogs(limit)
|
||
}
|
||
|
||
// ========== 文件服务器接口 ==========
|
||
|
||
// GetFileServerURL 获取本地文件服务器的URL
|
||
func (a *App) GetFileServerURL() string {
|
||
return "http://localhost:8073"
|
||
}
|
||
|
||
// DetectFileTypeByContent 通过文件内容检测文件类型(用于小文件)
|
||
func (a *App) DetectFileTypeByContent(path string) (map[string]interface{}, error) {
|
||
return filesystem.DetectFileTypeByContentSimple(path)
|
||
}
|
||
|
||
// ========== 回收站接口 ==========
|
||
|
||
// GetRecycleBinEntries 获取回收站条目
|
||
func (a *App) GetRecycleBinEntries() ([]map[string]interface{}, error) {
|
||
return a.filesystem.GetRecycleBinEntries()
|
||
}
|
||
|
||
// RestoreFromRecycleBin 从回收站恢复文件
|
||
func (a *App) RestoreFromRecycleBin(recyclePath string) error {
|
||
return a.filesystem.RestoreFromRecycleBin(recyclePath)
|
||
}
|
||
|
||
// DeletePermanently 永久删除回收站中的文件
|
||
func (a *App) DeletePermanently(recyclePath string) error {
|
||
return a.filesystem.DeletePermanently(recyclePath)
|
||
}
|
||
|
||
// EmptyRecycleBin 清空回收站
|
||
func (a *App) EmptyRecycleBin() error {
|
||
return a.filesystem.EmptyRecycleBin()
|
||
}
|
||
|
||
// ========== 应用配置接口 ==========
|
||
|
||
// GetAppConfig 获取应用配置
|
||
func (a *App) GetAppConfig() (map[string]interface{}, error) {
|
||
if a.configAPI == nil {
|
||
return nil, fmt.Errorf("配置服务正在初始化中")
|
||
}
|
||
return a.configAPI.GetAppConfig()
|
||
}
|
||
|
||
// SaveAppConfigRequest 保存应用配置请求
|
||
type SaveAppConfigRequest struct {
|
||
Tabs []api.AppTabDefinition `json:"tabs"`
|
||
VisibleTabs []string `json:"visibleTabs"`
|
||
DefaultTab string `json:"defaultTab"`
|
||
}
|
||
|
||
// SaveAppConfig 保存应用配置
|
||
func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, error) {
|
||
if a.configAPI == nil {
|
||
return nil, fmt.Errorf("配置服务正在初始化中")
|
||
}
|
||
|
||
// 保存前检查是否有新启用的模块,需要动态初始化
|
||
oldConfig, _ := a.configAPI.GetAppConfig()
|
||
var oldVisibleTabs []string
|
||
if success, ok := oldConfig["success"].(bool); ok && success {
|
||
if data, ok := oldConfig["data"].(map[string]interface{}); ok {
|
||
if vtInterface, ok := data["visibleTabs"].([]interface{}); ok {
|
||
oldVisibleTabs = common.InterfaceSliceToStringSlice(vtInterface)
|
||
}
|
||
}
|
||
}
|
||
|
||
apiReq := api.SaveAppConfigRequest{
|
||
Tabs: req.Tabs,
|
||
VisibleTabs: req.VisibleTabs,
|
||
DefaultTab: req.DefaultTab,
|
||
}
|
||
|
||
result, err := a.configAPI.SaveAppConfig(apiReq)
|
||
if err != nil {
|
||
return result, err
|
||
}
|
||
|
||
// 保存成功后,检查是否有新启用的模块需要初始化
|
||
if success, ok := result["success"].(bool); ok && success {
|
||
a.handleNewlyEnabledModules(oldVisibleTabs, req.VisibleTabs)
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// handleNewlyEnabledModules 处理新启用的模块
|
||
func (a *App) handleNewlyEnabledModules(oldTabs, newTabs []string) {
|
||
newlyEnabled := common.Difference(newTabs, oldTabs)
|
||
|
||
if len(newlyEnabled) == 0 {
|
||
return
|
||
}
|
||
|
||
fmt.Printf("[模块] 检测到新启用的模块: %v\n", newlyEnabled)
|
||
|
||
for _, tab := range newlyEnabled {
|
||
switch tab {
|
||
case common.TabFileSystem:
|
||
a.initFilesystemModule()
|
||
case common.TabDevice:
|
||
fmt.Println("[模块] 设备测试模块已启用")
|
||
}
|
||
}
|
||
}
|
||
|
||
// initFilesystemModule 延迟初始化文件系统模块
|
||
func (a *App) initFilesystemModule() {
|
||
if a.filesystem != nil {
|
||
fmt.Println("[模块] 文件系统模块已初始化,跳过")
|
||
return
|
||
}
|
||
|
||
fmt.Println("[模块] 延迟初始化文件系统模块...")
|
||
fsConfig := filesystem.DefaultConfig()
|
||
|
||
var err error
|
||
a.filesystem, err = filesystem.NewFileSystemService(fsConfig)
|
||
if err != nil {
|
||
fmt.Printf("[模块] 文件系统模块初始化失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 启动文件服务器
|
||
go a.startFileServer()
|
||
|
||
fmt.Println("[模块] 文件系统模块初始化完成")
|
||
}
|
||
|
||
// ExportPDF 导出PDF文件
|
||
func (a *App) ExportPDF(content string, title string, fileName string, fontSize int, pageWidth int, pageHeight int) (map[string]interface{}, error) {
|
||
if a.pdfAPI == nil {
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"message": "PDF导出功能未初始化",
|
||
}, fmt.Errorf("PDF导出功能未初始化")
|
||
}
|
||
|
||
req := api.PdfExportRequest{
|
||
Content: content,
|
||
Title: title,
|
||
FileName: fileName,
|
||
FontSize: fontSize,
|
||
PageWidth: pageWidth,
|
||
PageHeight: pageHeight,
|
||
}
|
||
|
||
result, err := a.pdfAPI.ExportMarkdownToPDF(req)
|
||
if err != nil {
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
}, err
|
||
}
|
||
|
||
return map[string]interface{}{
|
||
"success": result.Success,
|
||
"message": result.Message,
|
||
"path": result.Path,
|
||
"size": result.Size,
|
||
}, nil
|
||
}
|
||
|
||
// SelectPDFSaveDirectory 选择PDF保存目录
|
||
func (a *App) SelectPDFSaveDirectory() (string, error) {
|
||
if a.pdfAPI == nil {
|
||
return "", fmt.Errorf("PDF导出功能未初始化")
|
||
}
|
||
|
||
return a.pdfAPI.SelectDirectory()
|
||
}
|
||
|