mirror of
https://github.com/Mikescher/kpsync.git
synced 2025-08-25 08:38:03 +02:00
kinda works, upload, download etc
This commit is contained in:
parent
1f6fb583ee
commit
52399acb42
2
TODO.md
2
TODO.md
@ -17,3 +17,5 @@
|
||||
|
||||
- colorful log
|
||||
- download/upload progress log
|
||||
|
||||
- logfile in workdir
|
@ -33,6 +33,12 @@ type Application struct {
|
||||
|
||||
dbFile string
|
||||
stateFile string
|
||||
|
||||
currSysTrayTooltop string
|
||||
|
||||
trayItemChecksum *systray.MenuItem
|
||||
trayItemETag *systray.MenuItem
|
||||
trayItemLastModified *systray.MenuItem
|
||||
}
|
||||
|
||||
func NewApplication() *Application {
|
||||
@ -68,15 +74,14 @@ func (app *Application) Run() {
|
||||
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("Debounce := %d ms", 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)
|
||||
app.syncLoopRunning.Set(true)
|
||||
defer app.syncLoopRunning.Set(false)
|
||||
|
||||
isr, err := app.initSync()
|
||||
@ -91,7 +96,7 @@ func (app *Application) Run() {
|
||||
} else if isr == InitSyncResponseOkay {
|
||||
|
||||
go func() {
|
||||
app.keepassRunning = syncext.NewAtomicBool(true)
|
||||
app.keepassRunning.Set(true)
|
||||
defer app.keepassRunning.Set(false)
|
||||
|
||||
app.runKeepass(false)
|
||||
@ -113,7 +118,7 @@ func (app *Application) Run() {
|
||||
app.LogDebug(fmt.Sprintf("DB-Path := '%s'", app.config.LocalFallback))
|
||||
|
||||
go func() {
|
||||
app.keepassRunning = syncext.NewAtomicBool(true)
|
||||
app.keepassRunning.Set(true)
|
||||
defer app.keepassRunning.Set(false)
|
||||
|
||||
app.runKeepass(true)
|
||||
@ -139,7 +144,9 @@ func (app *Application) Run() {
|
||||
|
||||
app.stopBackgroundRoutines()
|
||||
|
||||
// TODO try final sync (?)
|
||||
app.runFinalSync()
|
||||
|
||||
return
|
||||
|
||||
case err := <-app.sigErrChan: // fatal error
|
||||
|
||||
@ -165,7 +172,9 @@ func (app *Application) Run() {
|
||||
|
||||
app.stopBackgroundRoutines()
|
||||
|
||||
// TODO try final sync
|
||||
app.runFinalSync()
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package app
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
@ -57,10 +57,9 @@ func (app *Application) loadConfig() (Config, string) {
|
||||
if err != nil {
|
||||
app.LogFatalErr("Failed to query users home directory", err)
|
||||
}
|
||||
fmt.Println(usr.HomeDir)
|
||||
|
||||
configPath = strings.TrimPrefix(configPath, "~")
|
||||
configPath = fmt.Sprintf("%s/%s", usr.HomeDir, configPath)
|
||||
configPath = path.Join(usr.HomeDir, configPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) && configPath != "" {
|
||||
|
@ -40,7 +40,7 @@ func (app *Application) showSuccessNotification(msg string, body string) {
|
||||
|
||||
res, err := cmdext.
|
||||
Runner("notify-send").
|
||||
Arg("--urgency=normal").
|
||||
Arg("--urgency=critical").
|
||||
Arg("--app-name=kpsync").
|
||||
Arg("--print-id").
|
||||
Arg(msg).
|
||||
@ -74,7 +74,7 @@ func (app *Application) showChoiceNotification(msg string, body string, options
|
||||
bldr = bldr.Arg("--action=" + kOpt + "=" + vOpt)
|
||||
}
|
||||
|
||||
bldr = bldr.Arg(msg)
|
||||
bldr = bldr.Arg(msg).Arg(body)
|
||||
|
||||
res, err := bldr.Run()
|
||||
if err != nil {
|
||||
|
45
app/reader.go
Normal file
45
app/reader.go
Normal file
@ -0,0 +1,45 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type teeReadCloser struct {
|
||||
r io.Reader
|
||||
c io.Closer
|
||||
}
|
||||
|
||||
func (t *teeReadCloser) Read(p []byte) (int, error) { return t.r.Read(p) }
|
||||
func (t *teeReadCloser) Close() error { return t.c.Close() }
|
||||
|
||||
type progressWriter struct {
|
||||
sync.Mutex
|
||||
|
||||
done int64
|
||||
total int64
|
||||
cb func(done, total int64)
|
||||
}
|
||||
|
||||
func (pw *progressWriter) Write(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
|
||||
if pw.cb != nil {
|
||||
pw.Lock()
|
||||
defer pw.Unlock()
|
||||
pw.done += int64(n)
|
||||
pw.cb(pw.done, pw.total)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func ReadAllWithProgress(r io.Reader, totalBytes int64, onProgress func(done, total int64)) ([]byte, error) {
|
||||
return io.ReadAll(NewProgressReader(r, totalBytes, onProgress))
|
||||
}
|
||||
|
||||
func NewProgressReader(r io.Reader, totalBytes int64, onProgress func(done, total int64)) io.ReadCloser {
|
||||
pw := &progressWriter{total: totalBytes, cb: onProgress}
|
||||
|
||||
return &teeReadCloser{r: io.TeeReader(r, pw), c: io.NopCloser(r)}
|
||||
}
|
62
app/sync.go
62
app/sync.go
@ -36,6 +36,7 @@ func (app *Application) initSync() (InitSyncResponse, error) {
|
||||
|
||||
if app.isKeepassRunning() {
|
||||
app.LogError("keepassxc is already running!", nil)
|
||||
app.showErrorNotification("KeePassSync: Error", "An keepassxc instance is already running!\nPlease close it before starting kpsync.")
|
||||
return "", exerr.New(exerr.TypeInternal, "keepassxc is already running").Build()
|
||||
}
|
||||
|
||||
@ -98,7 +99,7 @@ func (app *Application) initSync() (InitSyncResponse, error) {
|
||||
}()
|
||||
if err != nil {
|
||||
|
||||
r, err := app.showChoiceNotification("Failed to download remote database.\nUse local fallback?", map[string]string{"y": "Yes", "n": "Abort"})
|
||||
r, err := app.showChoiceNotification("KeePassSync", "Failed to download remote database.\nUse local fallback?", map[string]string{"y": "Yes", "n": "Abort"})
|
||||
if err != nil {
|
||||
app.LogError("Failed to show choice notification", err)
|
||||
return "", exerr.Wrap(err, "Failed to show choice notification").Build()
|
||||
@ -134,14 +135,20 @@ func (app *Application) runKeepass(fallback bool) {
|
||||
|
||||
cmd := exec.Command("keepassxc", filePath)
|
||||
|
||||
bgStop := make(chan bool, 128)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-bgStop:
|
||||
return
|
||||
case <-app.sigTermKeepassChan:
|
||||
app.LogInfo("Received signal to terminate keepassxc")
|
||||
if cmd != nil && cmd.Process != nil {
|
||||
app.LogInfo(fmt.Sprintf("Terminating keepassxc %d", cmd.Process.Pid))
|
||||
err := cmd.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrProcessDone) {
|
||||
app.LogInfo("keepassxc already terminated")
|
||||
} else if err != nil {
|
||||
app.LogError("Failed to terminate keepassxc", err)
|
||||
} else {
|
||||
app.LogInfo("keepassxc terminated successfully")
|
||||
@ -164,6 +171,8 @@ func (app *Application) runKeepass(fallback bool) {
|
||||
|
||||
err = cmd.Wait()
|
||||
|
||||
bgStop <- true
|
||||
|
||||
exitErr := &exec.ExitError{}
|
||||
if errors.As(err, &exitErr) {
|
||||
|
||||
@ -208,8 +217,8 @@ func (app *Application) runSyncLoop() error {
|
||||
case event := <-watcher.Events:
|
||||
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")
|
||||
if !event.Has(fsnotify.Write) && !event.Has(fsnotify.Create) {
|
||||
app.LogDebug("Ignoring event - not a write|create event")
|
||||
app.LogLine()
|
||||
continue
|
||||
}
|
||||
@ -240,9 +249,9 @@ func (app *Application) onDBFileChanged() {
|
||||
defer fin1()
|
||||
|
||||
app.LogInfo("Database file was modified")
|
||||
app.LogInfo(fmt.Sprintf("Sleeping for %d seconds", app.config.Debounce))
|
||||
app.LogInfo(fmt.Sprintf("Sleeping for %d ms", app.config.Debounce))
|
||||
|
||||
time.Sleep(timeext.FromSeconds(app.config.Debounce))
|
||||
time.Sleep(timeext.FromMilliseconds(app.config.Debounce))
|
||||
|
||||
state := app.readState()
|
||||
localCS, err := app.calcLocalChecksum()
|
||||
@ -259,6 +268,10 @@ func (app *Application) onDBFileChanged() {
|
||||
return
|
||||
}
|
||||
|
||||
app.doDBUpload(state, fin1, true)
|
||||
}
|
||||
|
||||
func (app *Application) doDBUpload(state *State, stateClear func(), allowConflictResolution bool) {
|
||||
app.LogInfo("Uploading database to remote")
|
||||
|
||||
var eTagPtr *string = nil
|
||||
@ -267,9 +280,9 @@ func (app *Application) onDBFileChanged() {
|
||||
}
|
||||
|
||||
etag, lm, sha, sz, err := app.uploadDatabase(eTagPtr)
|
||||
if errors.Is(err, ETagConflictError) {
|
||||
if errors.Is(err, ETagConflictError) && allowConflictResolution {
|
||||
|
||||
fin1()
|
||||
stateClear()
|
||||
fin2 := app.setTrayState("Uploading database (conflict", assets.IconUploadConflict)
|
||||
defer fin2()
|
||||
|
||||
@ -365,7 +378,38 @@ func (app *Application) onDBFileChanged() {
|
||||
return
|
||||
}
|
||||
|
||||
app.showSuccessNotification("KeePassSync: Error", "Uploaded database successfully")
|
||||
app.showSuccessNotification("KeePassSync", "Uploaded database successfully")
|
||||
|
||||
app.LogLine()
|
||||
}
|
||||
|
||||
func (app *Application) runFinalSync() {
|
||||
app.masterLock.Lock()
|
||||
app.uploadRunning.Wait(false)
|
||||
app.uploadRunning.Set(true)
|
||||
app.masterLock.Unlock()
|
||||
|
||||
defer app.uploadRunning.Set(false)
|
||||
|
||||
fin1 := app.setTrayState("Uploading database", assets.IconUpload)
|
||||
defer fin1()
|
||||
|
||||
app.LogInfo("Starting final sync...")
|
||||
|
||||
state := app.readState()
|
||||
localCS, err := app.calcLocalChecksum()
|
||||
if err != nil {
|
||||
app.LogError("Failed to calculate local database checksum", err)
|
||||
app.showErrorNotification("KeePassSync: Error", "Failed to calculate local database checksum")
|
||||
return
|
||||
}
|
||||
|
||||
if state != nil && 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
|
||||
}
|
||||
|
||||
app.doDBUpload(state, fin1, false)
|
||||
}
|
||||
|
59
app/tray.go
59
app/tray.go
@ -7,20 +7,61 @@ import (
|
||||
|
||||
func (app *Application) initTray() {
|
||||
|
||||
sigBGStop := make(chan bool, 128)
|
||||
|
||||
trayOnReady := func() {
|
||||
|
||||
app.masterLock.Lock()
|
||||
defer app.masterLock.Unlock()
|
||||
|
||||
systray.SetIcon(assets.IconInit)
|
||||
systray.SetTitle("KeepassXC Sync")
|
||||
systray.SetTooltip("Initializing...")
|
||||
app.currSysTrayTooltop = "Initializing..."
|
||||
systray.SetTooltip(app.currSysTrayTooltop)
|
||||
|
||||
miSync := systray.AddMenuItem("Sync Now (checked)", "")
|
||||
miSyncForce := systray.AddMenuItem("Sync Now (forced)", "")
|
||||
miShowLog := systray.AddMenuItem("Show Log", "")
|
||||
systray.AddMenuItem("", "")
|
||||
app.trayItemChecksum = systray.AddMenuItem("Checksum: {...}", "")
|
||||
app.trayItemETag = systray.AddMenuItem("ETag: {...}", "")
|
||||
app.trayItemLastModified = systray.AddMenuItem("LastModified: {...}", "")
|
||||
systray.AddMenuItem("", "")
|
||||
miQuit := systray.AddMenuItem("Quit", "")
|
||||
|
||||
app.LogDebug("SysTray initialized")
|
||||
app.LogLine()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-miSync.ClickedCh:
|
||||
app.LogDebug("SysTray: [Sync Now (checked)] clicked")
|
||||
//TODO
|
||||
case <-miSyncForce.ClickedCh:
|
||||
app.LogDebug("SysTray: [Sync Now (forced)] clicked")
|
||||
//TODO
|
||||
case <-miShowLog.ClickedCh:
|
||||
app.LogDebug("SysTray: [Show Log] clicked")
|
||||
//TODO
|
||||
case <-miQuit.ClickedCh:
|
||||
app.LogDebug("SysTray: [Quit] clicked")
|
||||
app.sigManualStopChan <- true
|
||||
case <-sigBGStop:
|
||||
app.LogDebug("SysTray: Click-Listener goroutine stopped")
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
app.trayReady.Set(true)
|
||||
}
|
||||
|
||||
systray.Run(trayOnReady, nil)
|
||||
|
||||
sigBGStop <- true
|
||||
|
||||
app.LogDebug("SysTray stopped")
|
||||
app.LogLine()
|
||||
|
||||
@ -53,7 +94,8 @@ func (app *Application) setTrayState(txt string, icon []byte) func() {
|
||||
}
|
||||
|
||||
systray.SetIcon(assets.IconDefault)
|
||||
systray.SetTooltip("Sleeping...")
|
||||
app.currSysTrayTooltop = "Sleeping..."
|
||||
systray.SetTooltip(app.currSysTrayTooltop)
|
||||
|
||||
finDone = true
|
||||
}
|
||||
@ -72,3 +114,16 @@ func (app *Application) setTrayStateDirect(txt string, icon []byte) {
|
||||
systray.SetIcon(icon)
|
||||
systray.SetTooltip(txt)
|
||||
}
|
||||
|
||||
func (app *Application) setTrayTooltip(txt string) {
|
||||
if !app.trayReady.Get() {
|
||||
return
|
||||
}
|
||||
|
||||
app.masterLock.Lock()
|
||||
defer app.masterLock.Unlock()
|
||||
|
||||
systray.SetTooltip(txt)
|
||||
app.currSysTrayTooltop = txt
|
||||
systray.SetTooltip(app.currSysTrayTooltop)
|
||||
}
|
||||
|
12
app/utils.go
12
app/utils.go
@ -2,12 +2,14 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/timeext"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
@ -65,6 +67,16 @@ func (app *Application) saveState(eTag string, lastModified time.Time, checksum
|
||||
return exerr.Wrap(err, "Failed to write state file").Build()
|
||||
}
|
||||
|
||||
if app.trayItemChecksum != nil {
|
||||
app.trayItemChecksum.SetTitle(fmt.Sprintf("Checksum: %s", checksum))
|
||||
}
|
||||
if app.trayItemETag != nil {
|
||||
app.trayItemETag.SetTitle(fmt.Sprintf("ETag: %s", eTag))
|
||||
}
|
||||
if app.trayItemLastModified != nil {
|
||||
app.trayItemLastModified.SetTitle(fmt.Sprintf("LastModified: %s", lastModified.In(timeext.TimezoneBerlin).Format(time.RFC3339)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
111
app/webdav.go
111
app/webdav.go
@ -1,8 +1,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@ -17,6 +18,9 @@ var ETagConflictError = errors.New("ETag conflict")
|
||||
|
||||
func (app *Application) downloadDatabase() (string, time.Time, string, int64, error) {
|
||||
|
||||
prevTT := app.currSysTrayTooltop
|
||||
defer app.setTrayTooltip(prevTT)
|
||||
|
||||
client := http.Client{Timeout: 90 * time.Second}
|
||||
|
||||
req, err := http.NewRequest("GET", app.config.WebDAVURL, nil)
|
||||
@ -26,6 +30,9 @@ func (app *Application) downloadDatabase() (string, time.Time, string, int64, er
|
||||
|
||||
req.SetBasicAuth(app.config.WebDAVUser, app.config.WebDAVPass)
|
||||
|
||||
t0 := time.Now()
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Starting WebDAV download..."))
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to download remote database").Build()
|
||||
@ -35,23 +42,26 @@ func (app *Application) downloadDatabase() (string, time.Time, string, int64, er
|
||||
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "Failed to download remote database").Int("sc", resp.StatusCode).Build()
|
||||
}
|
||||
|
||||
bin, err := io.ReadAll(resp.Body)
|
||||
currTT := ""
|
||||
progressCallback := func(current int64, total int64) {
|
||||
newTT := fmt.Sprintf("Downloading (%.0f%%)", float64(current)/float64(total)*100)
|
||||
if currTT != newTT {
|
||||
app.setTrayTooltip(newTT)
|
||||
currTT = newTT
|
||||
}
|
||||
}
|
||||
|
||||
bin, err := ReadAllWithProgress(resp.Body, resp.ContentLength, progressCallback)
|
||||
if err != nil {
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to read response body").Build()
|
||||
}
|
||||
|
||||
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 ")
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Finished WebDAV download in %s", time.Since(t0)))
|
||||
|
||||
lmStr := resp.Header.Get("Last-Modified")
|
||||
lm, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
||||
etag, lm, err := app.parseHeader(resp)
|
||||
if err != nil {
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "").Build()
|
||||
}
|
||||
lm = lm.In(timeext.TimezoneBerlin)
|
||||
|
||||
sha := cryptext.BytesSha256(bin)
|
||||
|
||||
@ -75,35 +85,33 @@ func (app *Application) getRemoteETag() (string, time.Time, error) {
|
||||
|
||||
req.SetBasicAuth(app.config.WebDAVUser, app.config.WebDAVPass)
|
||||
|
||||
t0 := time.Now()
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Starting WebDAV HEAD-request..."))
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", time.Time{}, exerr.Wrap(err, "Failed to download remote database").Build()
|
||||
}
|
||||
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Finished WebDAV request in %s", time.Since(t0)))
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", time.Time{}, exerr.New(exerr.TypeInternal, "Failed to download remote database").Int("sc", resp.StatusCode).Build()
|
||||
}
|
||||
|
||||
etag := resp.Header.Get("ETag")
|
||||
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")
|
||||
|
||||
lm, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
||||
etag, lm, err := app.parseHeader(resp)
|
||||
if err != nil {
|
||||
return "", time.Time{}, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||
return "", time.Time{}, exerr.Wrap(err, "").Build()
|
||||
}
|
||||
|
||||
lm = lm.In(timeext.TimezoneBerlin)
|
||||
|
||||
return etag, lm, nil
|
||||
}
|
||||
|
||||
func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time, string, int64, error) {
|
||||
|
||||
prevTT := app.currSysTrayTooltop
|
||||
defer app.setTrayTooltip(prevTT)
|
||||
|
||||
client := http.Client{Timeout: 90 * time.Second}
|
||||
|
||||
req, err := http.NewRequest("PUT", app.config.WebDAVURL, nil)
|
||||
@ -111,6 +119,8 @@ func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time,
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "").Build()
|
||||
}
|
||||
|
||||
req.SetBasicAuth(app.config.WebDAVUser, app.config.WebDAVPass)
|
||||
|
||||
if etagIfMatch != nil {
|
||||
req.Header.Set("If-Match", "\""+*etagIfMatch+"\"")
|
||||
}
|
||||
@ -124,7 +134,20 @@ func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time,
|
||||
|
||||
sz := int64(len(bin))
|
||||
|
||||
currTT := ""
|
||||
progressCallback := func(current int64, total int64) {
|
||||
newTT := fmt.Sprintf("Uploading (%.0f%%)", float64(current)/float64(total)*100)
|
||||
if currTT != newTT {
|
||||
app.setTrayTooltip(newTT)
|
||||
currTT = newTT
|
||||
}
|
||||
}
|
||||
|
||||
req.ContentLength = sz
|
||||
req.Body = NewProgressReader(bytes.NewReader(bin), int64(len(bin)), progressCallback)
|
||||
|
||||
t0 := time.Now()
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Starting WebDAV upload..."))
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@ -132,20 +155,14 @@ func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time,
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
app.LogDebug(fmt.Sprintf("{HTTP} Finished WebDAV upload in %s", time.Since(t0)))
|
||||
|
||||
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)
|
||||
etag, lm, err := app.parseHeader(resp)
|
||||
if err != nil {
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||
return "", time.Time{}, "", 0, exerr.Wrap(err, "").Build()
|
||||
}
|
||||
lm = lm.In(timeext.TimezoneBerlin)
|
||||
|
||||
return etag, lm, sha, sz, nil
|
||||
}
|
||||
@ -154,5 +171,31 @@ func (app *Application) uploadDatabase(etagIfMatch *string) (string, time.Time,
|
||||
return "", time.Time{}, "", 0, ETagConflictError
|
||||
}
|
||||
|
||||
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, "Failed to upload remote database").Int("sc", resp.StatusCode).Build()
|
||||
return "", time.Time{}, "", 0, exerr.New(exerr.TypeInternal, fmt.Sprintf("Failed to upload remote database (statuscode: %d)", resp.StatusCode)).Int("sc", resp.StatusCode).Build()
|
||||
}
|
||||
|
||||
func (app *Application) parseHeader(resp *http.Response) (string, time.Time, error) {
|
||||
var err error
|
||||
|
||||
etag := resp.Header.Get("ETag")
|
||||
if etag == "" {
|
||||
return "", time.Time{}, exerr.New(exerr.TypeInternal, "ETag header is missing").Build()
|
||||
}
|
||||
etag = strings.Trim(etag, "\"\r\n ")
|
||||
|
||||
var lm time.Time
|
||||
|
||||
lmStr := resp.Header.Get("Last-Modified")
|
||||
if lmStr == "" {
|
||||
lm = time.Now().In(timeext.TimezoneBerlin)
|
||||
app.LogDebug("Last-Modified header is missing, using current time as fallback")
|
||||
} else {
|
||||
lm, err = time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmStr)
|
||||
if err != nil {
|
||||
return "", time.Time{}, exerr.Wrap(err, "Failed to parse Last-Modified header").Build()
|
||||
}
|
||||
lm = lm.In(timeext.TimezoneBerlin)
|
||||
}
|
||||
|
||||
return etag, lm, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user