feat(backup): "keep every backup" option — timestamped files per exit
Sub-option of "back up on every exit": when on, backup.Run/RunADIF stamp the filename with the time (opslog-YYYY-MM-DD-HHMMSS.db) so each run is a distinct file instead of overwriting the day's snapshot. Rotation still trims to the newest N (raise Rotation for a longer history). Threaded as a `unique` flag through runConfiguredBackup/backupLogADIF from BackupSettings.KeepAll (keyBackupKeepAll); UI sub-checkbox shown only when EveryExit is on.
This commit is contained in:
@@ -23,10 +23,10 @@ import (
|
||||
// every time we add a field).
|
||||
type Settings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Folder string `json:"folder"` // empty → DefaultFolder
|
||||
Rotation int `json:"rotation"` // how many backups to keep; 0/neg = 5
|
||||
Zip bool `json:"zip"` // compress with deflate
|
||||
LastBackupAt string `json:"last_backup_at"`// RFC3339; empty if never
|
||||
Folder string `json:"folder"` // empty → DefaultFolder
|
||||
Rotation int `json:"rotation"` // how many backups to keep; 0/neg = 5
|
||||
Zip bool `json:"zip"` // compress with deflate
|
||||
LastBackupAt string `json:"last_backup_at"` // RFC3339; empty if never
|
||||
}
|
||||
|
||||
// DefaultFolder returns the folder used when Settings.Folder is empty.
|
||||
@@ -44,7 +44,7 @@ func DefaultFolder(dataDir string) string {
|
||||
// statement (no torn-copy window while the app keeps writing), and compacts
|
||||
// the destination as a bonus. It replaces the old "checkpoint + raw io.Copy",
|
||||
// which could capture a half-written page during a concurrent write.
|
||||
func Run(ctx context.Context, dbConn *sql.DB, dbPath, folder string, rotation int, doZip bool, prefix string) (string, error) {
|
||||
func Run(ctx context.Context, dbConn *sql.DB, dbPath, folder string, rotation int, doZip bool, prefix string, unique bool) (string, error) {
|
||||
if dbConn == nil {
|
||||
return "", fmt.Errorf("nil db connection")
|
||||
}
|
||||
@@ -61,7 +61,12 @@ func Run(ctx context.Context, dbConn *sql.DB, dbPath, folder string, rotation in
|
||||
return "", fmt.Errorf("create backup folder: %w", err)
|
||||
}
|
||||
|
||||
// One file per DAY by default (same-day runs overwrite). With `unique`, add the
|
||||
// time so every run is a distinct file — rotation still keeps the newest N.
|
||||
stamp := time.Now().Format("2006-01-02")
|
||||
if unique {
|
||||
stamp = time.Now().Format("2006-01-02-150405")
|
||||
}
|
||||
base := fmt.Sprintf("%s-%s", prefix, stamp)
|
||||
|
||||
// VACUUM INTO requires a non-existent target → use a temp file, then
|
||||
@@ -106,7 +111,7 @@ func Run(ctx context.Context, dbConn *sql.DB, dbPath, folder string, rotation in
|
||||
// log lives on a shared MySQL server, where VACUUM INTO can't snapshot it — an
|
||||
// ADIF export is a portable, re-importable backup of the actual contacts.
|
||||
// writeADIF must write the full ADIF to the path it's handed.
|
||||
func RunADIF(folder string, rotation int, doZip bool, writeADIF func(path string) error) (string, error) {
|
||||
func RunADIF(folder string, rotation int, doZip bool, unique bool, writeADIF func(path string) error) (string, error) {
|
||||
if rotation <= 0 {
|
||||
rotation = 5
|
||||
}
|
||||
@@ -116,7 +121,11 @@ func RunADIF(folder string, rotation int, doZip bool, writeADIF func(path string
|
||||
if err := os.MkdirAll(folder, 0o755); err != nil {
|
||||
return "", fmt.Errorf("create backup folder: %w", err)
|
||||
}
|
||||
base := "opslog-log-" + time.Now().Format("2006-01-02")
|
||||
stamp := time.Now().Format("2006-01-02")
|
||||
if unique {
|
||||
stamp = time.Now().Format("2006-01-02-150405")
|
||||
}
|
||||
base := "opslog-log-" + stamp
|
||||
tmp := filepath.Join(folder, base+".adi.tmp")
|
||||
_ = os.Remove(tmp)
|
||||
if err := writeADIF(tmp); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user