mirror of
https://git.sr.ht/~sircmpwn/tokidoki
synced 2025-12-12 14:17:21 +01:00
Compare commits
5 commits
7969df1a38
...
c6fdff75d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6fdff75d0 | ||
|
|
665b206709 | ||
|
|
fe0a0d0d00 | ||
|
|
7e03188c5a | ||
|
|
6eeea854be |
8 changed files with 218 additions and 81 deletions
12
Makefile
Normal file
12
Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
build:
|
||||
go build ./cmd/tokidoki
|
||||
|
||||
debug:
|
||||
go build -tags "pam nullauth" ./cmd/tokidoki
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
|
@ -207,7 +207,10 @@ func main() {
|
|||
for sig := range sigCh {
|
||||
switch sig {
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
server.Shutdown(context.Background())
|
||||
err := server.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Shutdown() error")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
go.mod
18
go.mod
|
|
@ -3,22 +3,22 @@ module git.sr.ht/~sircmpwn/tokidoki
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088
|
||||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51
|
||||
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01
|
||||
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.4
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/msteinert/pam/v2 v2.0.0
|
||||
github.com/rs/zerolog v1.32.0
|
||||
golang.org/x/crypto v0.18.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/emersion/go-message v0.18.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/teambition/rrule-go v1.8.2 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
)
|
||||
|
|
|
|||
37
go.sum
37
go.sum
|
|
@ -1,20 +1,21 @@
|
|||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088 h1:KuPliLD8CQM1WbCHdjHR6mhadIzLaAJCNENmvB1y9gs=
|
||||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240217160856-2e0d6e20b088/go.mod h1:VHj0jSCLIkrfEwmOvJ4+ykpoVbD/YLN7BM523oKKBHc=
|
||||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51 h1:iz8Tm7obSouGC0atCd+NtFSmCgfxDizXD1Rm+0Jw75w=
|
||||
git.sr.ht/~emersion/go-oauth2 v0.0.0-20240226120011-78f10ffd1d51/go.mod h1:VHj0jSCLIkrfEwmOvJ4+ykpoVbD/YLN7BM523oKKBHc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 h1:kHoSgklT8weIDl6R6xFpBJ5IioRdBU1v2X2aCZRVCcM=
|
||||
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01 h1:dq/06hDbCT+/DpbKWSrfrTeiJW97ION78N6J6Mktp2w=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.2.0.20240417100641-a587a14d3f01/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.4 h1:BS7+kUVhe/jfuFWgn8li0AbCKBIDoNvqJWsRJppltcc=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.4/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk=
|
||||
github.com/emersion/go-message v0.18.1 h1:tfTxIoXFSFRwWaZsgnqS1DSZuGpYGzSmCZD8SK3QA2E=
|
||||
github.com/emersion/go-message v0.18.1/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA=
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 h1:ATgqloALX6cHCranzkLb8/zjivwQ9DWWDCQRnxTPfaA=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2 h1:k/NO/RfeXFuKGcpHDkspYoE8u6tWoHs03tH5DXg22To=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135 h1:Ssk00uh7jhctJ23eclGxhhGqplSQB+wCt6fmbjhnOS8=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
|
|
@ -26,15 +27,15 @@ github.com/msteinert/pam/v2 v2.0.0 h1:jnObb8MT6jvMbmrUQO5J/puTUjxy7Av+55zVJRJsCy
|
|||
github.com/msteinert/pam/v2 v2.0.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
|
||||
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
|
@ -53,12 +54,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
|
|
|||
58
storage/etag.go
Normal file
58
storage/etag.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ETagWriter struct {
|
||||
io.Writer
|
||||
output io.Writer
|
||||
csum hash.Hash
|
||||
}
|
||||
|
||||
// NewETagWriter creates a new io.Writer that pipes writes to the provided
|
||||
// output while also calculating the contents ETag.
|
||||
func NewETagWriter(output io.Writer) *ETagWriter {
|
||||
csum := sha1.New()
|
||||
return &ETagWriter{
|
||||
output: io.MultiWriter(output, csum),
|
||||
csum: csum,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ETagWriter) Write(p []byte) (n int, err error) {
|
||||
return e.output.Write(p)
|
||||
}
|
||||
|
||||
func (e *ETagWriter) ETag() string {
|
||||
csum := e.csum.Sum(nil)
|
||||
return base64.StdEncoding.EncodeToString(csum[:])
|
||||
}
|
||||
|
||||
type ETagReader struct {
|
||||
io.Reader
|
||||
input io.Reader
|
||||
csum hash.Hash
|
||||
}
|
||||
|
||||
// NewETagReader creates a new io.Reader that pipes reads from the provided
|
||||
// input while also calculating the contents ETag.
|
||||
func NewETagReader(input io.Reader) *ETagReader {
|
||||
csum := sha1.New()
|
||||
return &ETagReader{
|
||||
input: io.TeeReader(input, csum),
|
||||
csum: csum,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ETagReader) Read(p []byte) (n int, err error) {
|
||||
return e.input.Read(p)
|
||||
}
|
||||
|
||||
func (e *ETagReader) ETag() string {
|
||||
csum := e.csum.Sum(nil)
|
||||
return base64.StdEncoding.EncodeToString(csum[:])
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
|
@ -10,6 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
|
|
@ -20,9 +19,13 @@ import (
|
|||
|
||||
type filesystemBackend struct {
|
||||
webdav.UserPrincipalBackend
|
||||
|
||||
path string
|
||||
caldavPrefix string
|
||||
carddavPrefix string
|
||||
|
||||
// maps file path to *sync.RWMutex
|
||||
locks sync.Map
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -47,6 +50,36 @@ func NewFilesystem(fsPath, caldavPrefix, carddavPrefix string, userPrincipalBack
|
|||
return backend, backend, nil
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) RLock(filename string) {
|
||||
lock_, _ := b.locks.LoadOrStore(filename, &sync.RWMutex{})
|
||||
lock := lock_.(*sync.RWMutex)
|
||||
lock.RLock()
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) RUnlock(filename string) {
|
||||
lock_, ok := b.locks.Load(filename)
|
||||
if !ok {
|
||||
panic("attempt to unlock non-existing lock")
|
||||
}
|
||||
lock := lock_.(*sync.RWMutex)
|
||||
lock.RUnlock()
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) Lock(filename string) {
|
||||
lock_, _ := b.locks.LoadOrStore(filename, &sync.RWMutex{})
|
||||
lock := lock_.(*sync.RWMutex)
|
||||
lock.Lock()
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) Unlock(filename string) {
|
||||
lock_, ok := b.locks.Load(filename)
|
||||
if !ok {
|
||||
panic("attempt to unlock non-existing lock")
|
||||
}
|
||||
lock := lock_.(*sync.RWMutex)
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func ensureLocalDir(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(path, 0755)
|
||||
|
|
@ -109,11 +142,10 @@ func etagForFile(path string) (string, error) {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha1.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
src := NewETagReader(f)
|
||||
if _, err := io.ReadAll(src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
csum := h.Sum(nil)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(csum[:]), nil
|
||||
return src.ETag(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,22 +47,23 @@ func (b *filesystemBackend) safeLocalCalDAVPath(ctx context.Context, urlPath str
|
|||
return b.safeLocalPath(homeSetPath, urlPath)
|
||||
}
|
||||
|
||||
func calendarFromFile(path string, propFilter []string) (*ical.Calendar, error) {
|
||||
func calendarAndEtagFromFile(path string, propFilter []string) (*ical.Calendar, string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := ical.NewDecoder(f)
|
||||
src := NewETagReader(f)
|
||||
dec := ical.NewDecoder(src)
|
||||
cal, err := dec.Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return cal, nil
|
||||
// TODO implement
|
||||
//return icalPropFilter(cal, propFilter), nil
|
||||
return cal, src.ETag(), nil
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath string, propFilter []string) ([]caldav.CalendarObject, error) {
|
||||
|
|
@ -85,17 +86,15 @@ func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath
|
|||
return nil
|
||||
}
|
||||
|
||||
cal, err := calendarFromFile(filename, propFilter)
|
||||
b.RLock(filename)
|
||||
defer b.RUnlock(filename)
|
||||
|
||||
cal, etag, err := calendarAndEtagFromFile(filename, propFilter)
|
||||
if err != nil {
|
||||
fmt.Printf("load calendar error for %s: %v\n", filename, err)
|
||||
return err
|
||||
}
|
||||
|
||||
etag, err := etagForFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO can this potentially be called on a calendar object resource?
|
||||
// Would work (as Walk() includes root), except for the path construction below
|
||||
obj := caldav.CalendarObject{
|
||||
|
|
@ -105,6 +104,7 @@ func (b *filesystemBackend) loadAllCalendarObjects(ctx context.Context, urlPath
|
|||
ETag: etag,
|
||||
Data: cal,
|
||||
}
|
||||
log.Debug().Str("path", obj.Path).Str("etag", etag).Int64("size", info.Size()).Msg("calendar object loaded")
|
||||
result = append(result, obj)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -138,7 +138,12 @@ func (b *filesystemBackend) createDefaultCalendar(ctx context.Context) (*caldav.
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating default calendar: %s", err.Error())
|
||||
}
|
||||
err = os.WriteFile(path.Join(localPath, calendarFileName), blob, 0644)
|
||||
|
||||
filename := path.Join(localPath, calendarFileName)
|
||||
b.Lock(filename)
|
||||
defer b.Unlock(filename)
|
||||
|
||||
err = os.WriteFile(filename, blob, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error writing default calendar: %s", err.Error())
|
||||
}
|
||||
|
|
@ -167,6 +172,10 @@ func (b *filesystemBackend) ListCalendars(ctx context.Context) ([]caldav.Calenda
|
|||
}
|
||||
|
||||
calPath := path.Join(filename, calendarFileName)
|
||||
|
||||
b.RLock(calPath)
|
||||
defer b.RUnlock(calPath)
|
||||
|
||||
data, err := os.ReadFile(calPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -209,6 +218,9 @@ func (b *filesystemBackend) GetCalendar(ctx context.Context, urlPath string) (*c
|
|||
|
||||
log.Debug().Str("path", localPath).Msg("loading calendar")
|
||||
|
||||
b.RLock(localPath)
|
||||
defer b.RUnlock(localPath)
|
||||
|
||||
data, err := os.ReadFile(localPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -237,6 +249,9 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b.RLock(localPath)
|
||||
defer b.RUnlock(localPath)
|
||||
|
||||
info, err := os.Stat(localPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
|
|
@ -250,17 +265,12 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin
|
|||
propFilter = req.Props
|
||||
}
|
||||
|
||||
calendar, err := calendarFromFile(localPath, propFilter)
|
||||
calendar, etag, err := calendarAndEtagFromFile(localPath, propFilter)
|
||||
if err != nil {
|
||||
log.Debug().Str("path", localPath).Err(err).Msg("error reading calendar")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
etag, err := etagForFile(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj := caldav.CalendarObject{
|
||||
Path: objPath,
|
||||
ModTime: info.ModTime(),
|
||||
|
|
@ -268,6 +278,7 @@ func (b *filesystemBackend) GetCalendarObject(ctx context.Context, objPath strin
|
|||
ETag: etag,
|
||||
Data: calendar,
|
||||
}
|
||||
log.Debug().Str("path", objPath).Str("etag", etag).Int64("size", info.Size()).Msg("returning calendar object")
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
|
|
@ -320,6 +331,9 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b.Lock(localPath)
|
||||
defer b.Unlock(localPath)
|
||||
|
||||
flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
|
||||
// TODO handle IfNoneMatch == ETag
|
||||
if opts.IfNoneMatch.IsWildcard() {
|
||||
|
|
@ -352,16 +366,13 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := ical.NewEncoder(f)
|
||||
out := NewETagWriter(f)
|
||||
enc := ical.NewEncoder(out)
|
||||
err = enc.Encode(calendar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
etag, err := etagForFile(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -371,9 +382,10 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin
|
|||
Path: objPath,
|
||||
ModTime: info.ModTime(),
|
||||
ContentLength: info.Size(),
|
||||
ETag: etag,
|
||||
ETag: out.ETag(),
|
||||
Data: calendar,
|
||||
}
|
||||
log.Debug().Str("path", r.Path).Str("etag", r.ETag).Int64("size", info.Size()).Msg("calendar object updated")
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
|
|
@ -384,6 +396,10 @@ func (b *filesystemBackend) DeleteCalendarObject(ctx context.Context, path strin
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Lock(localPath)
|
||||
defer b.Unlock(localPath)
|
||||
|
||||
err = os.Remove(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -68,20 +68,21 @@ func vcardPropFilter(card vcard.Card, props []string) vcard.Card {
|
|||
return result
|
||||
}
|
||||
|
||||
func vcardFromFile(path string, propFilter []string) (vcard.Card, error) {
|
||||
func vcardAndEtagFromFile(path string, propFilter []string) (vcard.Card, string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := vcard.NewDecoder(f)
|
||||
src := NewETagReader(f)
|
||||
dec := vcard.NewDecoder(src)
|
||||
card, err := dec.Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return vcardPropFilter(card, propFilter), nil
|
||||
return vcardPropFilter(card, propFilter), src.ETag(), nil
|
||||
}
|
||||
|
||||
func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath string, propFilter []string) ([]carddav.AddressObject, error) {
|
||||
|
|
@ -103,12 +104,10 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s
|
|||
return nil
|
||||
}
|
||||
|
||||
card, err := vcardFromFile(filename, propFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.RLock(filename)
|
||||
defer b.RUnlock(filename)
|
||||
|
||||
etag, err := etagForFile(filename)
|
||||
card, etag, err := vcardAndEtagFromFile(filename, propFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -122,6 +121,7 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s
|
|||
ETag: etag,
|
||||
Card: card,
|
||||
}
|
||||
log.Debug().Str("path", obj.Path).Str("etag", etag).Int64("size", info.Size()).Msg("address object loaded")
|
||||
result = append(result, obj)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -141,7 +141,12 @@ func (b *filesystemBackend) writeAddressBook(ctx context.Context, ab *carddav.Ad
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path.Join(localPath, addressBookFileName), blob, 0644)
|
||||
|
||||
filename := path.Join(localPath, addressBookFileName)
|
||||
b.Lock(filename)
|
||||
defer b.Unlock(filename)
|
||||
|
||||
err = os.WriteFile(filename, blob, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing address book: %s", err.Error())
|
||||
}
|
||||
|
|
@ -208,6 +213,9 @@ func (b *filesystemBackend) ListAddressBooks(ctx context.Context) ([]carddav.Add
|
|||
}
|
||||
|
||||
abPath := path.Join(filename, addressBookFileName)
|
||||
b.RLock(abPath)
|
||||
defer b.RUnlock(abPath)
|
||||
|
||||
data, err := os.ReadFile(abPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -250,6 +258,9 @@ func (b *filesystemBackend) GetAddressBook(ctx context.Context, urlPath string)
|
|||
|
||||
log.Debug().Str("path", localPath).Msg("loading addressbook")
|
||||
|
||||
b.RLock(localPath)
|
||||
defer b.RUnlock(localPath)
|
||||
|
||||
data, err := os.ReadFile(localPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -303,6 +314,9 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b.RLock(localPath)
|
||||
defer b.RUnlock(localPath)
|
||||
|
||||
info, err := os.Stat(localPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
|
|
@ -316,17 +330,12 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string
|
|||
propFilter = req.Props
|
||||
}
|
||||
|
||||
card, err := vcardFromFile(localPath, propFilter)
|
||||
card, etag, err := vcardAndEtagFromFile(localPath, propFilter)
|
||||
if err != nil {
|
||||
log.Debug().Str("path", localPath).Err(err).Msg("error reading calendar")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
etag, err := etagForFile(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj := carddav.AddressObject{
|
||||
Path: objPath,
|
||||
ModTime: info.ModTime(),
|
||||
|
|
@ -334,6 +343,7 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string
|
|||
ETag: etag,
|
||||
Card: card,
|
||||
}
|
||||
log.Debug().Str("path", objPath).Str("etag", etag).Int64("size", info.Size()).Msg("returning address object")
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
|
|
@ -381,6 +391,9 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b.Lock(localPath)
|
||||
defer b.Unlock(localPath)
|
||||
|
||||
flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
|
||||
// TODO handle IfNoneMatch == ETag
|
||||
if opts.IfNoneMatch.IsWildcard() {
|
||||
|
|
@ -413,15 +426,12 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := vcard.NewEncoder(f)
|
||||
out := NewETagWriter(f)
|
||||
enc := vcard.NewEncoder(out)
|
||||
if err := enc.Encode(card); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
etag, err := etagForFile(localPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -431,9 +441,10 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string
|
|||
Path: objPath,
|
||||
ModTime: info.ModTime(),
|
||||
ContentLength: info.Size(),
|
||||
ETag: etag,
|
||||
ETag: out.ETag(),
|
||||
Card: card,
|
||||
}
|
||||
log.Debug().Str("path", r.Path).Str("etag", r.ETag).Int64("size", info.Size()).Msg("address object updated")
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
|
|
@ -444,6 +455,10 @@ func (b *filesystemBackend) DeleteAddressObject(ctx context.Context, path string
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Lock(localPath)
|
||||
defer b.Unlock(localPath)
|
||||
|
||||
err = os.Remove(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue