mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-12 01:09:42 +08:00
200 lines
5.9 KiB
Go
200 lines
5.9 KiB
Go
package app
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
stdRuntime "runtime"
|
||
"strings"
|
||
|
||
"GoNavi-Wails/internal/appdata"
|
||
"GoNavi-Wails/internal/connection"
|
||
"GoNavi-Wails/internal/db"
|
||
"GoNavi-Wails/internal/logger"
|
||
|
||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||
)
|
||
|
||
var migratableDataRootEntries = []string{
|
||
"connections.json",
|
||
"global_proxy.json",
|
||
"ai_config.json",
|
||
"sessions",
|
||
"drivers",
|
||
}
|
||
|
||
func (a *App) GetDataRootDirectoryInfo() connection.QueryResult {
|
||
return connection.QueryResult{Success: true, Message: "OK", Data: dataRootInfoPayload(a.configDir)}
|
||
}
|
||
|
||
func (a *App) SelectDataRootDirectory(currentDir string) connection.QueryResult {
|
||
defaultDir := strings.TrimSpace(currentDir)
|
||
if defaultDir == "" {
|
||
defaultDir = appdata.MustResolveActiveRoot()
|
||
}
|
||
if !filepath.IsAbs(defaultDir) {
|
||
if abs, err := filepath.Abs(defaultDir); err == nil {
|
||
defaultDir = abs
|
||
}
|
||
}
|
||
|
||
selection, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{
|
||
Title: "选择 GoNavi 数据目录",
|
||
DefaultDirectory: defaultDir,
|
||
CanCreateDirectories: true,
|
||
})
|
||
if err != nil {
|
||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||
}
|
||
if strings.TrimSpace(selection) == "" {
|
||
return connection.QueryResult{Success: false, Message: "已取消"}
|
||
}
|
||
resolved, err := appdata.ResolveRoot(selection)
|
||
if err != nil {
|
||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||
}
|
||
return connection.QueryResult{Success: true, Data: dataRootInfoPayload(resolved)}
|
||
}
|
||
|
||
func (a *App) ApplyDataRootDirectory(directory string, migrate bool) connection.QueryResult {
|
||
currentRoot := appdata.MustResolveActiveRoot()
|
||
targetRoot, err := appdata.ResolveRoot(directory)
|
||
if err != nil {
|
||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||
}
|
||
if filepath.Clean(currentRoot) == filepath.Clean(targetRoot) {
|
||
a.configDir = targetRoot
|
||
db.SetExternalDriverDownloadDirectory(appdata.DriverRoot(targetRoot))
|
||
return connection.QueryResult{
|
||
Success: true,
|
||
Message: "数据目录未发生变化",
|
||
Data: dataRootInfoPayload(targetRoot),
|
||
}
|
||
}
|
||
|
||
if migrate {
|
||
if err := migrateDataRootContents(currentRoot, targetRoot); err != nil {
|
||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||
}
|
||
}
|
||
|
||
appliedRoot, err := appdata.SetActiveRoot(targetRoot)
|
||
if err != nil {
|
||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||
}
|
||
a.configDir = appliedRoot
|
||
db.SetExternalDriverDownloadDirectory(appdata.DriverRoot(appliedRoot))
|
||
message := "数据目录已更新,建议重启应用以让 AI 与其他运行态模块完全切换到新目录"
|
||
if migrate {
|
||
message = "数据已迁移并切换到新目录,建议重启应用以完成全部模块切换"
|
||
}
|
||
return connection.QueryResult{Success: true, Message: message, Data: dataRootInfoPayload(appliedRoot)}
|
||
}
|
||
|
||
func (a *App) OpenDataRootDirectory() connection.QueryResult {
|
||
root := appdata.MustResolveActiveRoot()
|
||
if stat, err := os.Stat(root); err != nil || !stat.IsDir() {
|
||
return connection.QueryResult{Success: false, Message: "数据目录不存在或不可访问"}
|
||
}
|
||
var cmd *exec.Cmd
|
||
switch stdRuntime.GOOS {
|
||
case "darwin":
|
||
cmd = exec.Command("open", root)
|
||
case "windows":
|
||
cmd = exec.Command("explorer", root)
|
||
case "linux":
|
||
cmd = exec.Command("xdg-open", root)
|
||
default:
|
||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("当前平台暂不支持打开目录:%s", stdRuntime.GOOS)}
|
||
}
|
||
if err := cmd.Start(); err != nil {
|
||
logger.Error(err, "打开数据目录失败")
|
||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("打开数据目录失败:%v", err)}
|
||
}
|
||
return connection.QueryResult{Success: true, Message: "已打开数据目录", Data: dataRootInfoPayload(root)}
|
||
}
|
||
|
||
func migrateDataRootContents(sourceRoot string, targetRoot string) error {
|
||
sourceRoot = strings.TrimSpace(sourceRoot)
|
||
targetRoot = strings.TrimSpace(targetRoot)
|
||
if sourceRoot == "" || targetRoot == "" {
|
||
return fmt.Errorf("数据目录不能为空")
|
||
}
|
||
if filepath.Clean(sourceRoot) == filepath.Clean(targetRoot) {
|
||
return nil
|
||
}
|
||
if err := os.MkdirAll(targetRoot, 0o755); err != nil {
|
||
return fmt.Errorf("创建目标数据目录失败:%w", err)
|
||
}
|
||
for _, name := range migratableDataRootEntries {
|
||
sourcePath := filepath.Join(sourceRoot, name)
|
||
info, err := os.Stat(sourcePath)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
continue
|
||
}
|
||
return fmt.Errorf("读取源数据失败(%s):%w", name, err)
|
||
}
|
||
targetPath := filepath.Join(targetRoot, name)
|
||
if info.IsDir() {
|
||
if err := copyDir(sourcePath, targetPath); err != nil {
|
||
return fmt.Errorf("迁移目录失败(%s):%w", name, err)
|
||
}
|
||
continue
|
||
}
|
||
if err := copyFile(sourcePath, targetPath, info.Mode()); err != nil {
|
||
return fmt.Errorf("迁移文件失败(%s):%w", name, err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func copyDir(sourceDir string, targetDir string) error {
|
||
return filepath.WalkDir(sourceDir, func(path string, entry os.DirEntry, walkErr error) error {
|
||
if walkErr != nil {
|
||
return walkErr
|
||
}
|
||
relativePath, err := filepath.Rel(sourceDir, path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
targetPath := filepath.Join(targetDir, relativePath)
|
||
if entry.IsDir() {
|
||
info, err := entry.Info()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return os.MkdirAll(targetPath, info.Mode())
|
||
}
|
||
info, err := entry.Info()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return copyFile(path, targetPath, info.Mode())
|
||
})
|
||
}
|
||
|
||
func copyFile(sourcePath string, targetPath string, mode os.FileMode) error {
|
||
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
|
||
return err
|
||
}
|
||
sourceFile, err := os.Open(sourcePath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer sourceFile.Close()
|
||
|
||
targetFile, err := os.Create(targetPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer targetFile.Close()
|
||
|
||
if _, err := io.Copy(targetFile, sourceFile); err != nil {
|
||
return err
|
||
}
|
||
return os.Chmod(targetPath, mode)
|
||
}
|