restictray/main.go

143 lines
3.8 KiB
Go
Raw Normal View History

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"
)
// I'm ignoring go threading issues here; assume no clicks happen at the same time.
var wrapper *restic.Wrapper
func addMenuQuit() {
systray.AddSeparator()
addMenuWithQuitAction("Quit", "Quit Restictray")
}
func addMenuError(err error) {
addMenuWithQuitAction("❗ "+err.Error(), "Restictray Error")
}
func handleError(err error) {
log.Err(err).Msg("")
systray.ResetMenu()
addMenuError(err)
addMenuQuit()
}
func addMenuWithQuitAction(title string, tooltip string) {
mQuit := systray.AddMenuItem(title, tooltip)
go func() {
<-mQuit.ClickedCh
log.Info().Msg("Requesting quit")
systray.Quit()
}()
}
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")
cnf, err := restic.ReadConfig()
if err != nil {
handleError(err)
return
}
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:
go onClickedMenuBackupNow(mnuBackupNow, cnf, func() {
updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow)
})
case <-mnuBrowse.ClickedCh:
// Restic allows backing up and consulting snapshots while mounted
go onClickedMenuBrowse(mnuBrowse, cnf)
}
}
}
func onClickedMenuBrowse(browse *systray.MenuItem, cnf *restic.Config) {
err := wrapper.MountBackups(cnf)
if err != nil {
handleError(err)
}
}
func onClickedMenuBackupNow(mnu *systray.MenuItem, cnf *restic.Config, onDone func()) {
log.Debug().Msg("Backup triggered")
mnu.SetTitle("🔄 Backup in progress...")
mnu.Disable()
err := wrapper.Backup(cnf)
if err != nil {
handleError(err)
}
onDone()
}