mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master
version: 1.2.0
source-ref: D:\code\open_source\ant-chrome master
source-commit: f3d7ec5
merged-public-base: 3d264eb
197 lines
5.4 KiB
Go
197 lines
5.4 KiB
Go
package backend
|
||
|
||
import (
|
||
"ant-chrome/backend/internal/backup"
|
||
"archive/zip"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"io/fs"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
func backupWritePackageZip(zipPath string, scope backup.Scope, manifest backup.Manifest, emitProgress func(phase string, progress int, message string, meta *backupProgressMeta)) (int, int, int, error) {
|
||
emit := func(phase string, progress int, message string, meta *backupProgressMeta) {
|
||
if emitProgress != nil {
|
||
emitProgress(phase, progress, message, meta)
|
||
}
|
||
}
|
||
if err := os.MkdirAll(filepath.Dir(zipPath), 0755); err != nil {
|
||
return 0, 0, 0, fmt.Errorf("创建导出目录失败: %w", err)
|
||
}
|
||
emit("writing", 18, "正在创建导出文件...", nil)
|
||
|
||
tmpPath := zipPath + ".tmp"
|
||
f, err := os.Create(tmpPath)
|
||
if err != nil {
|
||
return 0, 0, 0, fmt.Errorf("创建导出文件失败: %w", err)
|
||
}
|
||
w := zip.NewWriter(f)
|
||
|
||
includedEntries := 0
|
||
skippedEntries := 0
|
||
fileCount := 0
|
||
|
||
writeErr := func() error {
|
||
emit("writing", 20, "正在写入备份清单...", nil)
|
||
manifestData, err := json.MarshalIndent(manifest, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
mw, err := w.Create("manifest.json")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err := mw.Write(manifestData); err != nil {
|
||
return err
|
||
}
|
||
fileCount++
|
||
|
||
totalEntries := len(scope.Entries)
|
||
if totalEntries == 0 {
|
||
emit("writing", 90, "没有可导出的目录条目", nil)
|
||
}
|
||
for i, entry := range scope.Entries {
|
||
meta := &backupProgressMeta{
|
||
ComponentID: entry.ID,
|
||
ComponentName: backupResolveEntryComponentName(entry),
|
||
EntryIndex: i + 1,
|
||
EntryTotal: totalEntries,
|
||
}
|
||
startProgress := 20 + int(float64(i)/float64(totalEntries)*70)
|
||
emit("writing", startProgress, fmt.Sprintf("开始处理组件 %d/%d:%s", i+1, totalEntries, meta.ComponentName), meta)
|
||
|
||
info, err := os.Stat(entry.SourcePath)
|
||
if err != nil {
|
||
if os.IsNotExist(err) && !entry.Required {
|
||
skippedEntries++
|
||
progress := 20 + int(float64(i+1)/float64(totalEntries)*70)
|
||
emit("writing", progress, fmt.Sprintf("组件跳过:%s(源路径不存在)", meta.ComponentName), meta)
|
||
continue
|
||
}
|
||
return fmt.Errorf("读取导出源失败(%s): %w", entry.ID, err)
|
||
}
|
||
entryAddedFiles := 0
|
||
if info.IsDir() {
|
||
n, err := backupZipAddDir(w, entry.SourcePath, entry.ArchivePath, zipPath)
|
||
if err != nil {
|
||
return fmt.Errorf("写入目录失败(%s): %w", entry.ID, err)
|
||
}
|
||
fileCount += n
|
||
entryAddedFiles = n
|
||
} else {
|
||
if backupSamePath(entry.SourcePath, zipPath) {
|
||
skippedEntries++
|
||
progress := 20 + int(float64(i+1)/float64(totalEntries)*70)
|
||
emit("writing", progress, fmt.Sprintf("组件跳过:%s(导出文件本身)", meta.ComponentName), meta)
|
||
continue
|
||
}
|
||
if err := backupZipAddFile(w, entry.SourcePath, strings.TrimSuffix(entry.ArchivePath, "/")); err != nil {
|
||
return fmt.Errorf("写入文件失败(%s): %w", entry.ID, err)
|
||
}
|
||
fileCount++
|
||
entryAddedFiles = 1
|
||
}
|
||
includedEntries++
|
||
progress := 20 + int(float64(i+1)/float64(totalEntries)*70)
|
||
emit("writing", progress, fmt.Sprintf("组件完成:%s(新增 %d 个文件)", meta.ComponentName, entryAddedFiles), meta)
|
||
}
|
||
return nil
|
||
}()
|
||
|
||
closeErr := w.Close()
|
||
fileCloseErr := f.Close()
|
||
if writeErr != nil {
|
||
emit("error", 100, writeErr.Error(), nil)
|
||
_ = os.Remove(tmpPath)
|
||
return 0, 0, 0, writeErr
|
||
}
|
||
if closeErr != nil {
|
||
emit("error", 100, closeErr.Error(), nil)
|
||
_ = os.Remove(tmpPath)
|
||
return 0, 0, 0, closeErr
|
||
}
|
||
if fileCloseErr != nil {
|
||
emit("error", 100, fileCloseErr.Error(), nil)
|
||
_ = os.Remove(tmpPath)
|
||
return 0, 0, 0, fileCloseErr
|
||
}
|
||
if err := os.Rename(tmpPath, zipPath); err != nil {
|
||
emit("error", 100, err.Error(), nil)
|
||
_ = os.Remove(tmpPath)
|
||
return 0, 0, 0, fmt.Errorf("写入导出文件失败: %w", err)
|
||
}
|
||
emit("done", 100, "导出完成", nil)
|
||
return includedEntries, skippedEntries, fileCount, nil
|
||
}
|
||
|
||
func backupZipAddDir(w *zip.Writer, srcDir, archiveBase, outputZipPath string) (int, error) {
|
||
base := strings.TrimSuffix(filepath.ToSlash(strings.TrimSpace(archiveBase)), "/")
|
||
if base == "" {
|
||
return 0, fmt.Errorf("archive base 不能为空")
|
||
}
|
||
fileCount := 0
|
||
err := filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if backupSamePath(path, outputZipPath) {
|
||
return nil
|
||
}
|
||
if d.Type()&os.ModeSymlink != 0 {
|
||
return nil
|
||
}
|
||
rel, err := filepath.Rel(srcDir, path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if rel == "." {
|
||
return nil
|
||
}
|
||
rel = filepath.ToSlash(rel)
|
||
targetName := base + "/" + rel
|
||
if d.IsDir() {
|
||
_, err := w.Create(strings.TrimSuffix(targetName, "/") + "/")
|
||
return err
|
||
}
|
||
if err := backupZipAddFile(w, path, targetName); err != nil {
|
||
return err
|
||
}
|
||
fileCount++
|
||
return nil
|
||
})
|
||
return fileCount, err
|
||
}
|
||
|
||
func backupZipAddFile(w *zip.Writer, srcFile, archivePath string) error {
|
||
info, err := os.Stat(srcFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if info.IsDir() {
|
||
return fmt.Errorf("不支持将目录按文件写入: %s", srcFile)
|
||
}
|
||
header, err := zip.FileInfoHeader(info)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
header.Name = strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(archivePath)), "/")
|
||
header.Method = zip.Deflate
|
||
if header.Name == "" {
|
||
return fmt.Errorf("archivePath 不能为空")
|
||
}
|
||
writer, err := w.CreateHeader(header)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
in, err := os.Open(srcFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer in.Close()
|
||
_, err = io.Copy(writer, in)
|
||
return err
|
||
}
|