114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// SetBackupDir 设置备份目录(来自配置文件)。
|
|
func (db *DB) SetBackupDir(dir string) { db.BackupPath = dir }
|
|
|
|
// BackupDir 返回备份目录。
|
|
func (db *DB) BackupDir() string {
|
|
if db.BackupPath != "" {
|
|
return db.BackupPath
|
|
}
|
|
return filepath.Join(filepath.Dir(db.Path), "backup")
|
|
}
|
|
|
|
// BackupDaily 当天首次调用时生成一份备份;已有今日备份则跳过。
|
|
// 空库(还没有客户)不生成备份,避免第一天的备份是空壳。
|
|
func (db *DB) BackupDaily(keep int) (string, error) {
|
|
dir := db.BackupDir()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
now := time.Now()
|
|
prefix := "daili-" + now.Format("20060102") + "-"
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, e := range entries {
|
|
if strings.HasPrefix(e.Name(), prefix) {
|
|
return "", nil
|
|
}
|
|
}
|
|
if n, err := db.CustomerCount(); err != nil || n == 0 {
|
|
return "", err
|
|
}
|
|
target := filepath.Join(dir, fmt.Sprintf("daili-%s.db", now.Format("20060102-150405")))
|
|
return db.BackupTo(target, keep)
|
|
}
|
|
|
|
// BackupNow 立即生成一份备份。
|
|
func (db *DB) BackupNow(keep int) (string, error) {
|
|
dir := db.BackupDir()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
target := filepath.Join(dir, fmt.Sprintf("daili-%s.db", time.Now().Format("20060102-150405")))
|
|
return db.BackupTo(target, keep)
|
|
}
|
|
|
|
// BackupTo 用 VACUUM INTO 生成一致性备份,并按保留份数清理。
|
|
func (db *DB) BackupTo(target string, keep int) (string, error) {
|
|
abs, err := filepath.Abs(target)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
|
|
return "", fmt.Errorf("创建备份目录失败: %w", err)
|
|
}
|
|
if _, err := os.Stat(abs); err == nil {
|
|
return abs, fmt.Errorf("备份文件已存在: %s", filepath.Base(abs))
|
|
}
|
|
escaped := strings.ReplaceAll(strings.ReplaceAll(abs, "\\", "/"), "'", "''")
|
|
if _, err := db.sql.Exec("VACUUM INTO '" + escaped + "'"); err != nil {
|
|
return "", err
|
|
}
|
|
if keep > 0 {
|
|
_ = db.pruneBackups(keep)
|
|
}
|
|
return abs, nil
|
|
}
|
|
|
|
// ListBackups 列出备份(新→旧)。
|
|
func (db *DB) ListBackups() ([]string, error) {
|
|
dir := db.BackupDir()
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
var out []string
|
|
for _, e := range entries {
|
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".db") {
|
|
out = append(out, e.Name())
|
|
}
|
|
}
|
|
sort.Sort(sort.Reverse(sort.StringSlice(out)))
|
|
return out, nil
|
|
}
|
|
|
|
func (db *DB) pruneBackups(keep int) error {
|
|
list, err := db.ListBackups()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(list) <= keep {
|
|
return nil
|
|
}
|
|
dir := db.BackupDir()
|
|
for _, name := range list[keep:] {
|
|
_ = os.Remove(filepath.Join(dir, name))
|
|
}
|
|
return nil
|
|
}
|