diff --git a/main.go b/main.go index 5fcfe3c..3a27e67 100644 --- a/main.go +++ b/main.go @@ -2,42 +2,18 @@ package main import ( "brainbaking.com/restictray/restic" + "fyne.io/systray" + "fyne.io/systray/example/icon" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "os" + "strconv" "time" - - "fyne.io/systray" - "fyne.io/systray/example/icon" ) // I'm ignoring go threading issues here; assume no clicks happen at the same time. var wrapper *restic.Wrapper -func main() { - // init and setup logging - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - wrapper = &restic.Wrapper{} - - // bootstrap systray (this is a blocking call, second func is onExit) - systray.Run(onSystrayReady, func() {}) -} - -func addMenuLatestSnapshot() { - snapshot := wrapper.LastSnapshot() - systray.AddMenuItem("Latest: "+snapshot.Id+" @ "+snapshot.ShortTime(), "Latest Restic snapshot") -} - -func addMenuNextTime(cnf *restic.Config) { - nextTime := wrapper.LastSnapshot().Time.Add(time.Duration(cnf.BackupTimeInHours) * time.Hour) - msg := "Next @ " + nextTime.Format(restic.ShortTimeFormat) - if time.Now().After(nextTime) { - msg = "⚠️ Overdue - " + msg - } - - systray.AddMenuItem(msg, "Future Restic snapshot") -} - func addMenuQuit() { systray.AddSeparator() addMenuWithQuitAction("Quit", "Quit Restictray") @@ -63,11 +39,43 @@ func addMenuWithQuitAction(title string, tooltip string) { }() } +func main() { + // init and setup logging + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + wrapper = &restic.Wrapper{} + + // bootstrap systray (this is a blocking call, second func is onExit) + systray.Run(onSystrayReady, func() {}) +} + +func updateSnapshots(cnf *restic.Config, mnuLatest *systray.MenuItem, mnuNext *systray.MenuItem, mnuBackupNow *systray.MenuItem) { + err := wrapper.UpdateLatestSnapshots(cnf) + if err != nil { + handleError(err) + return + } + + snapshot := wrapper.LastSnapshot() + msg := strconv.Itoa(len(wrapper.LatestSnapshots)) + " snapshots. Next in " + strconv.Itoa(cnf.BackupTimeInHours) + " hour(s)" + if isBackupNeeded(cnf) { + msg = "⚠️ Overdue - " + msg + } + + mnuLatest.SetTitle("Latest: " + snapshot.Id + " @ " + snapshot.ShortTime()) + mnuNext.SetTitle(msg) + mnuBackupNow.Enable() + mnuBackupNow.SetTitle("Backup now") +} + +func isBackupNeeded(cnf *restic.Config) bool { + nextTime := wrapper.LastSnapshot().Time.Add(time.Duration(cnf.BackupTimeInHours) * time.Hour) + return time.Now().After(nextTime) +} + +// See https://github.com/fyne-io/systray/tree/master/example for more examples func onSystrayReady() { systray.SetTemplateIcon(icon.Data, icon.Data) systray.SetTooltip("Restictray") - systray.AddMenuItem("... Initializing", "Initializing, please wait.") - addMenuQuit() cnf, err := restic.ReadConfig() if err != nil { @@ -75,33 +83,41 @@ func onSystrayReady() { return } - go func() { - err := wrapper.UpdateLatestSnapshots(cnf) - if err != nil { - handleError(err) - return - } - - resetAndBuildMainMenu(cnf) - }() -} - -// See https://github.com/fyne-io/systray/tree/master/example for more examples -func resetAndBuildMainMenu(cnf *restic.Config) { - systray.ResetMenu() - addMenuLatestSnapshot() - addMenuNextTime(cnf) + mnuLatestSnapshot := systray.AddMenuItem("Latest: (Fetching...)", "Latest Restic snapshot") + mnuNextSnapshot := systray.AddMenuItem("Next @ (Unknown)", "Future Restic snapshot") systray.AddSeparator() mnuBackupNow := systray.AddMenuItem("Backup now", "Backup now") + mnuBackupNow.Disable() mnuBrowse := systray.AddMenuItem("Browse backups in Finder...", "Mount and browse backups") addMenuQuit() + go updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow) + backupCheckTime := make(chan bool, 1) + backupCheckFn := func() { + time.Sleep(3 * time.Second) + backupCheckTime <- true + } + go backupCheckFn() + for { select { + case <-backupCheckTime: + if !mnuBackupNow.Disabled() && isBackupNeeded(cnf) { + go onClickedMenuBackupNow(mnuBackupNow, cnf, func() { + updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow) + go backupCheckFn() + }) + } else { + log.Debug().Msg("Backup not yet needed/in progress/impossible") + go backupCheckFn() + } case <-mnuBackupNow.ClickedCh: - onClickedMenuBackupNow(mnuBackupNow, cnf) + go onClickedMenuBackupNow(mnuBackupNow, cnf, func() { + updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow) + }) case <-mnuBrowse.ClickedCh: - onClickedMenuBrowse(mnuBrowse, cnf) + // Restic allows backing up and consulting snapshots while mounted + go onClickedMenuBrowse(mnuBrowse, cnf) } } } @@ -113,18 +129,14 @@ func onClickedMenuBrowse(browse *systray.MenuItem, cnf *restic.Config) { } } -func onClickedMenuBackupNow(mnu *systray.MenuItem, cnf *restic.Config) { +func onClickedMenuBackupNow(mnu *systray.MenuItem, cnf *restic.Config, onDone func()) { + log.Debug().Msg("Backup triggered") mnu.SetTitle("🔄 Backup in progress...") mnu.Disable() - // TODO after a backup, reinitialize latest snapshot + latest/next menus - // TODO not by calling resetAndBuild again: this is from the for{}? - // TODO how does this interop with a future goroutine that auto-backups? - // TODO need for separate "backupInProgress" bool? err := wrapper.Backup(cnf) - mnu.SetTitle("Backup now") - mnu.Enable() if err != nil { handleError(err) } + onDone() }