88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package update
|
|
|
|
import (
|
|
domain "RenewCMS/internal/domain/update"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var errBackupExists = errors.New("a backup with this name already exists")
|
|
|
|
const (
|
|
backupsToKeep = 3
|
|
backupFileExtension = ".db"
|
|
backupFilePrefix = "renewcms-"
|
|
backupTimeLayout = "20060102T150405.000000000Z"
|
|
)
|
|
|
|
type SQLiteDatabaseBackup struct {
|
|
db *gorm.DB
|
|
directory string
|
|
now func() time.Time
|
|
}
|
|
|
|
var _ domain.DatabaseBackup = &SQLiteDatabaseBackup{}
|
|
|
|
func NewSQLiteDatabaseBackup(db *gorm.DB, directory string, now func() time.Time) *SQLiteDatabaseBackup {
|
|
return &SQLiteDatabaseBackup{db: db, directory: directory, now: now}
|
|
}
|
|
|
|
func (b *SQLiteDatabaseBackup) Backup() (string, error) {
|
|
if err := os.MkdirAll(b.directory, os.ModePerm); err != nil {
|
|
return "", err
|
|
}
|
|
target := filepath.Join(b.directory, backupFilePrefix+b.now().UTC().Format(backupTimeLayout)+backupFileExtension)
|
|
if _, err := os.Stat(target); err == nil {
|
|
return "", errBackupExists
|
|
}
|
|
if err := b.db.Exec("VACUUM INTO ?", target).Error; err != nil {
|
|
_ = os.Remove(target)
|
|
return "", err
|
|
}
|
|
if err := b.prune(); err != nil {
|
|
return "", err
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
type backupFile struct {
|
|
path string
|
|
modified time.Time
|
|
}
|
|
|
|
func (b *SQLiteDatabaseBackup) prune() error {
|
|
entries, err := os.ReadDir(b.directory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
files := make([]backupFile, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), backupFileExtension) {
|
|
continue
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
files = append(files, backupFile{path: filepath.Join(b.directory, entry.Name()), modified: info.ModTime()})
|
|
}
|
|
sort.Slice(files, func(i, j int) bool {
|
|
if files[i].modified.Equal(files[j].modified) {
|
|
return files[i].path > files[j].path
|
|
}
|
|
return files[i].modified.After(files[j].modified)
|
|
})
|
|
for _, stale := range files[min(backupsToKeep, len(files)):] {
|
|
if err := os.Remove(stale.path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|