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:
@@ -296,6 +296,7 @@ const (
|
||||
keyBackupRotation = "backup.rotation"
|
||||
keyBackupZip = "backup.zip"
|
||||
keyBackupEveryExit = "backup.every_exit" // "1" → back up on every quit, not just once/day
|
||||
keyBackupKeepAll = "backup.keep_all" // "1" → each backup is a distinct time-stamped file (no same-day overwrite)
|
||||
keyBackupLast = "backup.last_at"
|
||||
|
||||
keyQSLDefaultQSLSent = "qsl.qsl_sent"
|
||||
@@ -1535,7 +1536,7 @@ func (a *App) runBackupForShutdown() error {
|
||||
if done && !s.EveryExit { // "every exit" backs up regardless of a prior backup today
|
||||
return nil
|
||||
}
|
||||
if _, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip); err != nil {
|
||||
if _, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip, s.KeepAll); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.settings.Set(a.ctx, keyBackupLast, time.Now().UTC().Format(time.RFC3339))
|
||||
@@ -11784,6 +11785,7 @@ type BackupSettings struct {
|
||||
Rotation int `json:"rotation"`
|
||||
Zip bool `json:"zip"`
|
||||
EveryExit bool `json:"every_exit"` // back up on every quit, not just the first of the day
|
||||
KeepAll bool `json:"keep_all"` // time-stamped file per backup instead of one-per-day (kept up to Rotation)
|
||||
LastBackupAt string `json:"last_backup_at"`
|
||||
DefaultFolder string `json:"default_folder"` // computed, read-only — shown as a hint
|
||||
}
|
||||
@@ -11798,7 +11800,7 @@ func (a *App) GetBackupSettings() (BackupSettings, error) {
|
||||
return out, nil
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx,
|
||||
keyBackupEnabled, keyBackupFolder, keyBackupRotation, keyBackupZip, keyBackupEveryExit, keyBackupLast)
|
||||
keyBackupEnabled, keyBackupFolder, keyBackupRotation, keyBackupZip, keyBackupEveryExit, keyBackupKeepAll, keyBackupLast)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -11809,6 +11811,7 @@ func (a *App) GetBackupSettings() (BackupSettings, error) {
|
||||
}
|
||||
out.Zip = m[keyBackupZip] == "1"
|
||||
out.EveryExit = m[keyBackupEveryExit] == "1"
|
||||
out.KeepAll = m[keyBackupKeepAll] == "1"
|
||||
out.LastBackupAt = m[keyBackupLast]
|
||||
return out, nil
|
||||
}
|
||||
@@ -11836,6 +11839,7 @@ func (a *App) SaveBackupSettings(s BackupSettings) error {
|
||||
keyBackupRotation: strconv.Itoa(s.Rotation),
|
||||
keyBackupZip: doZip,
|
||||
keyBackupEveryExit: boolStr(s.EveryExit),
|
||||
keyBackupKeepAll: boolStr(s.KeepAll),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -11852,15 +11856,15 @@ func (a *App) SaveBackupSettings(s BackupSettings) error {
|
||||
// backing up the QSOs once the logbook was split out. On MySQL the contacts
|
||||
// aren't in a local file, so they're exported to ADIF instead. Returns the path
|
||||
// of the contacts backup.
|
||||
func (a *App) runConfiguredBackup(folder string, rotation int, zip bool) (string, error) {
|
||||
func (a *App) runConfiguredBackup(folder string, rotation int, zip, unique bool) (string, error) {
|
||||
// Config snapshot (profiles, hardware, awards). Best-effort — a config-backup
|
||||
// failure must never stop the contacts from being protected.
|
||||
if _, err := backup.Run(a.ctx, a.db, a.dbPath, folder, rotation, zip, "opslogcfg"); err != nil {
|
||||
if _, err := backup.Run(a.ctx, a.db, a.dbPath, folder, rotation, zip, "opslogcfg", unique); err != nil {
|
||||
applog.Printf("backup: config snapshot failed: %v", err)
|
||||
}
|
||||
if a.dbBackend == "mysql" {
|
||||
// The live log is on MySQL; VACUUM INTO can't reach it — export to ADIF.
|
||||
return a.backupLogADIF(folder, rotation, zip)
|
||||
return a.backupLogADIF(folder, rotation, zip, unique)
|
||||
}
|
||||
// SQLite: snapshot the logbook file that holds the contacts. Fall back to the
|
||||
// settings db only when it doubles as the logbook (rare split-failure case).
|
||||
@@ -11868,7 +11872,7 @@ func (a *App) runConfiguredBackup(folder string, rotation int, zip bool) (string
|
||||
if conn == nil || path == "" {
|
||||
conn, path = a.db, a.dbPath
|
||||
}
|
||||
return backup.Run(a.ctx, conn, path, folder, rotation, zip, "opslog")
|
||||
return backup.Run(a.ctx, conn, path, folder, rotation, zip, "opslog", unique)
|
||||
}
|
||||
|
||||
// RunBackupNow forces an immediate backup using the persisted settings.
|
||||
@@ -11882,7 +11886,7 @@ func (a *App) RunBackupNow() (string, error) {
|
||||
if folder == "" {
|
||||
folder = s.DefaultFolder
|
||||
}
|
||||
path, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip)
|
||||
path, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip, s.KeepAll)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
@@ -11893,11 +11897,11 @@ func (a *App) RunBackupNow() (string, error) {
|
||||
// backupLogADIF writes a rotating ADIF export of the (MySQL) logbook into the
|
||||
// backup folder. The full set of ADIF + app fields is included so the backup is
|
||||
// a complete, re-importable copy of the log.
|
||||
func (a *App) backupLogADIF(folder string, rotation int, zip bool) (string, error) {
|
||||
func (a *App) backupLogADIF(folder string, rotation int, zip, unique bool) (string, error) {
|
||||
if a.qso == nil {
|
||||
return "", fmt.Errorf("logbook not initialized")
|
||||
}
|
||||
return backup.RunADIF(folder, rotation, zip, func(p string) error {
|
||||
return backup.RunADIF(folder, rotation, zip, unique, func(p string) error {
|
||||
ex := &adif.Exporter{Repo: a.qso, AppName: "OpsLog", AppVersion: "0.1", IncludeAppFields: true}
|
||||
_, e := ex.ExportFile(a.ctx, p)
|
||||
return e
|
||||
@@ -11931,7 +11935,7 @@ func (a *App) maybeShutdownBackup() {
|
||||
if done && !s.EveryExit { // "every exit" backs up regardless of a prior backup today
|
||||
return
|
||||
}
|
||||
if _, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip); err != nil {
|
||||
if _, err := a.runConfiguredBackup(folder, s.Rotation, s.Zip, s.KeepAll); err != nil {
|
||||
fmt.Println("OpsLog: shutdown backup failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user