v0.277.0: kept data loads from the off-site copy too (R-691 (2))
gates / gates (push) Successful in 25s
gates / gates (push) Successful in 25s
Use my kept data / Load consider the off-site snapshot when it is newer than every local copy or the only one; the unit is downloaded alone, judged (drive, data, recorded data version) and only then restored. The page names the copy and its date. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/infra"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
@@ -63,32 +65,44 @@ func KeptViewName(it stacks.KeptItem) string {
|
||||
return base + filepath.Base(it.Path)
|
||||
}
|
||||
|
||||
// keptBackupFor names the copy a Load would use for it, and whether one exists.
|
||||
func (s *Server) keptBackupFor(lang string, it stacks.KeptItem) (string, string, bool) {
|
||||
if s.backupMgr == nil {
|
||||
return s.msgLang(lang, "kept.backup.none"), "", false
|
||||
// keptBackupFor names the copy a Load would use for it, and whether one exists. off is the off-site copy
|
||||
// per app (backup.KeptOffsiteCopies, asked once per page): it is used when it is newer than every local
|
||||
// copy, or the only one (R-691 (2)).
|
||||
func (s *Server) keptBackupFor(lang string, it stacks.KeptItem, off map[string]backup.KeptCopy) (string, backup.KeptCopy, bool) {
|
||||
none := s.msgLang(lang, "kept.backup.none")
|
||||
if s.backupMgr == nil || it.App == "" && it.Kind != stacks.KeptKindDated {
|
||||
return none, backup.KeptCopy{}, false
|
||||
}
|
||||
var local backup.KeptCopy
|
||||
var lok bool
|
||||
if it.Kind == stacks.KeptKindDated {
|
||||
if it.UnitDir == "" {
|
||||
return s.msgLang(lang, "kept.backup.none"), "", false
|
||||
// A dated folder's own unit is the copy taken with those files.
|
||||
if it.UnitDir != "" {
|
||||
if c, ok := s.backupMgr.KeptCopyAt(it.UnitDir, it.Drive, 1); ok {
|
||||
return s.msgLang(lang, "kept.backup.with", c.Time.In(getTimezone()).Format("2006-01-02 15:04")), c, true
|
||||
}
|
||||
}
|
||||
if c, ok := s.backupMgr.KeptCopyAt(it.UnitDir, it.Drive, 1); ok {
|
||||
return s.msgLang(lang, "kept.backup.with", c.Time.In(getTimezone()).Format("2006-01-02 15:04")), c.UnitDir, true
|
||||
}
|
||||
return s.msgLang(lang, "kept.backup.none"), "", false
|
||||
} else {
|
||||
local, lok = s.backupMgr.KeptDBCopy(it.App, it.Drive)
|
||||
}
|
||||
if it.App == "" {
|
||||
return s.msgLang(lang, "kept.backup.none"), "", false
|
||||
}
|
||||
c, ok := s.backupMgr.KeptDBCopy(it.App, it.Drive)
|
||||
oc, ook := off[it.App]
|
||||
c, ok := backup.KeptNewer(local, lok, oc, ook && it.App != "")
|
||||
if !ok {
|
||||
return s.msgLang(lang, "kept.backup.none"), "", false
|
||||
return none, backup.KeptCopy{}, false
|
||||
}
|
||||
key := "kept.backup.own"
|
||||
if c.Tier == 2 {
|
||||
key = "kept.backup.second"
|
||||
return s.msgLang(lang, backup.KeptCopyKey(c.Tier), c.Time.In(getTimezone()).Format("2006-01-02 15:04")), c, true
|
||||
}
|
||||
|
||||
// keptOffsite asks the off-site repository ONCE for every listed app.
|
||||
func (s *Server) keptOffsite(ctx context.Context, items []stacks.KeptItem) map[string]backup.KeptCopy {
|
||||
if s.backupMgr == nil {
|
||||
return nil
|
||||
}
|
||||
return s.msgLang(lang, key, c.Time.In(getTimezone()).Format("2006-01-02 15:04")), c.UnitDir, true
|
||||
var apps []string
|
||||
for _, it := range items {
|
||||
apps = append(apps, it.App)
|
||||
}
|
||||
return s.backupMgr.KeptOffsiteCopies(ctx, apps)
|
||||
}
|
||||
|
||||
func (s *Server) keptPageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -97,12 +111,14 @@ func (s *Server) keptPageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data["TitleKey"] = "page.title.kept_data"
|
||||
var rows []keptRow
|
||||
if s.stackMgr != nil {
|
||||
for _, it := range s.stackMgr.ListKept(s.keptDrives()) {
|
||||
backup, _, can := s.keptBackupFor(lang, it)
|
||||
items := s.stackMgr.ListKept(s.keptDrives())
|
||||
off := s.keptOffsite(r.Context(), items)
|
||||
for _, it := range items {
|
||||
backupName, _, can := s.keptBackupFor(lang, it, off)
|
||||
row := keptRow{
|
||||
DisplayName: it.DisplayName, App: it.App, Path: it.Path, Drive: it.Drive,
|
||||
Date: it.Date.In(getTimezone()).Format("2006-01-02 15:04"),
|
||||
Size: appbackup.HumanizeBytes(it.SizeBytes), Backup: backup, CanLoad: can && it.App != "",
|
||||
Size: appbackup.HumanizeBytes(it.SizeBytes), Backup: backupName, CanLoad: can && it.App != "",
|
||||
LookURL: fileBrowserLink(s.cfg.Customer.Domain, s.msgLang(s.boxLang(), "kept.fb_source"), KeptViewName(it)),
|
||||
}
|
||||
row.DeleteText = s.msgLang(lang, "kept.delete.confirm", it.DisplayName, row.Size)
|
||||
@@ -165,31 +181,43 @@ func (s *Server) keptLoadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
s.keptRedirect(w, r, "flash_error", msg)
|
||||
return
|
||||
}
|
||||
_, unit, can := s.keptBackupFor(lang, it)
|
||||
_, c, can := s.keptBackupFor(lang, it, s.keptOffsite(r.Context(), []stacks.KeptItem{it}))
|
||||
if !can {
|
||||
s.keptRedirect(w, r, "flash_error", s.msgLang(lang, "kept.choice.use_off"))
|
||||
return
|
||||
}
|
||||
if it.Kind == stacks.KeptKindDated {
|
||||
if _, err := s.stackMgr.RestoreKeptFiles(it); err != nil {
|
||||
app, disp := it.App, it.DisplayName
|
||||
okMsg := func(error) string { return s.msgLang(lang, "kept.load.done", disp) }
|
||||
failMsg := func(err error) string { return s.msgLang(lang, "kept.load.failed", disp, err) }
|
||||
after := func(ok bool) {
|
||||
if ok {
|
||||
if ran, err := s.stackMgr.RunAfterLoad(app, 10*time.Minute); ran && err != nil {
|
||||
s.logger.Printf("[WARN] [web] kept load %s: after_load failed: %v", app, err)
|
||||
}
|
||||
s.stackMgr.FinishKeptLoad(it, s.backupMgr.PrimaryUnitHome(app, it.Drive))
|
||||
}
|
||||
s.SyncFileBrowserMounts()
|
||||
}
|
||||
// A dated folder's files move back BEFORE the load — for the off-site copy only after its unit is
|
||||
// downloaded and judged, so a failed download or a refusal leaves the kept folder as it was.
|
||||
moveBack := func() error {
|
||||
if it.Kind != stacks.KeptKindDated {
|
||||
return nil
|
||||
}
|
||||
_, err := s.stackMgr.RestoreKeptFiles(it)
|
||||
return err
|
||||
}
|
||||
if c.Tier == backup.KeptTierOffsite {
|
||||
s.logger.Printf("[INFO] [web] kept LOAD %s: %s from the off-site snapshot %s (from %s)", it.App, it.Path, c.SnapshotID, r.RemoteAddr)
|
||||
s.backupMgr.LoadKeptOffsite(app, it.Drive, c, moveBack, okMsg, failMsg, after)
|
||||
} else {
|
||||
if err := moveBack(); err != nil {
|
||||
s.keptRedirect(w, r, "flash_error", s.errText(r, err))
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] kept LOAD %s: %s from %s (from %s)", it.App, it.Path, c.UnitDir, r.RemoteAddr)
|
||||
s.backupMgr.LoadKeptApp(app, c.UnitDir, okMsg, failMsg, after)
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] kept LOAD %s: %s from %s (from %s)", it.App, it.Path, unit, r.RemoteAddr)
|
||||
app, disp := it.App, it.DisplayName
|
||||
s.backupMgr.LoadKeptApp(app, unit,
|
||||
func(error) string { return s.msgLang(lang, "kept.load.done", disp) },
|
||||
func(err error) string { return s.msgLang(lang, "kept.load.failed", disp, err) },
|
||||
func(ok bool) {
|
||||
if ok {
|
||||
if ran, err := s.stackMgr.RunAfterLoad(app, 10*time.Minute); ran && err != nil {
|
||||
s.logger.Printf("[WARN] [web] kept load %s: after_load failed: %v", app, err)
|
||||
}
|
||||
s.stackMgr.FinishKeptLoad(it, s.backupMgr.PrimaryUnitHome(app, it.Drive))
|
||||
}
|
||||
s.SyncFileBrowserMounts()
|
||||
})
|
||||
http.Redirect(w, r, "/backups/restore?"+flashQuery("flash", "flash.restore.started"), http.StatusFound)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user