Files
Ant-Browser/backend/app_snapshot_api.go

163 lines
3.9 KiB
Go

package backend
import (
"ant-chrome/backend/internal/snapshot"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
// getProfileForSnapshot 获取实例信息(加锁)
func (a *App) getProfileForSnapshot(profileId string) (*BrowserProfile, error) {
a.browserMgr.Mutex.Lock()
defer a.browserMgr.Mutex.Unlock()
profile, exists := a.browserMgr.Profiles[profileId]
if !exists {
return nil, fmt.Errorf("实例不存在: %s", profileId)
}
return profile, nil
}
// BrowserSnapshotCreate 创建快照
func (a *App) BrowserSnapshotCreate(profileId, name string) (SnapshotInfo, error) {
profile, err := a.getProfileForSnapshot(profileId)
if err != nil {
return SnapshotInfo{}, err
}
if profile.Running {
return SnapshotInfo{}, fmt.Errorf("请先停止实例再创建快照")
}
userDataDir := a.browserMgr.ResolveUserDataDir(profile)
if _, err := os.Stat(userDataDir); os.IsNotExist(err) {
return SnapshotInfo{}, fmt.Errorf("用户数据目录不存在,无法创建快照")
}
snapDir, err := a.snapshotDir(profileId)
if err != nil {
return SnapshotInfo{}, err
}
snapshotID := uuid.NewString()
safeName := strings.ReplaceAll(name, string(os.PathSeparator), "_")
zipPath := filepath.Join(snapDir, snapshotID+"_"+safeName+".zip")
metaPath := filepath.Join(snapDir, snapshotID+"_"+safeName+".meta.json")
if err := snapshot.ZipDir(userDataDir, zipPath); err != nil {
return SnapshotInfo{}, fmt.Errorf("压缩失败: %w", err)
}
fi, err := os.Stat(zipPath)
if err != nil {
return SnapshotInfo{}, err
}
sizeMB := float64(fi.Size()) / 1024 / 1024
info := SnapshotInfo{
SnapshotId: snapshotID,
ProfileId: profileId,
Name: name,
SizeMB: sizeMB,
CreatedAt: time.Now().Format(time.RFC3339),
FilePath: zipPath,
}
metaData, _ := json.Marshal(info)
if err := os.WriteFile(metaPath, metaData, 0o644); err != nil {
return SnapshotInfo{}, err
}
info.FilePath = ""
return info, nil
}
// BrowserSnapshotList 列出实例的所有快照
func (a *App) BrowserSnapshotList(profileId string) ([]SnapshotInfo, error) {
snapDir, err := a.snapshotDir(profileId)
if err != nil {
return nil, err
}
entries, err := os.ReadDir(snapDir)
if err != nil {
if os.IsNotExist(err) {
return []SnapshotInfo{}, nil
}
return nil, err
}
var list []SnapshotInfo
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".meta.json") {
continue
}
data, err := os.ReadFile(filepath.Join(snapDir, entry.Name()))
if err != nil {
continue
}
var info SnapshotInfo
if err := json.Unmarshal(data, &info); err != nil {
continue
}
info.FilePath = ""
list = append(list, info)
}
sort.Slice(list, func(i, j int) bool {
return list[i].CreatedAt > list[j].CreatedAt
})
return list, nil
}
// BrowserSnapshotRestore 恢复快照
func (a *App) BrowserSnapshotRestore(profileId, snapshotId string) error {
profile, err := a.getProfileForSnapshot(profileId)
if err != nil {
return err
}
if profile.Running {
return fmt.Errorf("请先停止实例再恢复快照")
}
snapDir, err := a.snapshotDir(profileId)
if err != nil {
return err
}
metaPath, zipPath, err := snapshot.FindFiles(snapDir, snapshotId)
if err != nil {
return err
}
_ = metaPath
userDataDir := a.browserMgr.ResolveUserDataDir(profile)
if err := os.RemoveAll(userDataDir); err != nil {
return fmt.Errorf("清空用户数据目录失败: %w", err)
}
if err := os.MkdirAll(userDataDir, 0o755); err != nil {
return err
}
return snapshot.UnzipTo(zipPath, userDataDir)
}
// BrowserSnapshotDelete 删除快照
func (a *App) BrowserSnapshotDelete(profileId, snapshotId string) error {
snapDir, err := a.snapshotDir(profileId)
if err != nil {
return err
}
metaPath, zipPath, err := snapshot.FindFiles(snapDir, snapshotId)
if err != nil {
return err
}
_ = os.Remove(zipPath)
_ = os.Remove(metaPath)
return nil
}