mirror of
https://github.com/Mikescher/kpsync.git
synced 2025-08-25 08:38:03 +02:00
logging, upload, etc
This commit is contained in:
parent
0f9b423d2f
commit
47080e14db
4
Makefile
4
Makefile
@ -3,10 +3,10 @@
|
|||||||
#########################
|
#########################
|
||||||
|
|
||||||
build: enums
|
build: enums
|
||||||
CGO_ENABLED=0 go build -o _out/kpsync ./cmd/kpsync
|
CGO_ENABLED=0 go build -o _out/kpsync ./cmd/cli
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./_out/ffsclient
|
./_out/kpsync
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean
|
go clean
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"fyne.io/systray"
|
"fyne.io/systray"
|
||||||
"git.blackforestbytes.com/BlackForestBytes/goext/syncext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/syncext"
|
||||||
"mikescher.com/kpsync"
|
"git.blackforestbytes.com/BlackForestBytes/goext/termext"
|
||||||
"mikescher.com/kpsync/assets"
|
"mikescher.com/kpsync/assets"
|
||||||
"mikescher.com/kpsync/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
masterLock sync.Mutex
|
masterLock sync.Mutex
|
||||||
|
|
||||||
config kpsync.Config
|
config Config
|
||||||
|
|
||||||
trayReady bool
|
trayReady *syncext.AtomicBool
|
||||||
uploadRunning *syncext.AtomicBool
|
uploadRunning *syncext.AtomicBool
|
||||||
|
syncLoopRunning *syncext.AtomicBool
|
||||||
|
keepassRunning *syncext.AtomicBool
|
||||||
|
|
||||||
sigStopChan chan bool // keepass exited
|
sigStopChan chan bool // keepass exited
|
||||||
sigErrChan chan error // fatal error
|
sigErrChan chan error // fatal error
|
||||||
@ -33,31 +36,62 @@ type Application struct {
|
|||||||
|
|
||||||
func NewApplication() *Application {
|
func NewApplication() *Application {
|
||||||
|
|
||||||
cfg := kpsync.LoadConfig()
|
app := &Application{
|
||||||
|
|
||||||
return &Application{
|
|
||||||
masterLock: sync.Mutex{},
|
masterLock: sync.Mutex{},
|
||||||
config: cfg,
|
|
||||||
uploadRunning: syncext.NewAtomicBool(false),
|
uploadRunning: syncext.NewAtomicBool(false),
|
||||||
trayReady: false,
|
trayReady: syncext.NewAtomicBool(false),
|
||||||
|
syncLoopRunning: syncext.NewAtomicBool(false),
|
||||||
|
keepassRunning: syncext.NewAtomicBool(false),
|
||||||
sigStopChan: make(chan bool, 128),
|
sigStopChan: make(chan bool, 128),
|
||||||
sigErrChan: make(chan error, 128),
|
sigErrChan: make(chan error, 128),
|
||||||
sigSyncLoopStopChan: make(chan bool, 128),
|
sigSyncLoopStopChan: make(chan bool, 128),
|
||||||
sigTermKeepassChan: make(chan bool, 128),
|
sigTermKeepassChan: make(chan bool, 128),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.LogInfo("Starting kpsync...")
|
||||||
|
app.LogDebug(fmt.Sprintf("SupportsColors := %v", termext.SupportsColors()))
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) Run() {
|
func (app *Application) Run() {
|
||||||
|
var configPath string
|
||||||
|
|
||||||
|
app.config, configPath = app.loadConfig()
|
||||||
|
|
||||||
|
app.LogInfo(fmt.Sprintf("Loaded config from %s", configPath))
|
||||||
|
app.LogDebug(fmt.Sprintf("WebDAVURL := '%s'", app.config.WebDAVURL))
|
||||||
|
app.LogDebug(fmt.Sprintf("WebDAVUser := '%s'", app.config.WebDAVUser))
|
||||||
|
app.LogDebug(fmt.Sprintf("WebDAVPass := '%s'", app.config.WebDAVPass))
|
||||||
|
app.LogDebug(fmt.Sprintf("LocalFallback := '%s'", app.config.LocalFallback))
|
||||||
|
app.LogDebug(fmt.Sprintf("WorkDir := '%s'", app.config.WorkDir))
|
||||||
|
app.LogDebug(fmt.Sprintf("ForceColors := %v", app.config.ForceColors))
|
||||||
|
app.LogDebug(fmt.Sprintf("Debounce := %d", app.config.Debounce))
|
||||||
|
app.LogDebug(fmt.Sprintf("ForceColors := %v", app.config.ForceColors))
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
go func() { app.initTray() }()
|
go func() { app.initTray() }()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
app.syncLoopRunning = syncext.NewAtomicBool(true)
|
||||||
|
defer app.syncLoopRunning.Set(false)
|
||||||
|
|
||||||
err := app.initSync()
|
err := app.initSync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.sigErrChan <- err
|
app.sigErrChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
app.keepassRunning = syncext.NewAtomicBool(true)
|
||||||
|
defer app.keepassRunning.Set(false)
|
||||||
|
|
||||||
|
app.runKeepass()
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
app.setTrayStateDirect("Sleeping...", assets.IconDefault)
|
app.setTrayStateDirect("Sleeping...", assets.IconDefault)
|
||||||
|
|
||||||
err = app.runSyncLoop()
|
err = app.runSyncLoop()
|
||||||
@ -71,34 +105,58 @@ func (app *Application) Run() {
|
|||||||
signal.Notify(sigTerm, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigTerm, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-sigTerm:
|
case <-sigTerm: // kpsync received SIGTERM
|
||||||
|
|
||||||
app.sigSyncLoopStopChan <- true
|
app.LogInfo("Stopping application (received SIGTERM signal)")
|
||||||
app.sigTermKeepassChan <- true
|
|
||||||
log.LogInfo("Stopping application (received SIGTERM signal)")
|
|
||||||
|
|
||||||
// TODO term
|
app.stopBackgroundRoutines()
|
||||||
|
|
||||||
case err := <-app.sigErrChan:
|
// TODO try final sync
|
||||||
|
|
||||||
app.sigSyncLoopStopChan <- true
|
case err := <-app.sigErrChan: // fatal error
|
||||||
app.sigTermKeepassChan <- true
|
|
||||||
log.LogInfo("Stopping application (received ERROR)")
|
|
||||||
log.LogError(err.Error(), err)
|
|
||||||
|
|
||||||
// TODO stop
|
app.LogInfo("Stopping application (received ERROR)")
|
||||||
|
|
||||||
case _ = <-app.sigStopChan:
|
app.stopBackgroundRoutines()
|
||||||
|
|
||||||
app.sigSyncLoopStopChan <- true
|
app.LogError("Stopped due to error: "+err.Error(), nil)
|
||||||
app.sigTermKeepassChan <- true
|
|
||||||
log.LogInfo("Stopping application (received STOP)")
|
// TODO stop?
|
||||||
|
|
||||||
|
case _ = <-app.sigStopChan: // keepass exited
|
||||||
|
|
||||||
|
app.LogInfo("Stopping application (received STOP)")
|
||||||
|
|
||||||
|
app.stopBackgroundRoutines()
|
||||||
|
|
||||||
|
// TODO try final sync
|
||||||
|
|
||||||
// TODO stop
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if app.trayReady {
|
|
||||||
systray.Quit()
|
func (app *Application) stopBackgroundRoutines() {
|
||||||
}
|
app.LogInfo("Stopping go-routines...")
|
||||||
|
|
||||||
|
app.LogDebug("Stopping systray...")
|
||||||
|
systray.Quit()
|
||||||
|
app.trayReady.Wait(false)
|
||||||
|
app.LogDebug("Stopped systray.")
|
||||||
|
|
||||||
|
if app.uploadRunning.Get() {
|
||||||
|
app.LogInfo("Waiting for active upload...")
|
||||||
|
app.uploadRunning.Wait(false)
|
||||||
|
app.LogInfo("Upload finished.")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.LogDebug("Stopping sync-loop...")
|
||||||
|
app.sigSyncLoopStopChan <- true
|
||||||
|
app.syncLoopRunning.Wait(false)
|
||||||
|
app.LogDebug("Stopped sync-loop.")
|
||||||
|
|
||||||
|
app.LogDebug("Stopping keepass...")
|
||||||
|
app.sigTermKeepassChan <- true
|
||||||
|
app.keepassRunning.Wait(false)
|
||||||
|
app.LogDebug("Stopped keepass.")
|
||||||
|
|
||||||
|
app.LogLine()
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package kpsync
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||||
"mikescher.com/kpsync/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -21,10 +20,12 @@ type Config struct {
|
|||||||
|
|
||||||
WorkDir string `json:"work_dir"`
|
WorkDir string `json:"work_dir"`
|
||||||
|
|
||||||
|
ForceColors bool `json:"force_colors"`
|
||||||
|
|
||||||
Debounce int `json:"debounce"`
|
Debounce int `json:"debounce"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() Config {
|
func (app *Application) loadConfig() (Config, string) {
|
||||||
var configPath string
|
var configPath string
|
||||||
flag.StringVar(&configPath, "config", "~/.config/kpsync.json", "Path to the configuration file")
|
flag.StringVar(&configPath, "config", "~/.config/kpsync.json", "Path to the configuration file")
|
||||||
|
|
||||||
@ -46,12 +47,15 @@ func LoadConfig() Config {
|
|||||||
var debounce int
|
var debounce int
|
||||||
flag.IntVar(&debounce, "debounce", 0, "Debounce before sync (in seconds)")
|
flag.IntVar(&debounce, "debounce", 0, "Debounce before sync (in seconds)")
|
||||||
|
|
||||||
|
var forceColors bool
|
||||||
|
flag.BoolVar(&forceColors, "color", false, "Force color-output (default: auto-detect)")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if strings.HasPrefix(configPath, "~") {
|
if strings.HasPrefix(configPath, "~") {
|
||||||
usr, err := user.Current()
|
usr, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FatalErr("Failed to query users home directory", err)
|
app.LogFatalErr("Failed to query users home directory", err)
|
||||||
}
|
}
|
||||||
fmt.Println(usr.HomeDir)
|
fmt.Println(usr.HomeDir)
|
||||||
|
|
||||||
@ -67,18 +71,19 @@ func LoadConfig() Config {
|
|||||||
LocalFallback: "",
|
LocalFallback: "",
|
||||||
WorkDir: "/tmp/kpsync",
|
WorkDir: "/tmp/kpsync",
|
||||||
Debounce: 3500,
|
Debounce: 3500,
|
||||||
|
ForceColors: false,
|
||||||
}, "", " ")), 0644)
|
}, "", " ")), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgBin, err := os.ReadFile(configPath)
|
cfgBin, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FatalErr("Failed to read config file from "+configPath, err)
|
app.LogFatalErr("Failed to read config file from "+configPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg Config
|
var cfg Config
|
||||||
err = json.Unmarshal(cfgBin, &cfg)
|
err = json.Unmarshal(cfgBin, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FatalErr("Failed to parse config file from "+configPath, err)
|
app.LogFatalErr("Failed to parse config file from "+configPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if webdavURL != "" {
|
if webdavURL != "" {
|
||||||
@ -99,6 +104,9 @@ func LoadConfig() Config {
|
|||||||
if debounce > 0 {
|
if debounce > 0 {
|
||||||
cfg.Debounce = debounce
|
cfg.Debounce = debounce
|
||||||
}
|
}
|
||||||
|
if forceColors {
|
||||||
|
cfg.ForceColors = forceColors
|
||||||
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg, configPath
|
||||||
}
|
}
|
66
app/logger.go
Normal file
66
app/logger.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||||
|
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||||
|
"git.blackforestbytes.com/BlackForestBytes/goext/termext"
|
||||||
|
)
|
||||||
|
|
||||||
|
func colDefault(v string) string {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogFatal(msg string) {
|
||||||
|
app.logInternal("[F] ", msg, termext.Red)
|
||||||
|
panic(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogFatalErr(msg string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
app.logInternal("[F] ", msg+"\n"+err.Error()+"\n"+exerr.FromError(err).FormatLog(exerr.LogPrintOverview), termext.Red)
|
||||||
|
panic(0)
|
||||||
|
} else {
|
||||||
|
app.logInternal("[F] ", msg, termext.Red)
|
||||||
|
panic(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogError(msg string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
app.logInternal("[E] ", msg+"\n"+err.Error()+"\n"+exerr.FromError(err).FormatLog(exerr.LogPrintOverview), termext.Red)
|
||||||
|
} else {
|
||||||
|
app.logInternal("[E] ", msg, termext.Red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogWarn(msg string) {
|
||||||
|
app.logInternal("[W] ", msg, termext.Red)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogInfo(msg string) {
|
||||||
|
app.logInternal("[I] ", msg, colDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogDebug(msg string) {
|
||||||
|
app.logInternal("[D] ", msg, termext.Gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) logInternal(pf string, msg string, c func(_ string) string) {
|
||||||
|
if !termext.SupportsColors() && !app.config.ForceColors {
|
||||||
|
c = func(s string) string { return s }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range strings.Split(msg, "\n") {
|
||||||
|
if i == 0 {
|
||||||
|
println(c(pf + s))
|
||||||
|
} else {
|
||||||
|
println(c(langext.StrRepeat(" ", len(pf)) + s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) LogLine() {
|
||||||
|
println()
|
||||||
|
}
|
9
app/notifications.go
Normal file
9
app/notifications.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
func (app *Application) showErrorNotification(msg string) {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) showSuccessNotification(msg string) {
|
||||||
|
//TODO
|
||||||
|
}
|
237
app/sync.go
237
app/sync.go
@ -14,7 +14,6 @@ import (
|
|||||||
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"mikescher.com/kpsync/assets"
|
"mikescher.com/kpsync/assets"
|
||||||
"mikescher.com/kpsync/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *Application) initSync() error {
|
func (app *Application) initSync() error {
|
||||||
@ -27,8 +26,8 @@ func (app *Application) initSync() error {
|
|||||||
app.dbFile = path.Join(app.config.WorkDir, path.Base(app.config.LocalFallback))
|
app.dbFile = path.Join(app.config.WorkDir, path.Base(app.config.LocalFallback))
|
||||||
app.stateFile = path.Join(app.config.WorkDir, "kpsync.state")
|
app.stateFile = path.Join(app.config.WorkDir, "kpsync.state")
|
||||||
|
|
||||||
if isKeepassRunning() {
|
if app.isKeepassRunning() {
|
||||||
log.LogError("keepassxc is already running!", nil)
|
app.LogError("keepassxc is already running!", nil)
|
||||||
return exerr.New(exerr.TypeInternal, "keepassxc is already running").Build()
|
return exerr.New(exerr.TypeInternal, "keepassxc is already running").Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,20 +38,21 @@ func (app *Application) initSync() error {
|
|||||||
if state != nil && fileExists(app.dbFile) {
|
if state != nil && fileExists(app.dbFile) {
|
||||||
localCS, err := app.calcLocalChecksum()
|
localCS, err := app.calcLocalChecksum()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.LogError("Failed to calculate local database checksum", err)
|
app.LogError("Failed to calculate local database checksum", err)
|
||||||
} else if localCS == state.Checksum {
|
} else if localCS == state.Checksum {
|
||||||
remoteETag, remoteLM, err := app.getRemoteETag()
|
remoteETag, remoteLM, err := app.getRemoteETag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.LogError("Failed to get remote ETag", err)
|
app.LogError("Failed to get remote ETag", err)
|
||||||
} else if remoteETag == state.ETag {
|
} else if remoteETag == state.ETag {
|
||||||
|
|
||||||
log.LogInfo(fmt.Sprintf("Found local database matching remote database - skip initial download"))
|
app.LogInfo(fmt.Sprintf("Found local database matching remote database - skip initial download"))
|
||||||
log.LogInfo(fmt.Sprintf("Checksum (cached) := %s", state.Checksum))
|
app.LogDebug(fmt.Sprintf("Checksum (cached) := %s", state.Checksum))
|
||||||
log.LogInfo(fmt.Sprintf("Checksum (local) := %s", localCS))
|
app.LogDebug(fmt.Sprintf("Checksum (local) := %s", localCS))
|
||||||
log.LogInfo(fmt.Sprintf("ETag (cached) := %s", state.ETag))
|
app.LogDebug(fmt.Sprintf("ETag (cached) := %s", state.ETag))
|
||||||
log.LogInfo(fmt.Sprintf("ETag (remote) := %s", remoteETag))
|
app.LogDebug(fmt.Sprintf("ETag (remote) := %s", remoteETag))
|
||||||
log.LogInfo(fmt.Sprintf("LastModified (cached) := %s", state.LastModified.Format(time.RFC3339)))
|
app.LogDebug(fmt.Sprintf("LastModified (cached) := %s", state.LastModified.Format(time.RFC3339)))
|
||||||
log.LogInfo(fmt.Sprintf("LastModified (remote) := %s", remoteLM.Format(time.RFC3339)))
|
app.LogDebug(fmt.Sprintf("LastModified (remote) := %s", remoteLM.Format(time.RFC3339)))
|
||||||
|
app.LogLine()
|
||||||
needsDownload = false
|
needsDownload = false
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -64,92 +64,97 @@ func (app *Application) initSync() error {
|
|||||||
fin := app.setTrayState("Downloading database", assets.IconDownload)
|
fin := app.setTrayState("Downloading database", assets.IconDownload)
|
||||||
defer fin()
|
defer fin()
|
||||||
|
|
||||||
log.LogInfo(fmt.Sprintf("Downloading remote database to %s", app.dbFile))
|
app.LogInfo(fmt.Sprintf("Downloading remote database to %s", app.dbFile))
|
||||||
|
|
||||||
etag, lm, sha, sz, err := app.downloadDatabase()
|
etag, lm, sha, sz, err := app.downloadDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.LogError("Failed to download remote database", err)
|
app.LogError("Failed to download remote database", err)
|
||||||
return exerr.Wrap(err, "Failed to download remote database").Build()
|
return exerr.Wrap(err, "Failed to download remote database").Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.LogInfo(fmt.Sprintf("Downloaded remote database to %s", app.dbFile))
|
app.LogInfo(fmt.Sprintf("Downloaded remote database to %s", app.dbFile))
|
||||||
log.LogInfo(fmt.Sprintf("Checksum := %s", sha))
|
app.LogInfo(fmt.Sprintf("Checksum := %s", sha))
|
||||||
log.LogInfo(fmt.Sprintf("ETag := %s", etag))
|
app.LogInfo(fmt.Sprintf("ETag := %s", etag))
|
||||||
log.LogInfo(fmt.Sprintf("Size := %s", langext.FormatBytes(sz)))
|
app.LogInfo(fmt.Sprintf("Size := %s (%d)", langext.FormatBytes(sz), sz))
|
||||||
log.LogInfo(fmt.Sprintf("LastModified := %s", lm.Format(time.RFC3339)))
|
app.LogInfo(fmt.Sprintf("LastModified := %s", lm.Format(time.RFC3339)))
|
||||||
|
|
||||||
err = app.saveState(etag, lm, sha, sz)
|
err = app.saveState(etag, lm, sha, sz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.LogError("Failed to save state", err)
|
app.LogError("Failed to save state", err)
|
||||||
return exerr.Wrap(err, "Failed to save state").Build()
|
return exerr.Wrap(err, "Failed to save state").Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return exerr.Wrap(err, "").Build()
|
return exerr.Wrap(err, "").Build()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.LogInfo(fmt.Sprintf("Skip download - use existing local database %s", app.dbFile))
|
app.LogInfo(fmt.Sprintf("Skip download - use existing local database %s", app.dbFile))
|
||||||
|
app.LogLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) runKeepass() {
|
||||||
|
app.LogInfo("Starting keepassxc...")
|
||||||
|
|
||||||
|
cmd := exec.Command("keepassxc", app.dbFile)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
select {
|
||||||
log.LogInfo("Starting keepassxc...")
|
case <-app.sigTermKeepassChan:
|
||||||
|
app.LogInfo("Received signal to terminate keepassxc")
|
||||||
cmd := exec.Command("keepassxc", app.dbFile)
|
if cmd != nil && cmd.Process != nil {
|
||||||
|
app.LogInfo(fmt.Sprintf("Terminating keepassxc %d", cmd.Process.Pid))
|
||||||
go func() {
|
err := cmd.Process.Signal(syscall.SIGTERM)
|
||||||
select {
|
if err != nil {
|
||||||
case <-app.sigTermKeepassChan:
|
app.LogError("Failed to terminate keepassxc", err)
|
||||||
log.LogInfo("Received signal to terminate keepassxc")
|
|
||||||
if cmd != nil && cmd.Process != nil {
|
|
||||||
log.LogInfo(fmt.Sprintf("Terminating keepassxc %d", cmd.Process.Pid))
|
|
||||||
err := cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
log.LogError("Failed to terminate keepassxc", err)
|
|
||||||
} else {
|
|
||||||
log.LogInfo("keepassxc terminated successfully")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.LogInfo("No keepassxc process to terminate")
|
app.LogInfo("keepassxc terminated successfully")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
app.LogInfo("No keepassxc process to terminate")
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.LogError("Failed to start keepassxc", err)
|
|
||||||
app.sigErrChan <- exerr.Wrap(err, "Failed to start keepassxc").Build()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
log.LogInfo(fmt.Sprintf("keepassxc started with PID %d", cmd.Process.Pid))
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
app.LogError("Failed to start keepassxc", err)
|
||||||
|
app.sigErrChan <- exerr.Wrap(err, "Failed to start keepassxc").Build()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = cmd.Wait()
|
app.LogInfo(fmt.Sprintf("keepassxc started with PID %d", cmd.Process.Pid))
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
exitErr := &exec.ExitError{}
|
err = cmd.Wait()
|
||||||
if errors.As(err, &exitErr) {
|
|
||||||
|
|
||||||
log.LogInfo(fmt.Sprintf("keepass exited with code %d", exitErr.ExitCode()))
|
exitErr := &exec.ExitError{}
|
||||||
app.sigStopChan <- true
|
if errors.As(err, &exitErr) {
|
||||||
return
|
|
||||||
|
|
||||||
}
|
app.LogInfo(fmt.Sprintf("keepass exited with code %d", exitErr.ExitCode()))
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.LogError("Failed to run keepassxc", err)
|
|
||||||
app.sigErrChan <- exerr.Wrap(err, "Failed to run keepassxc").Build()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.LogInfo("keepassxc exited successfully")
|
|
||||||
app.sigStopChan <- true
|
app.sigStopChan <- true
|
||||||
return
|
return
|
||||||
|
|
||||||
}()
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
app.LogError("Failed to run keepassxc", err)
|
||||||
|
app.sigErrChan <- exerr.Wrap(err, "Failed to run keepassxc").Build()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.LogInfo("keepassxc exited successfully")
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
|
app.sigStopChan <- true
|
||||||
|
return
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) runSyncLoop() error {
|
func (app *Application) runSyncLoop() error {
|
||||||
@ -167,45 +172,83 @@ func (app *Application) runSyncLoop() error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-app.sigSyncLoopStopChan:
|
case <-app.sigSyncLoopStopChan:
|
||||||
log.LogInfo("Stopping sync loop (received signal)")
|
app.LogInfo("Stopping sync loop (received signal)")
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case event := <-watcher.Events:
|
case event := <-watcher.Events:
|
||||||
log.LogInfo(fmt.Sprintf("inotify event: [%s] %s", event.Op.String(), event.Name))
|
app.LogDebug(fmt.Sprintf("Received inotify event: [%s] %s", event.Op.String(), event.Name))
|
||||||
|
|
||||||
if event.Has(fsnotify.Write) && event.Name == app.dbFile {
|
if !event.Has(fsnotify.Write) {
|
||||||
func() {
|
app.LogDebug("Ignoring event - not a write event")
|
||||||
app.masterLock.Lock()
|
app.LogLine()
|
||||||
app.uploadRunning.Wait(false)
|
continue
|
||||||
app.uploadRunning.Set(true)
|
|
||||||
app.masterLock.Unlock()
|
|
||||||
|
|
||||||
defer app.uploadRunning.Set(false)
|
|
||||||
|
|
||||||
log.LogInfo("Database file was modified")
|
|
||||||
log.LogInfo(fmt.Sprintf("Sleeping for %d seconds", app.config.Debounce))
|
|
||||||
|
|
||||||
time.Sleep(timeext.FromSeconds(app.config.Debounce))
|
|
||||||
|
|
||||||
state := app.readState()
|
|
||||||
localCS, err := app.calcLocalChecksum()
|
|
||||||
if err != nil {
|
|
||||||
log.LogError("Failed to calculate local database checksum", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if localCS == state.Checksum {
|
|
||||||
log.LogInfo("Local database still matches remote (via checksum) - no need to upload")
|
|
||||||
log.LogInfo(fmt.Sprintf("Checksum (remote/cached) := %s", state.Checksum))
|
|
||||||
log.LogInfo(fmt.Sprintf("Checksum (local) := %s", localCS))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO upload with IfMatch
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.Name != app.dbFile {
|
||||||
|
app.LogDebug(fmt.Sprintf("Ignoring event - not the database file (%s)", app.dbFile))
|
||||||
|
app.LogLine()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
app.masterLock.Lock()
|
||||||
|
app.uploadRunning.Wait(false)
|
||||||
|
app.uploadRunning.Set(true)
|
||||||
|
app.masterLock.Unlock()
|
||||||
|
|
||||||
|
defer app.uploadRunning.Set(false)
|
||||||
|
|
||||||
|
app.LogInfo("Database file was modified")
|
||||||
|
app.LogInfo(fmt.Sprintf("Sleeping for %d seconds", app.config.Debounce))
|
||||||
|
|
||||||
|
time.Sleep(timeext.FromSeconds(app.config.Debounce))
|
||||||
|
|
||||||
|
state := app.readState()
|
||||||
|
localCS, err := app.calcLocalChecksum()
|
||||||
|
if err != nil {
|
||||||
|
app.LogError("Failed to calculate local database checksum", err)
|
||||||
|
app.showErrorNotification("Failed to calculate local database checksum")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if localCS == state.Checksum {
|
||||||
|
app.LogInfo("Local database still matches remote (via checksum) - no need to upload")
|
||||||
|
app.LogInfo(fmt.Sprintf("Checksum (remote/cached) := %s", state.Checksum))
|
||||||
|
app.LogInfo(fmt.Sprintf("Checksum (local) := %s", localCS))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
etag, lm, sha, sz, err := app.uploadDatabase(langext.Ptr(state.ETag))
|
||||||
|
if errors.Is(err, ETagConflictError) {
|
||||||
|
|
||||||
|
//TODO - choice notification
|
||||||
|
|
||||||
|
} else if err != nil {
|
||||||
|
app.LogError("Failed to upload remote database", err)
|
||||||
|
app.showErrorNotification("Failed to upload remote database")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.LogInfo(fmt.Sprintf("Uploaded database to remote"))
|
||||||
|
app.LogDebug(fmt.Sprintf("Checksum := %s", sha))
|
||||||
|
app.LogDebug(fmt.Sprintf("ETag := %s", etag))
|
||||||
|
app.LogDebug(fmt.Sprintf("Size := %s (%d)", langext.FormatBytes(sz), sz))
|
||||||
|
app.LogDebug(fmt.Sprintf("LastModified := %s", lm.Format(time.RFC3339)))
|
||||||
|
|
||||||
|
err = app.saveState(etag, lm, sha, sz)
|
||||||
|
if err != nil {
|
||||||
|
app.LogError("Failed to save state", err)
|
||||||
|
app.showErrorNotification("Failed to save state")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.showSuccessNotification("Uploaded database successfully")
|
||||||
|
|
||||||
|
app.LogLine()
|
||||||
|
}()
|
||||||
|
|
||||||
case err := <-watcher.Errors:
|
case err := <-watcher.Errors:
|
||||||
log.LogError("Filewatcher reported an error", err)
|
app.LogError("Filewatcher reported an error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
app/tray.go
16
app/tray.go
@ -13,14 +13,22 @@ func (app *Application) initTray() {
|
|||||||
systray.SetTitle("KeepassXC Sync")
|
systray.SetTitle("KeepassXC Sync")
|
||||||
systray.SetTooltip("Initializing...")
|
systray.SetTooltip("Initializing...")
|
||||||
|
|
||||||
app.trayReady = true
|
app.LogDebug("SysTray initialized")
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
|
app.trayReady.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
systray.Run(trayOnReady, nil)
|
systray.Run(trayOnReady, nil)
|
||||||
|
|
||||||
|
app.LogDebug("SysTray stopped")
|
||||||
|
app.LogLine()
|
||||||
|
|
||||||
|
app.trayReady.Set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) setTrayState(txt string, icon []byte) func() {
|
func (app *Application) setTrayState(txt string, icon []byte) func() {
|
||||||
if !app.trayReady {
|
if !app.trayReady.Get() {
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +42,7 @@ func (app *Application) setTrayState(txt string, icon []byte) func() {
|
|||||||
app.masterLock.Lock()
|
app.masterLock.Lock()
|
||||||
defer app.masterLock.Unlock()
|
defer app.masterLock.Unlock()
|
||||||
|
|
||||||
if !app.trayReady {
|
if !app.trayReady.Get() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +54,7 @@ func (app *Application) setTrayState(txt string, icon []byte) func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) setTrayStateDirect(txt string, icon []byte) {
|
func (app *Application) setTrayStateDirect(txt string, icon []byte) {
|
||||||
if !app.trayReady {
|
if !app.trayReady.Get() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
||||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||||
"github.com/shirou/gopsutil/v3/process"
|
"github.com/shirou/gopsutil/v3/process"
|
||||||
"mikescher.com/kpsync/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func fileExists(p string) bool {
|
func fileExists(p string) bool {
|
||||||
@ -78,10 +77,10 @@ func (app *Application) calcLocalChecksum() (string, error) {
|
|||||||
return cryptext.BytesSha256(bin), nil
|
return cryptext.BytesSha256(bin), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isKeepassRunning() bool {
|
func (app *Application) isKeepassRunning() bool {
|
||||||
proc, err := process.Processes()
|
proc, err := process.Processes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.LogError("failed to query existing keepass process", err)
|
app.LogError("failed to query existing keepass process", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
||||||
@ -11,6 +13,8 @@ import (
|
|||||||
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
|
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ETagConflictError = errors.New("ETag conflict")
|
||||||
|
|
||||||
func (app *Application) downloadDatabase() (string, time.Time, string, int64, error) {
|
func (app *Application) downloadDatabase() (string, time.Time, string, int64, error) {
|
||||||
|
|
||||||
client := http.Client{Timeout: 90 * time.Second}
|
client := http.Client{Timeout: 90 * time.Second}
|
||||||
@ -40,14 +44,13 @@ func (app *Application) downloadDatabase() (string, time.Time, string, int64, er
|
|||||||
if etag == "" {
|
if etag == "" {
|
||||||
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
||||||
}
|
}
|
||||||
|
etag = strings.Trim(etag, "\"\r\n ")
|
||||||
|
|
||||||
lmStr := resp.Header.Get("Last-Modified")
|
lmStr := resp.Header.Get("Last-Modified")
|
||||||
|
|
||||||
lm, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
lm, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
lm = lm.In(timeext.TimezoneBerlin)
|
lm = lm.In(timeext.TimezoneBerlin)
|
||||||
|
|
||||||
sha := cryptext.BytesSha256(bin)
|
sha := cryptext.BytesSha256(bin)
|
||||||
@ -85,6 +88,7 @@ func (app *Application) getRemoteETag() (string, time.Time, error) {
|
|||||||
if etag == "" {
|
if etag == "" {
|
||||||
return "", time.Time{}, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
return "", time.Time{}, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
||||||
}
|
}
|
||||||
|
etag = strings.Trim(etag, "\"\r\n ")
|
||||||
|
|
||||||
lmStr := resp.Header.Get("Last-Modified")
|
lmStr := resp.Header.Get("Last-Modified")
|
||||||
|
|
||||||
@ -97,3 +101,58 @@ func (app *Application) getRemoteETag() (string, time.Time, error) {
|
|||||||
|
|
||||||
return etag, lm, nil
|
return etag, lm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time, string, int64, error) {
|
||||||
|
|
||||||
|
client := http.Client{Timeout: 90 * time.Second}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", app.config.WebDAVURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, "", 0, exerr.Wrap(err, "").Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if etagIfMatch != nil {
|
||||||
|
req.Header.Set("If-Match", "\""+*etagIfMatch+"\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
bin, err := os.ReadFile(app.dbFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to read database file").Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := cryptext.BytesSha256(bin)
|
||||||
|
|
||||||
|
sz := int64(len(bin))
|
||||||
|
|
||||||
|
req.ContentLength = sz
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to upload remote database").Build()
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusNoContent {
|
||||||
|
|
||||||
|
etag := resp.Header.Get("ETag")
|
||||||
|
if etag == "" {
|
||||||
|
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
||||||
|
}
|
||||||
|
etag = strings.Trim(etag, "\"\r\n ")
|
||||||
|
|
||||||
|
lmStr := resp.Header.Get("Last-Modified")
|
||||||
|
lm, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||||
|
}
|
||||||
|
lm = lm.In(timeext.TimezoneBerlin)
|
||||||
|
|
||||||
|
return etag, lm, sha, sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusPreconditionFailed {
|
||||||
|
return "", time.Time{}, "", 0, ETagConflictError
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "Failed to upload remote database").Int("sc", resp.StatusCode).Build()
|
||||||
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -46,6 +46,7 @@ require (
|
|||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/term v0.33.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -133,6 +133,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||||
|
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
|
||||||
)
|
|
||||||
|
|
||||||
//TODO
|
|
||||||
|
|
||||||
func Fatal(msg string) {
|
|
||||||
panic(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FatalErr(msg string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
println("FATAL: " + msg)
|
|
||||||
println(" " + err.Error())
|
|
||||||
println(exerr.FromError(err).FormatLog(exerr.LogPrintOverview))
|
|
||||||
panic(0)
|
|
||||||
} else {
|
|
||||||
panic("FATAL: " + msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LogError(msg string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
println("ERROR: " + msg)
|
|
||||||
println(" " + err.Error())
|
|
||||||
println(exerr.FromError(err).FormatLog(exerr.LogPrintOverview))
|
|
||||||
} else {
|
|
||||||
println("ERROR: " + msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LogInfo(msg string) {
|
|
||||||
println("INFO: " + msg)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user