1319 lines
36 KiB
Go
1319 lines
36 KiB
Go
// [fs-only] 数据库客户端模块已移除(feature/fs-only 分支)
|
||
// 保留模块:文件系统 | Markdown编辑器 | 版本历史(抽屉) | 系统信息 | 更新检查 | PDF导出
|
||
// 顶部Tab仅:file-system(数据库 db-cli 已删除)
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
stdruntime "runtime"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"u-desk/internal/api"
|
||
"u-desk/internal/common"
|
||
"u-desk/internal/filesystem"
|
||
"u-desk/internal/hotkey"
|
||
osssvc "u-desk/internal/ossdrv"
|
||
"u-desk/internal/service"
|
||
"u-desk/internal/sftp"
|
||
"u-desk/internal/storage"
|
||
"u-desk/internal/storage/models"
|
||
"u-desk/internal/system"
|
||
|
||
"golang.org/x/sys/windows/registry"
|
||
|
||
"github.com/wailsapp/wails/v3/pkg/application"
|
||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// App 应用结构体
|
||
type App struct {
|
||
ctx context.Context
|
||
mainWindow *application.WebviewWindow
|
||
updateAPI *api.UpdateAPI
|
||
updateTicker *time.Ticker
|
||
configAPI *api.ConfigAPI
|
||
pdfAPI *api.PdfAPI
|
||
filesystem *filesystem.FileSystemService
|
||
sftpService *sftp.Service
|
||
ossService *osssvc.Service
|
||
profileSvc *service.ProfileService
|
||
isAlwaysOnTop bool
|
||
mu sync.Mutex
|
||
unregisterHotkey func()
|
||
}
|
||
|
||
// App 方法命名约定:
|
||
// - 多参数操作 → XxxRequest 结构体(Wails 自动生成 TS 类型)
|
||
// - 单参数查询/简单操作 → 直接参数
|
||
|
||
// NewApp 创建新的应用实例
|
||
func NewApp() *App {
|
||
return &App{}
|
||
}
|
||
|
||
// SetMainWindow 设置主窗口引用(由 main.go 在创建窗口后调用)
|
||
func (a *App) SetMainWindow(w *application.WebviewWindow) {
|
||
a.mainWindow = w
|
||
}
|
||
|
||
// RegisterGlobalHotkey 注册 Ctrl+Shift+B 全局热键(需在窗口创建后调用)
|
||
func (a *App) RegisterGlobalHotkey() {
|
||
if a.mainWindow == nil {
|
||
return
|
||
}
|
||
a.mu.Lock()
|
||
if a.unregisterHotkey != nil {
|
||
a.mu.Unlock()
|
||
return
|
||
}
|
||
a.mu.Unlock()
|
||
hwnd := uintptr(a.mainWindow.NativeWindow())
|
||
if hwnd == 0 {
|
||
fmt.Println("[全局热键] HWND 为 0,注册跳过")
|
||
return
|
||
}
|
||
const id int32 = 1
|
||
if err := hotkey.Register(hwnd, id, hotkey.ModControl|hotkey.ModShift, 0x42); err != nil {
|
||
fmt.Printf("[全局热键] RegisterHotKey Ctrl+Shift+B 失败: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Println("[全局热键] Ctrl+Shift+B 已注册")
|
||
a.mu.Lock()
|
||
a.unregisterHotkey = func() { hotkey.Unregister(hwnd, id) }
|
||
a.mu.Unlock()
|
||
}
|
||
|
||
// HandleHotkey 处理全局热键回调:切换 BgmBar 显示/隐藏
|
||
func (a *App) HandleHotkey() {
|
||
if a.mainWindow == nil {
|
||
return
|
||
}
|
||
a.mainWindow.EmitEvent("toggle-bgm-bar")
|
||
}
|
||
|
||
// ServiceStartup Wails v3 服务启动生命周期(替代 v2 的 Startup)
|
||
func (a *App) ServiceStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||
a.ctx = ctx
|
||
|
||
// 1. 核心初始化:SQLite(必须同步,很快)
|
||
if _, err := storage.InitFast(); err != nil {
|
||
return fmt.Errorf("SQLite 初始化失败,应用无法启动: %w", err)
|
||
}
|
||
|
||
// 2. 初始化配置服务
|
||
configService, err := api.NewConfigAPI()
|
||
if err != nil {
|
||
return fmt.Errorf("配置服务初始化失败: %w", 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)
|
||
} 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 {
|
||
return fmt.Errorf("模块初始化失败: %w", err)
|
||
}
|
||
|
||
// 5. 清理过期的下载缓存
|
||
storage.CleanupExpiredCache()
|
||
|
||
// 6. 异步初始化:UpdateAPI(涉及网络请求,完全异步)
|
||
go func() {
|
||
if updateAPI, err := api.NewUpdateAPI("https://c.1216.top/last-version.json"); err == nil {
|
||
a.mu.Lock()
|
||
a.updateAPI = updateAPI
|
||
a.mu.Unlock()
|
||
|
||
a.updateAPI.SetContext(ctx)
|
||
a.updateAPI.SetEventEmitter(func(name string, data ...any) {
|
||
if a.mainWindow != nil {
|
||
a.mainWindow.EmitEvent(name, data...)
|
||
}
|
||
})
|
||
a.startAutoUpdateCheck()
|
||
}
|
||
}()
|
||
|
||
// 延迟注册全局热键(轮询等待原生窗口创建完成)
|
||
// RegisterHotKey 必须在创建窗口的同一线程调用,
|
||
// 通过 PostMessage 将注册请求投递到主线程消息循环
|
||
go func() {
|
||
for i := 0; i < 20; i++ {
|
||
time.Sleep(200 * time.Millisecond)
|
||
if a.mainWindow == nil {
|
||
return
|
||
}
|
||
hwnd := uintptr(a.mainWindow.NativeWindow())
|
||
if hwnd != 0 {
|
||
hotkey.PostMessage(hwnd, hotkey.WM_APP_HOTKEY, 0, 0)
|
||
return
|
||
}
|
||
}
|
||
fmt.Println("[全局热键] 等待窗口超时")
|
||
}()
|
||
|
||
return nil
|
||
}
|
||
|
||
// 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, ok := config["data"].(map[string]interface{})
|
||
if !ok {
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
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() {
|
||
if _, err := filesystem.StartLocalFileServer(); err != nil {
|
||
fmt.Printf("[文件服务器] 启动失败: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Printf("[文件服务器] 启动在 http://%s\n", filesystem.GetLocalFileServerAddr())
|
||
}
|
||
|
||
// ServiceShutdown Wails v3 服务关闭生命周期(替代 v2 的 Shutdown)
|
||
func (a *App) ServiceShutdown() error {
|
||
if a.updateTicker != nil {
|
||
a.updateTicker.Stop()
|
||
}
|
||
if a.unregisterHotkey != nil {
|
||
a.unregisterHotkey()
|
||
}
|
||
|
||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
|
||
if a.filesystem != nil {
|
||
fmt.Println("[文件系统服务] 正在关闭...")
|
||
if err := a.filesystem.Close(shutdownCtx); err != nil {
|
||
fmt.Printf("[文件系统服务] 关闭失败: %v\n", err)
|
||
} else {
|
||
fmt.Println("[文件系统服务] 已关闭")
|
||
}
|
||
}
|
||
|
||
fmt.Println("[文件服务器] 正在关闭...")
|
||
if err := filesystem.ShutdownLocalFileServer(); err != nil {
|
||
fmt.Printf("[文件服务器] 关闭失败: %v\n", err)
|
||
} else {
|
||
fmt.Println("[文件服务器] 已关闭")
|
||
}
|
||
|
||
// 关闭所有 SFTP 连接 + 清理临时文件
|
||
if a.sftpService != nil {
|
||
sftp.GetManager().Shutdown()
|
||
}
|
||
storage.CleanupExpiredCache()
|
||
|
||
// 关闭所有 OSS 连接
|
||
osssvc.GetManager().Shutdown()
|
||
|
||
return nil
|
||
}
|
||
|
||
// 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 ""
|
||
}
|
||
|
||
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,
|
||
}
|
||
|
||
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 {
|
||
paths[name] = filepath.Join(homeDir, strings.ToUpper(name[:1])+name[1:])
|
||
}
|
||
}
|
||
|
||
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.mainWindow != nil {
|
||
a.mainWindow.Reload()
|
||
}
|
||
}
|
||
|
||
// ClearCache 清理本地缓存(用于菜单项)
|
||
func (a *App) ClearCache() {
|
||
if a.mainWindow != nil {
|
||
a.mainWindow.EmitEvent("clear-cache")
|
||
}
|
||
}
|
||
|
||
// ========== 窗口控制方法 ==========
|
||
|
||
// WindowMinimize 最小化窗口
|
||
func (a *App) WindowMinimize() {
|
||
if a.mainWindow != nil {
|
||
a.mainWindow.Minimise()
|
||
}
|
||
}
|
||
|
||
// WindowMaximize 最大化/还原窗口
|
||
func (a *App) WindowMaximize() {
|
||
if a.mainWindow == nil {
|
||
return
|
||
}
|
||
if a.mainWindow.IsMaximised() {
|
||
a.mainWindow.UnMaximise()
|
||
} else {
|
||
a.mainWindow.Maximise()
|
||
}
|
||
}
|
||
|
||
// WindowClose 关闭窗口
|
||
func (a *App) WindowClose() {
|
||
application.Get().Quit()
|
||
}
|
||
|
||
// WindowIsMaximized 检查窗口是否最大化
|
||
func (a *App) WindowIsMaximized() bool {
|
||
if a.mainWindow != nil {
|
||
return a.mainWindow.IsMaximised()
|
||
}
|
||
return false
|
||
}
|
||
|
||
// WindowToggleAlwaysOnTop 切换窗口置顶
|
||
func (a *App) WindowToggleAlwaysOnTop() bool {
|
||
if a.mainWindow == nil {
|
||
return false
|
||
}
|
||
a.isAlwaysOnTop = !a.isAlwaysOnTop
|
||
a.mainWindow.SetAlwaysOnTop(a.isAlwaysOnTop)
|
||
return a.isAlwaysOnTop
|
||
}
|
||
|
||
// SetWindowTitleBarColor 设置原生标题栏颜色 + 主题模式(0x00BBGGRR 格式)
|
||
func (a *App) SetWindowTitleBarColor(color uint32, isDark bool) {
|
||
if a.mainWindow == nil || stdruntime.GOOS != "windows" {
|
||
return
|
||
}
|
||
hwnd := uintptr(a.mainWindow.NativeWindow())
|
||
if hwnd == 0 {
|
||
return
|
||
}
|
||
w32.SetTheme(hwnd, isDark)
|
||
w32.SetTitleBarColour(hwnd, color)
|
||
}
|
||
|
||
// ========== 版本更新管理接口 ==========
|
||
|
||
// requireUpdateAPI 检查 updateAPI 是否已初始化
|
||
func (a *App) requireUpdateAPI() (*api.UpdateAPI, error) {
|
||
a.mu.Lock()
|
||
defer a.mu.Unlock()
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI, nil
|
||
}
|
||
|
||
// CheckUpdate 检查更新
|
||
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()
|
||
|
||
a.updateTicker = time.NewTicker(time.Duration(interval) * time.Minute)
|
||
go func() {
|
||
for range a.updateTicker.C {
|
||
a.checkUpdate()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// checkUpdate 执行更新检查
|
||
func (a *App) checkUpdate() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
fmt.Printf("[自动检查更新] 发生错误: %v\n", r)
|
||
}
|
||
}()
|
||
|
||
a.mu.Lock()
|
||
api := a.updateAPI
|
||
a.mu.Unlock()
|
||
|
||
if api == nil {
|
||
return
|
||
}
|
||
|
||
result, err := api.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.mainWindow != nil {
|
||
a.mainWindow.EmitEvent("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 fmt.Sprintf("http://%s", filesystem.GetLocalFileServerAddr())
|
||
}
|
||
|
||
// 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()
|
||
}
|
||
|
||
// ========== SFTP 接口 ==========
|
||
|
||
func (a *App) ensureSftpService() *sftp.Service {
|
||
a.mu.Lock()
|
||
defer a.mu.Unlock()
|
||
if a.sftpService == nil {
|
||
a.sftpService = sftp.NewService()
|
||
}
|
||
return a.sftpService
|
||
}
|
||
|
||
// SftpConnectRequest SFTP 连接请求
|
||
type SftpConnectRequest struct {
|
||
Host string `json:"host"`
|
||
Port int `json:"port"`
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
KeyPath string `json:"key_path"`
|
||
KeyPassphrase string `json:"key_passphrase"`
|
||
}
|
||
|
||
// SftpConnect 建立 SFTP 连接,返回连接标识符 connID
|
||
func (a *App) SftpConnect(req SftpConnectRequest) (string, error) {
|
||
config := &sftp.Config{
|
||
Host: req.Host,
|
||
Port: req.Port,
|
||
Username: req.Username,
|
||
Password: req.Password,
|
||
KeyPath: req.KeyPath,
|
||
KeyPassphrase: req.KeyPassphrase,
|
||
}
|
||
if config.Port == 0 {
|
||
config.Port = 22
|
||
}
|
||
if config.Timeout == 0 {
|
||
config.Timeout = 15 * time.Second
|
||
}
|
||
|
||
svc := a.ensureSftpService()
|
||
_, err := svc.GetManager().Connect(config)
|
||
if err != nil {
|
||
return "", sftp.ToUserMessage(err)
|
||
}
|
||
|
||
connID := sftp.ConnID(config.Host, config.Port)
|
||
return connID, nil
|
||
}
|
||
|
||
// SftpDisconnect 断开 SFTP 连接
|
||
func (a *App) SftpDisconnect(connID string) error {
|
||
parts := strings.SplitN(connID, ":", 2)
|
||
if len(parts) < 2 {
|
||
return fmt.Errorf("无效的连接标识符")
|
||
}
|
||
host := parts[0]
|
||
port, err := strconv.Atoi(parts[1])
|
||
if err != nil {
|
||
return fmt.Errorf("无效的端口号")
|
||
}
|
||
|
||
sftp.GetManager().Disconnect(host, port)
|
||
return nil
|
||
}
|
||
|
||
// SftpListDir SFTP 列出目录
|
||
func (a *App) SftpListDir(connID string, dirPath string) ([]map[string]interface{}, error) {
|
||
return a.ensureSftpService().ListDir(connID, dirPath)
|
||
}
|
||
|
||
// SftpReadFile SFTP 读取文件内容
|
||
func (a *App) SftpReadFile(connID string, filePath string) (string, error) {
|
||
return a.ensureSftpService().ReadFile(connID, filePath)
|
||
}
|
||
|
||
// SftpWriteFileRequest SFTP 写入请求
|
||
type SftpWriteFileRequest struct {
|
||
SessionID string `json:"session_id"`
|
||
Path string `json:"path"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
// SftpWriteFile SFTP 写入文件
|
||
func (a *App) SftpWriteFile(req SftpWriteFileRequest) error {
|
||
return a.ensureSftpService().WriteFile(req.SessionID, req.Path, req.Content)
|
||
}
|
||
|
||
// SftpWriteBase64File SFTP 写入 base64 编码的二进制文件(粘贴图片等)
|
||
func (a *App) SftpWriteBase64File(sessionID, filePath, base64Content string) error {
|
||
return a.ensureSftpService().WriteBase64File(sessionID, filePath, base64Content)
|
||
}
|
||
|
||
// SftpGetFileInfo SFTP 获取文件信息
|
||
func (a *App) SftpGetFileInfo(connID string, filePath string) (map[string]interface{}, error) {
|
||
return a.ensureSftpService().GetFileInfo(connID, filePath)
|
||
}
|
||
|
||
// SftpCreateDir SFTP 创建目录
|
||
func (a *App) SftpCreateDir(connID string, dirPath string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureSftpService().CreateDir(connID, dirPath)
|
||
}
|
||
|
||
// SftpCreateFile SFTP 创建文件
|
||
func (a *App) SftpCreateFile(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureSftpService().CreateFile(connID, filePath)
|
||
}
|
||
|
||
// SftpDeletePath SFTP 删除文件或目录
|
||
func (a *App) SftpDeletePath(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureSftpService().DeletePath(connID, filePath)
|
||
}
|
||
|
||
// SftpRenamePathRequest SFTP 重命名请求
|
||
type SftpRenamePathRequest struct {
|
||
SessionID string `json:"session_id"`
|
||
OldPath string `json:"old_path"`
|
||
NewPath string `json:"new_path"`
|
||
}
|
||
|
||
// SftpRenamePath SFTP 重命名文件或目录
|
||
func (a *App) SftpRenamePath(req SftpRenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureSftpService().RenamePath(req.SessionID, req.OldPath, req.NewPath)
|
||
}
|
||
|
||
// SftpDownloadToTemp 下载远程文件到本地临时目录(用于预览)
|
||
func (a *App) SftpDownloadToTemp(connID string, remotePath string) (string, error) {
|
||
return a.ensureSftpService().DownloadToTemp(connID, remotePath)
|
||
}
|
||
|
||
// SftpDownloadSiteForPreview 下载 HTML 及其网站资源到本地临时目录
|
||
func (a *App) SftpDownloadSiteForPreview(connID string, remotePath string) (string, error) {
|
||
return a.ensureSftpService().DownloadSiteForPreview(connID, remotePath)
|
||
}
|
||
|
||
// SftpDownloadToTempCached 带缓存的 SFTP 下载(命中缓存直接返回本地路径)
|
||
func (a *App) SftpDownloadToTempCached(connID string, remotePath string, fileSize int64, modTime string) (string, error) {
|
||
return a.ensureSftpService().DownloadToTempCached(connID, remotePath, fileSize, modTime)
|
||
}
|
||
|
||
// SftpGetCommonPaths 获取 SFTP 远程主机常用路径
|
||
func (a *App) SftpGetCommonPaths(connID string) (map[string]string, error) {
|
||
return a.ensureSftpService().GetCommonPaths(connID)
|
||
}
|
||
|
||
// SftpGetSystemInfo 获取 SFTP 远程主机系统信息(CPU/内存/磁盘)
|
||
func (a *App) SftpGetSystemInfo(connID string) (map[string]interface{}, error) {
|
||
return a.ensureSftpService().GetSystemInfo(connID)
|
||
}
|
||
|
||
// ========== OSS 接口 ==========
|
||
|
||
func (a *App) ensureOssService() *osssvc.Service {
|
||
a.mu.Lock()
|
||
defer a.mu.Unlock()
|
||
if a.ossService == nil {
|
||
a.ossService = osssvc.NewService()
|
||
}
|
||
return a.ossService
|
||
}
|
||
|
||
type OssConnectRequest struct {
|
||
Provider string `json:"provider"`
|
||
AccessKey string `json:"access_key"`
|
||
SecretKey string `json:"secret_key"`
|
||
Endpoint string `json:"endpoint"`
|
||
}
|
||
|
||
func (a *App) OssConnect(req OssConnectRequest) (string, error) {
|
||
if err := a.ensureOssService().GetManager().Connect(req.Provider, req.AccessKey, req.SecretKey, req.Endpoint); err != nil {
|
||
return "", err
|
||
}
|
||
return req.Provider, nil
|
||
}
|
||
|
||
// OssDisconnect 断开 OSS 连接
|
||
func (a *App) OssDisconnect(connID string) error {
|
||
osssvc.GetManager().Disconnect(connID)
|
||
return nil
|
||
}
|
||
|
||
// OssListDir OSS 列出目录
|
||
func (a *App) OssListDir(connID string, prefix string) ([]map[string]interface{}, error) {
|
||
return a.ensureOssService().ListDir(connID, prefix)
|
||
}
|
||
|
||
// OssDownloadToTemp OSS 下载到临时文件
|
||
func (a *App) OssDownloadToTemp(connID string, key string) (string, error) {
|
||
return a.ensureOssService().DownloadToTemp(connID, key)
|
||
}
|
||
|
||
// OssDownloadSiteForPreview OSS 下载 HTML 及其引用的资源到临时目录
|
||
func (a *App) OssDownloadSiteForPreview(connID string, key string) (string, error) {
|
||
return a.ensureOssService().DownloadSiteForPreview(connID, key)
|
||
}
|
||
|
||
// OssDownloadToTempCached 带缓存的 OSS 下载(命中缓存直接返回本地路径)
|
||
func (a *App) OssDownloadToTempCached(connID string, key string, fileSize int64, modTime string) (string, error) {
|
||
return a.ensureOssService().DownloadToTempCached(connID, key, fileSize, modTime)
|
||
}
|
||
|
||
// OssReadFile OSS 读取文件
|
||
func (a *App) OssReadFile(connID string, key string) (string, error) {
|
||
return a.ensureOssService().ReadFile(connID, key)
|
||
}
|
||
|
||
// OssWriteFile OSS 写入文件
|
||
func (a *App) OssWriteFile(connID string, key string, content string) error {
|
||
return a.ensureOssService().WriteFile(connID, key, content)
|
||
}
|
||
|
||
// OssWriteBase64File OSS 写入 base64 编码文件
|
||
func (a *App) OssWriteBase64File(connID string, key string, base64Content string) error {
|
||
return a.ensureOssService().WriteBase64File(connID, key, base64Content)
|
||
}
|
||
|
||
// OssGetFileInfo OSS 获取文件信息
|
||
func (a *App) OssGetFileInfo(connID string, key string) (map[string]interface{}, error) {
|
||
return a.ensureOssService().GetFileInfo(connID, key)
|
||
}
|
||
|
||
// OssCreateDir OSS 创建目录
|
||
func (a *App) OssCreateDir(connID string, dirPath string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureOssService().CreateDir(connID, dirPath)
|
||
}
|
||
|
||
// OssCreateFile OSS 创建文件
|
||
func (a *App) OssCreateFile(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureOssService().CreateFile(connID, filePath)
|
||
}
|
||
|
||
// OssDeletePath OSS 删除
|
||
func (a *App) OssDeletePath(connID string, key string) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureOssService().DeletePath(connID, key)
|
||
}
|
||
|
||
// OssRenamePathRequest OSS 重命名请求
|
||
type OssRenamePathRequest struct {
|
||
ConnID string `json:"conn_id"`
|
||
OldPath string `json:"old_path"`
|
||
NewPath string `json:"new_path"`
|
||
}
|
||
|
||
// OssRenamePath OSS 重命名
|
||
func (a *App) OssRenamePath(req OssRenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||
return a.ensureOssService().RenamePath(req.ConnID, req.OldPath, req.NewPath)
|
||
}
|
||
|
||
// OssGetCommonPaths OSS 获取常用路径
|
||
func (a *App) OssGetCommonPaths(connID string) (map[string]string, error) {
|
||
return a.ensureOssService().GetCommonPaths(connID)
|
||
}
|
||
|
||
// OssGetSignedURL OSS 获取预签名 URL
|
||
func (a *App) OssGetSignedURL(connID string, key string) (string, error) {
|
||
return a.ensureOssService().GetSignedURL(connID, key)
|
||
}
|
||
|
||
// --- 连接配置 CRUD (SQLite 持久化) ---
|
||
|
||
type SaveProfileRequest struct {
|
||
ID *uint `json:"id"`
|
||
Name string `json:"name"`
|
||
Host string `json:"host"`
|
||
Port int `json:"port"`
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
KeyPath string `json:"key_path"`
|
||
Type string `json:"type"`
|
||
Provider string `json:"provider"`
|
||
Token string `json:"token"`
|
||
AccessKey string `json:"access_key"`
|
||
SecretKey string `json:"secret_key"`
|
||
Bucket string `json:"bucket"`
|
||
Region string `json:"region"`
|
||
Endpoint string `json:"endpoint"`
|
||
LastConnected *int64 `json:"last_connected"`
|
||
}
|
||
|
||
func (a *App) ensureProfileSvc() *service.ProfileService {
|
||
a.mu.Lock()
|
||
defer a.mu.Unlock()
|
||
if a.profileSvc == nil {
|
||
a.profileSvc = service.NewProfileService()
|
||
}
|
||
return a.profileSvc
|
||
}
|
||
|
||
func (a *App) LoadConnectionProfiles() ([]map[string]interface{}, error) {
|
||
list, err := a.ensureProfileSvc().ListProfiles()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result := make([]map[string]interface{}, len(list))
|
||
for i, p := range list {
|
||
result[i] = map[string]interface{}{
|
||
"id": float64(p.ID),
|
||
"name": p.Name,
|
||
"host": p.Host,
|
||
"port": p.Port,
|
||
"username": p.Username,
|
||
"password": p.Password,
|
||
"keyPath": p.KeyPath,
|
||
"type": p.Type,
|
||
"provider": p.Provider,
|
||
"token": p.Token,
|
||
"accessKey": p.AccessKey,
|
||
"secretKey": p.SecretKey,
|
||
"bucket": p.Bucket,
|
||
"region": p.Region,
|
||
"endpoint": p.Endpoint,
|
||
"lastConnected": p.LastConnected,
|
||
"sortOrder": float64(p.SortOrder),
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (a *App) SaveConnectionProfile(req SaveProfileRequest) (map[string]interface{}, error) {
|
||
p := &models.ConnectionProfile{
|
||
Name: req.Name, Host: req.Host, Port: req.Port,
|
||
Username: req.Username, Password: req.Password,
|
||
KeyPath: req.KeyPath, Type: req.Type, Provider: req.Provider, Token: req.Token,
|
||
AccessKey: req.AccessKey, SecretKey: req.SecretKey,
|
||
Bucket: req.Bucket, Region: req.Region, Endpoint: req.Endpoint,
|
||
}
|
||
if req.LastConnected != nil {
|
||
t := time.Unix(*req.LastConnected, 0)
|
||
p.LastConnected = &t
|
||
}
|
||
if req.ID != nil {
|
||
p.ID = *req.ID
|
||
}
|
||
if err := a.ensureProfileSvc().SaveProfile(p); err != nil {
|
||
return nil, err
|
||
}
|
||
return map[string]interface{}{"id": float64(p.ID), "success": true}, nil
|
||
}
|
||
|
||
func (a *App) DeleteConnectionProfile(id uint) error {
|
||
return a.ensureProfileSvc().DeleteProfile(id)
|
||
}
|
||
|
||
func (a *App) GetLocalSystemInfo() (map[string]interface{}, error) {
|
||
info := make(map[string]interface{})
|
||
|
||
cpuInfo, err := system.GetCPUInfo()
|
||
if err == nil && cpuInfo != nil {
|
||
if v, ok := cpuInfo["usage"].(string); ok {
|
||
info["cpu_usage"] = v
|
||
}
|
||
}
|
||
|
||
memInfo, err := system.GetMemoryInfo()
|
||
if err == nil && memInfo != nil {
|
||
if v, ok := memInfo["usage"].(string); ok {
|
||
info["mem_usage"] = v
|
||
}
|
||
}
|
||
|
||
diskInfos, err := system.GetDiskInfo()
|
||
if err == nil && len(diskInfos) > 0 {
|
||
if v, ok := diskInfos[0]["usage"].(string); ok {
|
||
info["disk_usage"] = v
|
||
}
|
||
}
|
||
|
||
return info, nil
|
||
}
|
||
|
||
// ========== BGM 播放列表持久化 ==========
|
||
|
||
// BgmPlaylistItem 播放列表条目
|
||
type BgmPlaylistItem struct {
|
||
Name string `json:"name"`
|
||
Path string `json:"path"`
|
||
ProfileID string `json:"profile_id"`
|
||
}
|
||
|
||
// BgmGetPlaylist 获取播放列表
|
||
func (a *App) BgmGetPlaylist() ([]BgmPlaylistItem, error) {
|
||
db := storage.GetDB()
|
||
if db == nil {
|
||
return nil, fmt.Errorf("数据库未初始化")
|
||
}
|
||
var rows []models.BgmPlaylist
|
||
db.Order("sort ASC").Find(&rows)
|
||
items := make([]BgmPlaylistItem, len(rows))
|
||
for i, r := range rows {
|
||
items[i] = BgmPlaylistItem{Name: r.Name, Path: r.Path, ProfileID: r.ProfileID}
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
// BgmSavePlaylist 全量保存播放列表(前端调用时传完整列表)
|
||
func (a *App) BgmSavePlaylist(items []BgmPlaylistItem) error {
|
||
db := storage.GetDB()
|
||
if db == nil {
|
||
return fmt.Errorf("数据库未初始化")
|
||
}
|
||
return db.Transaction(func(tx *gorm.DB) error {
|
||
if err := tx.Exec("DELETE FROM bgm_playlist").Error; err != nil {
|
||
return err
|
||
}
|
||
sort := uint(0)
|
||
for _, item := range items {
|
||
if item.Name == "" || item.Path == "" {
|
||
continue
|
||
}
|
||
tx.Create(&models.BgmPlaylist{Name: item.Name, Path: item.Path, ProfileID: item.ProfileID, Sort: sort})
|
||
sort++
|
||
}
|
||
return nil
|
||
})
|
||
}
|