logging, upload, etc

This commit is contained in:
Mike Schwörer 2025-08-19 12:44:15 +02:00
parent 0f9b423d2f
commit 47080e14db
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
12 changed files with 399 additions and 182 deletions

View File

@ -3,10 +3,10 @@
#########################
build: enums
CGO_ENABLED=0 go build -o _out/kpsync ./cmd/kpsync
CGO_ENABLED=0 go build -o _out/kpsync ./cmd/cli
run: build
./_out/ffsclient
./_out/kpsync
clean:
go clean

View File

@ -1,25 +1,28 @@
package app
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"fyne.io/systray"
"git.blackforestbytes.com/BlackForestBytes/goext/syncext"
"mikescher.com/kpsync"
"git.blackforestbytes.com/BlackForestBytes/goext/termext"
"mikescher.com/kpsync/assets"
"mikescher.com/kpsync/log"
)
type Application struct {
masterLock sync.Mutex
config kpsync.Config
config Config
trayReady bool
trayReady *syncext.AtomicBool
uploadRunning *syncext.AtomicBool
syncLoopRunning *syncext.AtomicBool
keepassRunning *syncext.AtomicBool
sigStopChan chan bool // keepass exited
sigErrChan chan error // fatal error
@ -33,31 +36,62 @@ type Application struct {
func NewApplication() *Application {
cfg := kpsync.LoadConfig()
return &Application{
app := &Application{
masterLock: sync.Mutex{},
config: cfg,
uploadRunning: syncext.NewAtomicBool(false),
trayReady: false,
trayReady: syncext.NewAtomicBool(false),
syncLoopRunning: syncext.NewAtomicBool(false),
keepassRunning: syncext.NewAtomicBool(false),
sigStopChan: make(chan bool, 128),
sigErrChan: make(chan error, 128),
sigSyncLoopStopChan: 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() {
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.syncLoopRunning = syncext.NewAtomicBool(true)
defer app.syncLoopRunning.Set(false)
err := app.initSync()
if err != nil {
app.sigErrChan <- err
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)
err = app.runSyncLoop()
@ -71,34 +105,58 @@ func (app *Application) Run() {
signal.Notify(sigTerm, os.Interrupt, syscall.SIGTERM)
select {
case <-sigTerm:
case <-sigTerm: // kpsync received SIGTERM
app.sigSyncLoopStopChan <- true
app.sigTermKeepassChan <- true
log.LogInfo("Stopping application (received SIGTERM signal)")
app.LogInfo("Stopping application (received SIGTERM signal)")
// TODO term
app.stopBackgroundRoutines()
case err := <-app.sigErrChan:
// TODO try final sync
app.sigSyncLoopStopChan <- true
app.sigTermKeepassChan <- true
log.LogInfo("Stopping application (received ERROR)")
log.LogError(err.Error(), err)
case err := <-app.sigErrChan: // fatal error
// TODO stop
app.LogInfo("Stopping application (received ERROR)")
case _ = <-app.sigStopChan:
app.stopBackgroundRoutines()
app.sigSyncLoopStopChan <- true
app.sigTermKeepassChan <- true
log.LogInfo("Stopping application (received STOP)")
app.LogError("Stopped due to error: "+err.Error(), nil)
// TODO stop
// TODO stop?
case _ = <-app.sigStopChan: // keepass exited
app.LogInfo("Stopping application (received STOP)")
app.stopBackgroundRoutines()
// TODO try final sync
}
}
if app.trayReady {
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()
}

View File

@ -1,4 +1,4 @@
package kpsync
package app
import (
"encoding/json"
@ -9,7 +9,6 @@ import (
"strings"
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"mikescher.com/kpsync/log"
)
type Config struct {
@ -21,10 +20,12 @@ type Config struct {
WorkDir string `json:"work_dir"`
ForceColors bool `json:"force_colors"`
Debounce int `json:"debounce"`
}
func LoadConfig() Config {
func (app *Application) loadConfig() (Config, string) {
var configPath string
flag.StringVar(&configPath, "config", "~/.config/kpsync.json", "Path to the configuration file")
@ -46,12 +47,15 @@ func LoadConfig() Config {
var debounce int
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()
if strings.HasPrefix(configPath, "~") {
usr, err := user.Current()
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)
@ -67,18 +71,19 @@ func LoadConfig() Config {
LocalFallback: "",
WorkDir: "/tmp/kpsync",
Debounce: 3500,
ForceColors: false,
}, "", " ")), 0644)
}
cfgBin, err := os.ReadFile(configPath)
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
err = json.Unmarshal(cfgBin, &cfg)
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 != "" {
@ -99,6 +104,9 @@ func LoadConfig() Config {
if debounce > 0 {
cfg.Debounce = debounce
}
return cfg
if forceColors {
cfg.ForceColors = forceColors
}
return cfg, configPath
}

66
app/logger.go Normal file
View 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
View File

@ -0,0 +1,9 @@
package app
func (app *Application) showErrorNotification(msg string) {
//TODO
}
func (app *Application) showSuccessNotification(msg string) {
//TODO
}

View File

@ -14,7 +14,6 @@ import (
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
"github.com/fsnotify/fsnotify"
"mikescher.com/kpsync/assets"
"mikescher.com/kpsync/log"
)
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.stateFile = path.Join(app.config.WorkDir, "kpsync.state")
if isKeepassRunning() {
log.LogError("keepassxc is already running!", nil)
if app.isKeepassRunning() {
app.LogError("keepassxc is already running!", nil)
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) {
localCS, err := app.calcLocalChecksum()
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 {
remoteETag, remoteLM, err := app.getRemoteETag()
if err != nil {
log.LogError("Failed to get remote ETag", err)
app.LogError("Failed to get remote ETag", err)
} else if remoteETag == state.ETag {
log.LogInfo(fmt.Sprintf("Found local database matching remote database - skip initial download"))
log.LogInfo(fmt.Sprintf("Checksum (cached) := %s", state.Checksum))
log.LogInfo(fmt.Sprintf("Checksum (local) := %s", localCS))
log.LogInfo(fmt.Sprintf("ETag (cached) := %s", state.ETag))
log.LogInfo(fmt.Sprintf("ETag (remote) := %s", remoteETag))
log.LogInfo(fmt.Sprintf("LastModified (cached) := %s", state.LastModified.Format(time.RFC3339)))
log.LogInfo(fmt.Sprintf("LastModified (remote) := %s", remoteLM.Format(time.RFC3339)))
app.LogInfo(fmt.Sprintf("Found local database matching remote database - skip initial download"))
app.LogDebug(fmt.Sprintf("Checksum (cached) := %s", state.Checksum))
app.LogDebug(fmt.Sprintf("Checksum (local) := %s", localCS))
app.LogDebug(fmt.Sprintf("ETag (cached) := %s", state.ETag))
app.LogDebug(fmt.Sprintf("ETag (remote) := %s", remoteETag))
app.LogDebug(fmt.Sprintf("LastModified (cached) := %s", state.LastModified.Format(time.RFC3339)))
app.LogDebug(fmt.Sprintf("LastModified (remote) := %s", remoteLM.Format(time.RFC3339)))
app.LogLine()
needsDownload = false
}
@ -64,92 +64,97 @@ func (app *Application) initSync() error {
fin := app.setTrayState("Downloading database", assets.IconDownload)
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()
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()
}
log.LogInfo(fmt.Sprintf("Downloaded remote database to %s", app.dbFile))
log.LogInfo(fmt.Sprintf("Checksum := %s", sha))
log.LogInfo(fmt.Sprintf("ETag := %s", etag))
log.LogInfo(fmt.Sprintf("Size := %s", langext.FormatBytes(sz)))
log.LogInfo(fmt.Sprintf("LastModified := %s", lm.Format(time.RFC3339)))
app.LogInfo(fmt.Sprintf("Downloaded remote database to %s", app.dbFile))
app.LogInfo(fmt.Sprintf("Checksum := %s", sha))
app.LogInfo(fmt.Sprintf("ETag := %s", etag))
app.LogInfo(fmt.Sprintf("Size := %s (%d)", langext.FormatBytes(sz), sz))
app.LogInfo(fmt.Sprintf("LastModified := %s", lm.Format(time.RFC3339)))
err = app.saveState(etag, lm, sha, sz)
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()
}
app.LogLine()
return nil
}()
if err != nil {
return exerr.Wrap(err, "").Build()
}
} 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()
}
go func() {
return nil
}
log.LogInfo("Starting keepassxc...")
func (app *Application) runKeepass() {
app.LogInfo("Starting keepassxc...")
cmd := exec.Command("keepassxc", app.dbFile)
go func() {
select {
case <-app.sigTermKeepassChan:
log.LogInfo("Received signal to terminate keepassxc")
app.LogInfo("Received signal to terminate keepassxc")
if cmd != nil && cmd.Process != nil {
log.LogInfo(fmt.Sprintf("Terminating keepassxc %d", cmd.Process.Pid))
app.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)
app.LogError("Failed to terminate keepassxc", err)
} else {
log.LogInfo("keepassxc terminated successfully")
app.LogInfo("keepassxc terminated successfully")
}
} else {
log.LogInfo("No keepassxc process to terminate")
app.LogInfo("No keepassxc process to terminate")
}
}
}()
err := cmd.Start()
if err != nil {
log.LogError("Failed to start keepassxc", err)
app.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))
app.LogInfo(fmt.Sprintf("keepassxc started with PID %d", cmd.Process.Pid))
app.LogLine()
err = cmd.Wait()
exitErr := &exec.ExitError{}
if errors.As(err, &exitErr) {
log.LogInfo(fmt.Sprintf("keepass exited with code %d", exitErr.ExitCode()))
app.LogInfo(fmt.Sprintf("keepass exited with code %d", exitErr.ExitCode()))
app.sigStopChan <- true
return
}
if err != nil {
log.LogError("Failed to run keepassxc", err)
app.LogError("Failed to run keepassxc", err)
app.sigErrChan <- exerr.Wrap(err, "Failed to run keepassxc").Build()
return
}
log.LogInfo("keepassxc exited successfully")
app.LogInfo("keepassxc exited successfully")
app.LogLine()
app.sigStopChan <- true
return
}()
return nil
}
func (app *Application) runSyncLoop() error {
@ -167,13 +172,24 @@ func (app *Application) runSyncLoop() error {
for {
select {
case <-app.sigSyncLoopStopChan:
log.LogInfo("Stopping sync loop (received signal)")
app.LogInfo("Stopping sync loop (received signal)")
return nil
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) {
app.LogDebug("Ignoring event - not a write event")
app.LogLine()
continue
}
if event.Name != app.dbFile {
app.LogDebug(fmt.Sprintf("Ignoring event - not the database file (%s)", app.dbFile))
app.LogLine()
continue
}
if event.Has(fsnotify.Write) && event.Name == app.dbFile {
func() {
app.masterLock.Lock()
app.uploadRunning.Wait(false)
@ -182,30 +198,57 @@ func (app *Application) runSyncLoop() error {
defer app.uploadRunning.Set(false)
log.LogInfo("Database file was modified")
log.LogInfo(fmt.Sprintf("Sleeping for %d seconds", app.config.Debounce))
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 {
log.LogError("Failed to calculate local database checksum", err)
app.LogError("Failed to calculate local database checksum", err)
app.showErrorNotification("Failed to calculate local database checksum")
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))
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
}
//TODO upload with IfMatch
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:
log.LogError("Filewatcher reported an error", err)
app.LogError("Filewatcher reported an error", err)
}
}
}

View File

@ -13,14 +13,22 @@ func (app *Application) initTray() {
systray.SetTitle("KeepassXC Sync")
systray.SetTooltip("Initializing...")
app.trayReady = true
app.LogDebug("SysTray initialized")
app.LogLine()
app.trayReady.Set(true)
}
systray.Run(trayOnReady, nil)
app.LogDebug("SysTray stopped")
app.LogLine()
app.trayReady.Set(false)
}
func (app *Application) setTrayState(txt string, icon []byte) func() {
if !app.trayReady {
if !app.trayReady.Get() {
return func() {}
}
@ -34,7 +42,7 @@ func (app *Application) setTrayState(txt string, icon []byte) func() {
app.masterLock.Lock()
defer app.masterLock.Unlock()
if !app.trayReady {
if !app.trayReady.Get() {
return
}
@ -46,7 +54,7 @@ func (app *Application) setTrayState(txt string, icon []byte) func() {
}
func (app *Application) setTrayStateDirect(txt string, icon []byte) {
if !app.trayReady {
if !app.trayReady.Get() {
return
}

View File

@ -9,7 +9,6 @@ import (
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
"github.com/shirou/gopsutil/v3/process"
"mikescher.com/kpsync/log"
)
func fileExists(p string) bool {
@ -78,10 +77,10 @@ func (app *Application) calcLocalChecksum() (string, error) {
return cryptext.BytesSha256(bin), nil
}
func isKeepassRunning() bool {
func (app *Application) isKeepassRunning() bool {
proc, err := process.Processes()
if err != nil {
log.LogError("failed to query existing keepass process", err)
app.LogError("failed to query existing keepass process", err)
return false
}

View File

@ -1,9 +1,11 @@
package app
import (
"errors"
"io"
"net/http"
"os"
"strings"
"time"
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
@ -11,6 +13,8 @@ import (
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
)
var ETagConflictError = errors.New("ETag conflict")
func (app *Application) downloadDatabase() (string, time.Time, string, int64, error) {
client := http.Client{Timeout: 90 * time.Second}
@ -40,14 +44,13 @@ func (app *Application) downloadDatabase() (string, time.Time, string, int64, er
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)
sha := cryptext.BytesSha256(bin)
@ -85,6 +88,7 @@ func (app *Application) getRemoteETag() (string, time.Time, error) {
if etag == "" {
return "", time.Time{}, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
}
etag = strings.Trim(etag, "\"\r\n ")
lmStr := resp.Header.Get("Last-Modified")
@ -97,3 +101,58 @@ func (app *Application) getRemoteETag() (string, time.Time, error) {
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
View File

@ -46,6 +46,7 @@ require (
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.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
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

2
go.sum
View File

@ -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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
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/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -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)
}