From e34c9759e4028db011b9c46b2e6c8e38d13ae1c7 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Tue, 28 Mar 2023 20:37:42 +0200 Subject: [PATCH] WebDAV: Replace studio-b12/gowebdav client with emersion/go-webdav #3310 Signed-off-by: Michael Mayer --- go.mod | 23 +- go.sum | 143 +------- internal/entity/service.go | 8 +- internal/remote/webdav/client.go | 335 ++++++++++++++++++ .../webdav/{webdav_test.go => client_test.go} | 143 +++++--- internal/remote/webdav/path.go | 18 + internal/remote/webdav/webdav.go | 234 ------------ internal/workers/share.go | 25 +- internal/workers/sync_download.go | 6 +- internal/workers/sync_refresh.go | 8 +- internal/workers/sync_upload.go | 18 +- pkg/fs/fileinfo.go | 48 ++- pkg/fs/name.go | 2 +- 13 files changed, 547 insertions(+), 464 deletions(-) create mode 100644 internal/remote/webdav/client.go rename internal/remote/webdav/{webdav_test.go => client_test.go} (53%) create mode 100644 internal/remote/webdav/path.go diff --git a/go.mod b/go.mod index 1216ee45d..8d820ef5d 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,6 @@ require ( github.com/sevlyar/go-daemon v0.1.6 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.2 - github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 github.com/tensorflow/tensorflow v1.15.2 github.com/tidwall/gjson v1.14.4 github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 @@ -63,7 +62,6 @@ require github.com/google/uuid v1.3.0 require ( github.com/chzyer/readline v1.5.1 // indirect github.com/zitadel/oidc v1.13.4 - golang.org/x/oauth2 v0.6.0 // indirect ) require github.com/gabriel-vasile/mimetype v1.4.2 @@ -76,8 +74,6 @@ require ( require github.com/go-ldap/ldap/v3 v3.4.5-0.20230210083308-d16fb563008d require ( - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/bytedance/sonic v1.8.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -99,19 +95,14 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/mandykoh/go-parallel v0.1.0 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.10 // indirect - golang.org/x/arch v0.2.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -120,4 +111,16 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) +require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/bytedance/sonic v1.8.1 // indirect + github.com/emersion/go-webdav v0.4.0 + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/ugorji/go/codec v1.2.10 // indirect + golang.org/x/arch v0.2.0 // indirect +) + go 1.17 diff --git a/go.sum b/go.sum index 601ace8b9..b150b1de8 100644 --- a/go.sum +++ b/go.sum @@ -6,31 +6,15 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= @@ -70,7 +54,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -119,9 +102,11 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgfNMULRxqZkadG1Vh44we3y5gJAtTBlVsx1BKQ= +github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= +github.com/emersion/go-webdav v0.4.0 h1:iIkgitJBUNu2c1vL0KqqRb5jDjs38bzM/H7WxewrIh4= +github.com/emersion/go-webdav v0.4.0/go.mod h1:lkPYZO/vsDNV9GPyVMBBsAUZzzxINL97bEVFykApo58= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= @@ -152,7 +137,6 @@ github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2H github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-ldap/ldap/v3 v3.4.5-0.20230210083308-d16fb563008d h1:zLvjSsMkzkYjkU/MOPn/OTlYsSzIdzN7jH/zcC/dn/I= @@ -194,25 +178,12 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -222,9 +193,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -232,18 +200,14 @@ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/open-location-code/go v0.0.0-20221010173056-817c0086479a h1:73RF0aJQNQ3GIIQTTbTKZr0wWHw2F1Winh0NzVJg/wA= github.com/google/open-location-code/go v0.0.0-20221010173056-817c0086479a/go.mod h1:eJfRN6aj+kR/rnua/rw9jAgYhqoMHldQkdTi+sePRKk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -389,8 +353,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyAOu7kJw6O/2jjG2UXLFoBzihqDU9Ofg9M= -github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= +github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU= github.com/tensorflow/tensorflow v1.15.2 h1:7/f/A664Tml/nRJg04+p3StcrsT53mkcvmxYHXI21Qo= github.com/tensorflow/tensorflow v1.15.2/go.mod h1:itOSERT4trABok4UOoG+X4BoKds9F3rIsySdn+Lvu90= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= @@ -412,24 +375,18 @@ github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8Q github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM= github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0= -github.com/zitadel/oidc v1.13.2 h1:DiwAgHtw1kKcngNzLjgv1/oRL1OjdYf3emEsFzQzraI= -github.com/zitadel/oidc v1.13.2/go.mod h1:GUywRhXAiTvvjdRdXblmGknDOeMwrsdMxTRCypi6974= github.com/zitadel/oidc v1.13.4 h1:+k2GKqP9Ld9S2MSFlj+KaNsoZ3J9oy+Ezw51EzSFuC8= github.com/zitadel/oidc v1.13.4/go.mod h1:3h2DhUcP02YV6q/CA/BG4yla0o6rXjK+DkJGK/dwJfw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= @@ -460,11 +417,8 @@ golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgn golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd h1:zkO/Lhoka23X63N9OSzpSeROEUQ5ODw47tM3YWjygbs= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -493,13 +447,11 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -517,23 +469,16 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -551,9 +496,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -562,8 +504,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -579,23 +519,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -646,7 +575,6 @@ golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -671,27 +599,11 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= @@ -718,20 +630,11 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -746,49 +649,17 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= @@ -817,8 +688,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/entity/service.go b/internal/entity/service.go index 67de9201e..8d28d1f1b 100644 --- a/internal/entity/service.go +++ b/internal/entity/service.go @@ -193,7 +193,11 @@ func (m *Service) Delete() error { // Directories returns a list of directories or albums in an account. func (m *Service) Directories() (result fs.FileInfos, err error) { if m.AccType == remote.ServiceWebDAV { - client := webdav.New(m.AccURL, m.AccUser, m.AccPass, webdav.Timeout(m.AccTimeout)) + var client *webdav.Client + if client, err = webdav.NewClient(m.AccURL, m.AccUser, m.AccPass, webdav.Timeout(m.AccTimeout)); err != nil { + return result, err + } + result, err = client.Directories("/", true, 0) } @@ -202,7 +206,7 @@ func (m *Service) Directories() (result fs.FileInfos, err error) { // Update error count and message. if err := m.LogError(err); err != nil { - log.Warnf("account: %s", err) + log.Warnf("service: %s", err) } return result, err diff --git a/internal/remote/webdav/client.go b/internal/remote/webdav/client.go new file mode 100644 index 000000000..9b1e9ab76 --- /dev/null +++ b/internal/remote/webdav/client.go @@ -0,0 +1,335 @@ +package webdav + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "runtime/debug" + "strings" + "time" + + "github.com/emersion/go-webdav" + + "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/fs" +) + +// Client represents a webdav client. +type Client struct { + client *webdav.Client + endpoint *url.URL + timeout time.Duration + mkdir map[string]bool +} + +// clientUrl returns the validated server url including username and password, if specified. +func clientUrl(serverUrl, user, pass string) (*url.URL, error) { + result, err := url.Parse(serverUrl) + + // Check url. + if err != nil { + return nil, err + } else if result == nil { + return nil, fmt.Errorf("invalid server url") + } + + // Set user and password if provided. + if user != "" { + result.User = url.UserPassword(user, pass) + } + + return result, nil +} + +// NewClient creates a new WebDAV client for the specified endpoint. +func NewClient(serverUrl, user, pass string, timeout Timeout) (*Client, error) { + // Create a new http.Client without timeout. + httpClient := &http.Client{} + + endpoint, err := clientUrl(serverUrl, user, pass) + + if err != nil { + return nil, err + } + + serverUrl = endpoint.String() + + log.Debugf("webdav: connecting to %s", clean.Log(serverUrl)) + + client, err := webdav.NewClient(httpClient, serverUrl) + + if err != nil { + return nil, err + } + + // Create a new webdav.Client wrapper. + result := &Client{ + client: client, + endpoint: endpoint, + timeout: Durations[timeout], + mkdir: make(map[string]bool, 128), + } + + return result, nil +} + +// withTimeout returns a *webdav.Client with specified total request time. +func (c *Client) withTimeout(timeout time.Duration) *webdav.Client { + if timeout < 0 { + return c.client + } else if timeout == 0 { + timeout = c.timeout + } + + // Create webdav client with the specified total request time. + client, err := webdav.NewClient(&http.Client{Timeout: timeout}, c.endpoint.String()) + + if err != nil { + return c.client + } + + return client +} + +// readDirWithTimeout returns the contents of the specified directory with a request time limit if timeout is not negative. +func (c *Client) readDirWithTimeout(dir string, recursive bool, timeout time.Duration) ([]webdav.FileInfo, error) { + dir = trimPath(dir) + return c.withTimeout(timeout).Readdir(dir, recursive) +} + +// readDirWithTimeout returns the contents of the specified directory without a request timeout. +func (c *Client) readDir(dir string, recursive bool) ([]webdav.FileInfo, error) { + return c.readDirWithTimeout(dir, recursive, -1) +} + +// Files returns information about files in a directory, optionally recursively. +func (c *Client) Files(dir string, recursive bool) (result fs.FileInfos, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("webdav: %s (panic while listing files)\nstack: %s", r, debug.Stack()) + } + }() + + dir = trimPath(dir) + + found, err := c.readDir(dir, recursive) + + if err != nil { + return result, err + } + + result = make(fs.FileInfos, 0, len(found)) + + for _, f := range found { + if f.IsDir || f.Path == "" || strings.HasPrefix(f.Path, ".") { + continue + } + + info := fs.WebFileInfo(f, c.endpoint.Path) + + result = append(result, info) + } + + return result, nil +} + +// Directories returns all subdirectories in a path as string slice. +func (c *Client) Directories(dir string, recursive bool, timeout time.Duration) (result fs.FileInfos, err error) { + dir = trimPath(dir) + + found, err := c.readDirWithTimeout(dir, recursive, timeout) + + if err != nil { + return result, err + } + + result = make(fs.FileInfos, 0, len(found)) + + for _, f := range found { + if !f.IsDir || f.Path == "" || strings.HasPrefix(f.Path, ".") { + continue + } + + info := fs.WebFileInfo(f, c.endpoint.Path) + + result = append(result, info) + } + + return result, err +} + +// MkdirAll recursively creates remote directories. +func (c *Client) MkdirAll(dir string) (err error) { + folders := splitPath(dir) + + if len(folders) == 0 { + return nil + } + + dir = "" + + for _, folder := range folders { + dir = path.Join(dir, folder) + err = c.client.Mkdir(dir) + } + + return err +} + +// Mkdir creates a single remote directory. +func (c *Client) Mkdir(dir string) error { + dir = trimPath(dir) + + if dir == "" || dir == "." || dir == ".." { + // Ignore. + return nil + } else if c.mkdir[dir] { + // Dir was already created. + return nil + } + + c.mkdir[dir] = true + + return c.client.Mkdir(dir) +} + +// Upload uploads a single file to the remote server. +func (c *Client) Upload(src, dest string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("webdav: %s (panic while uploading)\nstack: %s", r, debug.Stack()) + } + }() + + dest = trimPath(dest) + + if !fs.FileExists(src) { + return fmt.Errorf("file %s not found", clean.Log(path.Base(src))) + } + + f, err := os.OpenFile(src, os.O_RDONLY, 0) + + if err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed to read %s", clean.Log(path.Base(src))) + } + + defer f.Close() + + var writer io.WriteCloser + + writer, err = c.client.Create(dest) + + if err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed to write %s", clean.Log(dest)) + } + + defer writer.Close() + + if _, err = io.Copy(writer, f); err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed to upload %s", clean.Log(dest)) + } + + return nil +} + +// Download downloads a single file to the given location. +func (c *Client) Download(src, dest string, force bool) (err error) { + defer func() { + if r := recover(); r != nil { + log.Errorf("webdav: %s (panic)\nstack: %s", r, clean.Log(src)) + err = fmt.Errorf("webdav: unexpected error while downloading %s", clean.Log(src)) + } + }() + + src = trimPath(src) + + // Skip if file already exists. + if _, err := os.Stat(dest); err == nil && !force { + return fmt.Errorf("webdav: download skipped, %s already exists", clean.Log(dest)) + } + + dir := path.Dir(dest) + dirInfo, err := os.Stat(dir) + + if err != nil { + // Create local storage path. + if err := os.MkdirAll(dir, fs.ModeDir); err != nil { + return fmt.Errorf("webdav: cannot create folder %s (%s)", clean.Log(dir), err) + } + } else if !dirInfo.IsDir() { + return fmt.Errorf("webdav: %s is not a folder", clean.Log(dir)) + } + + var reader io.ReadCloser + + // Start download. + reader, err = c.client.Open(src) + + // Error? + if err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed to download %s", clean.Log(src)) + } + + defer reader.Close() + + f, err := os.OpenFile(dest, os.O_TRUNC|os.O_RDWR|os.O_CREATE, fs.ModeFile) + + if err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed to create %s", clean.Log(path.Base(dest))) + } + + defer f.Close() + + if _, err = f.ReadFrom(reader); err != nil { + log.Errorf("webdav: %s", clean.Log(err.Error())) + return fmt.Errorf("webdav: failed writing to %s", clean.Log(path.Base(dest))) + } + + return nil +} + +// DownloadDir downloads all files from a remote to a local directory. +func (c *Client) DownloadDir(src, dest string, recursive, force bool) (errs []error) { + src = trimPath(src) + + files, err := c.Files(src, recursive) + + if err != nil { + return append(errs, err) + } + + for _, file := range files { + dest := path.Join(dest, file.Abs) + + if _, err = os.Stat(dest); err == nil { + // File already exists. + msg := fmt.Errorf("webdav: %s already exists", clean.Log(dest)) + log.Warn(msg) + errs = append(errs, msg) + continue + } + + if err = c.Download(file.Abs, dest, force); err != nil { + // Failed to download file. + errs = append(errs, err) + log.Error(err) + continue + } + } + + return errs +} + +// Delete deletes a single file or directory on a remote server. +func (c *Client) Delete(dir string) error { + dir = trimPath(dir) + return c.client.RemoveAll(dir) +} diff --git a/internal/remote/webdav/webdav_test.go b/internal/remote/webdav/client_test.go similarity index 53% rename from internal/remote/webdav/webdav_test.go rename to internal/remote/webdav/client_test.go index 8164f7505..7aba2b9c1 100644 --- a/internal/remote/webdav/webdav_test.go +++ b/internal/remote/webdav/client_test.go @@ -17,34 +17,69 @@ const ( testPass = "photoprism" ) -func TestConnect(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutLow) - - assert.IsType(t, Client{}, c) -} - -func TestClient_Files(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutLow) - - assert.IsType(t, Client{}, c) - - files, err := c.Files("Photos") +func TestClientUrl(t *testing.T) { + result, err := clientUrl(testUrl, testUser, testPass) if err != nil { t.Fatal(err) } - if len(files) == 0 { - t.Fatal("no files found") + assert.Equal(t, "http://admin:photoprism@dummy-webdav/", result.String()) +} + +func TestNewClient(t *testing.T) { + c, err := NewClient(testUrl, testUser, testPass, TimeoutLow) + + if err != nil { + t.Fatal(err) } + + assert.IsType(t, &Client{}, c) +} + +func TestClient_Files(t *testing.T) { + c, err := NewClient(testUrl, testUser, testPass, TimeoutLow) + + if err != nil { + t.Fatal(err) + } + + assert.IsType(t, &Client{}, c) + + t.Run("NonRecursive", func(t *testing.T) { + files, err := c.Files("Photos", false) + + if err != nil { + t.Fatal(err) + } + + if len(files) == 0 { + t.Fatal("no files found") + } + }) + t.Run("Recursive", func(t *testing.T) { + files, err := c.Files("Photos", true) + + if err != nil { + t.Fatal(err) + } + + if len(files) == 0 { + t.Fatal("no files found") + } + }) } func TestClient_Directories(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutLow) + c, err := NewClient(testUrl, testUser, testPass, TimeoutLow) - assert.IsType(t, Client{}, c) + if err != nil { + t.Fatal(err) + } - t.Run("non-recursive", func(t *testing.T) { + assert.IsType(t, &Client{}, c) + + t.Run("NonRecursive", func(t *testing.T) { dirs, err := c.Directories("", false, MaxRequestDuration) if err != nil { @@ -61,8 +96,7 @@ func TestClient_Directories(t *testing.T) { assert.Equal(t, true, dirs[0].Dir) assert.Equal(t, int64(0), dirs[0].Size) }) - - t.Run("recursive", func(t *testing.T) { + t.Run("Recursive", func(t *testing.T) { dirs, err := c.Directories("", true, 0) if err != nil { @@ -75,12 +109,36 @@ func TestClient_Directories(t *testing.T) { }) } +func TestClient_UploadAndDelete(t *testing.T) { + c, err := NewClient(testUrl, testUser, testPass, TimeoutLow) + + if err != nil { + t.Fatal(err) + } + + assert.IsType(t, &Client{}, c) + + tempName := rnd.UUID() + fs.ExtJPEG + + if err := c.Upload(fs.Abs("testdata/example.jpg"), tempName); err != nil { + t.Fatal(err) + } + + if err := c.Delete(tempName); err != nil { + t.Fatal(err) + } +} + func TestClient_Download(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutDefault) + c, err := NewClient(testUrl, testUser, testPass, TimeoutDefault) - assert.IsType(t, Client{}, c) + if err != nil { + t.Fatal(err) + } - files, err := c.Files("Photos") + assert.IsType(t, &Client{}, c) + + files, err := c.Files("Photos", false) if err != nil { t.Fatal(err) @@ -107,47 +165,42 @@ func TestClient_Download(t *testing.T) { } func TestClient_DownloadDir(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutLow) + c, err := NewClient(testUrl, testUser, testPass, TimeoutLow) - assert.IsType(t, Client{}, c) + if err != nil { + t.Fatal(err) + } - t.Run("non-recursive", func(t *testing.T) { + assert.IsType(t, &Client{}, c) + + t.Run("NonRecursive", func(t *testing.T) { tempDir := filepath.Join(os.TempDir(), rnd.UUID()) + if err = os.RemoveAll(tempDir); err != nil { + t.Fatal(err) + } + if errs := c.DownloadDir("Photos", tempDir, false, false); len(errs) > 0 { t.Fatal(errs) } - if err := os.RemoveAll(tempDir); err != nil { + if err = os.RemoveAll(tempDir); err != nil { t.Fatal(err) } }) - - t.Run("recursive", func(t *testing.T) { + t.Run("Recursive", func(t *testing.T) { tempDir := filepath.Join(os.TempDir(), rnd.UUID()) + if err = os.RemoveAll(tempDir); err != nil { + t.Fatal(err) + } + if errs := c.DownloadDir("Photos", tempDir, true, false); len(errs) > 0 { t.Fatal(errs) } - if err := os.RemoveAll(tempDir); err != nil { + if err = os.RemoveAll(tempDir); err != nil { t.Fatal(err) } }) } - -func TestClient_UploadAndDelete(t *testing.T) { - c := New(testUrl, testUser, testPass, TimeoutLow) - - assert.IsType(t, Client{}, c) - - tempName := rnd.UUID() + fs.ExtJPEG - - if err := c.Upload(fs.Abs("testdata/example.jpg"), tempName); err != nil { - t.Fatal(err) - } - - if err := c.Delete(tempName); err != nil { - t.Fatal(err) - } -} diff --git a/internal/remote/webdav/path.go b/internal/remote/webdav/path.go new file mode 100644 index 000000000..23e4b9e93 --- /dev/null +++ b/internal/remote/webdav/path.go @@ -0,0 +1,18 @@ +package webdav + +import ( + "path" + "strings" +) + +func trimPath(dir string) string { + if dir = strings.Trim(path.Clean(dir), "/"); dir != "." && dir != ".." { + return dir + } + + return "" +} + +func splitPath(dir string) []string { + return strings.Split(trimPath(dir), "/") +} diff --git a/internal/remote/webdav/webdav.go b/internal/remote/webdav/webdav.go index 6c4f62878..3d16fa443 100644 --- a/internal/remote/webdav/webdav.go +++ b/internal/remote/webdav/webdav.go @@ -25,18 +25,9 @@ Additional information can be found in our Developer Guide: package webdav import ( - "bufio" - "fmt" - "os" - "path" - "runtime/debug" "time" - "github.com/studio-b12/gowebdav" - "github.com/photoprism/photoprism/internal/event" - "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/fs" ) // Global log instance. @@ -67,228 +58,3 @@ var Durations = map[Timeout]time.Duration{ TimeoutLow: 30 * Second, TimeoutNone: 0, } - -// Client represents a gowebdav.Client wrapper. -type Client struct { - client *gowebdav.Client - timeout Timeout -} - -// New creates a new WebDAV client. -func New(url, user, pass string, timeout Timeout) Client { - // Create a new gowebdav.Client instance. - client := gowebdav.NewClient(url, user, pass) - - // Create a new gowebdav.Client wrapper. - result := Client{ - client: client, - timeout: timeout, - } - - return result -} - -func (c Client) readDir(path string) ([]os.FileInfo, error) { - if path == "" { - path = "/" - } - - return c.client.ReadDir(path) -} - -// Files returns all files in a directory as string slice. -func (c Client) Files(dir string) (result fs.FileInfos, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("webdav: %s (panic while listing files)\nstack: %s", r, debug.Stack()) - } - }() - - files, err := c.readDir(dir) - - if err != nil { - return result, err - } - - for _, file := range files { - if !file.Mode().IsRegular() { - continue - } - - info := fs.NewFileInfo(file, dir) - - result = append(result, info) - } - - return result, nil -} - -// Directories returns all subdirectories in a path as string slice. -func (c Client) Directories(root string, recursive bool, timeout time.Duration) (result fs.FileInfos, err error) { - start := time.Now() - - if timeout == 0 { - timeout = Durations[c.timeout] - } - - result, err = c.fetchDirs(root, recursive, start, timeout) - - if time.Now().Sub(start) >= timeout { - log.Warnf("webdav: read dir timeout reached") - } - - return result, err -} - -// fetchDirs recursively fetches all directories until the timeout is reached. -func (c Client) fetchDirs(root string, recursive bool, start time.Time, timeout time.Duration) (result fs.FileInfos, err error) { - files, err := c.readDir(root) - - if err != nil { - return result, err - } - - if root == "/" { - root = "" - } - - for _, file := range files { - if !file.Mode().IsDir() { - continue - } - - info := fs.NewFileInfo(file, root) - - result = append(result, info) - - if recursive && (timeout < time.Second || time.Now().Sub(start) < timeout) { - subDirs, err := c.fetchDirs(info.Abs, true, start, timeout) - - if err != nil { - return result, err - } - - result = append(result, subDirs...) - } - } - - return result, nil -} - -// Download downloads a single file to the given location. -func (c Client) Download(from, to string, force bool) (err error) { - defer func() { - if r := recover(); r != nil { - log.Errorf("webdav: %s (panic)\nstack: %s", r, clean.Log(from)) - err = fmt.Errorf("webdav: unexpected error while downloading %s", clean.Log(from)) - } - }() - - // Skip if file already exists. - if _, err := os.Stat(to); err == nil && !force { - return fmt.Errorf("webdav: download skipped, %s already exists", clean.Log(to)) - } - - dir := path.Dir(to) - dirInfo, err := os.Stat(dir) - - if err != nil { - // Create local storage path. - if err := os.MkdirAll(dir, fs.ModeDir); err != nil { - return fmt.Errorf("webdav: cannot create folder %s (%s)", clean.Log(dir), err) - } - } else if !dirInfo.IsDir() { - return fmt.Errorf("webdav: %s is not a folder", clean.Log(dir)) - } - - var bytes []byte - - // Start download. - bytes, err = c.client.Read(from) - - // Error? - if err != nil { - log.Errorf("webdav: %s", clean.Log(err.Error())) - return fmt.Errorf("webdav: failed downloading %s", clean.Log(from)) - } - - // Write data to file and return. - return os.WriteFile(to, bytes, fs.ModeFile) -} - -// DownloadDir downloads all files from a remote to a local directory. -func (c Client) DownloadDir(from, to string, recursive, force bool) (errs []error) { - files, err := c.Files(from) - - if err != nil { - return append(errs, err) - } - - for _, file := range files { - dest := to + string(os.PathSeparator) + file.Abs - - if _, err = os.Stat(dest); err == nil { - // File already exists. - msg := fmt.Errorf("webdav: %s already exists", clean.Log(dest)) - log.Warn(msg) - errs = append(errs, msg) - continue - } - - if err = c.Download(file.Abs, dest, force); err != nil { - // Failed to download file. - errs = append(errs, err) - log.Error(err) - continue - } - } - - if !recursive { - return errs - } - - dirs, err := c.Directories(from, false, MaxRequestDuration) - - for _, dir := range dirs { - errs = append(errs, c.DownloadDir(dir.Abs, to, true, force)...) - } - - return errs -} - -// CreateDir recursively creates directories if they don't exist. -func (c Client) CreateDir(dir string) error { - if dir == "" || dir == "/" || dir == "." { - return nil - } - - return c.client.MkdirAll(dir, fs.ModeDir) -} - -// Upload uploads a single file to the remote server. -func (c Client) Upload(from, to string) (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("webdav: %s (panic while uploading)\nstack: %s", r, debug.Stack()) - } - }() - - file, err := os.Open(from) - - if err != nil { - return err - } - - defer file.Close() - - reader := bufio.NewReader(file) - - err = c.client.WriteStream(to, reader, fs.ModeFile) - - return err -} - -// Delete deletes a single file or directory on a remote server. -func (c Client) Delete(path string) error { - return c.client.Remove(path) -} diff --git a/internal/workers/share.go b/internal/workers/share.go index cd3dcf756..277b774bf 100644 --- a/internal/workers/share.go +++ b/internal/workers/share.go @@ -2,7 +2,7 @@ package workers import ( "fmt" - "path/filepath" + "path" "runtime/debug" "github.com/photoprism/photoprism/internal/config" @@ -89,22 +89,21 @@ func (w *Share) Start() (err error) { } } - client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) - existingDirs := make(map[string]string) + client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + + if err != nil { + return err + } for _, file := range files { if mutex.ShareWorker.Canceled() { return nil } - dir := filepath.Dir(file.RemoteName) + dir := path.Dir(file.RemoteName) - if _, ok := existingDirs[dir]; !ok { - if err := client.CreateDir(dir); err != nil { - log.Errorf("share: failed creating folder %s", dir) - continue - } - } + // Ensure destination folder exists. + _ = client.MkdirAll(dir) srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName) @@ -163,7 +162,11 @@ func (w *Share) Start() (err error) { continue } - client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + + if err != nil { + return err + } for _, file := range files { if mutex.ShareWorker.Canceled() { diff --git a/internal/workers/sync_download.go b/internal/workers/sync_download.go index 86aa057ea..408245110 100644 --- a/internal/workers/sync_download.go +++ b/internal/workers/sync_download.go @@ -76,7 +76,11 @@ func (w *Sync) download(a entity.Service) (complete bool, err error) { log.Infof("sync: downloading from %s", a.AccName) - client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + + if err != nil { + return false, err + } var baseDir string diff --git a/internal/workers/sync_refresh.go b/internal/workers/sync_refresh.go index 043424e05..9a23a17ef 100644 --- a/internal/workers/sync_refresh.go +++ b/internal/workers/sync_refresh.go @@ -14,7 +14,11 @@ func (w *Sync) refresh(a entity.Service) (complete bool, err error) { return false, nil } - client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + + if err != nil { + return false, err + } subDirs, err := client.Directories(a.SyncPath, true, webdav.MaxRequestDuration) @@ -30,7 +34,7 @@ func (w *Sync) refresh(a entity.Service) (complete bool, err error) { return false, nil } - files, err := client.Files(dir) + files, err := client.Files(dir, false) if err != nil { log.Error(err) diff --git a/internal/workers/sync_upload.go b/internal/workers/sync_upload.go index ba7ef9e8a..df9c0103b 100644 --- a/internal/workers/sync_upload.go +++ b/internal/workers/sync_upload.go @@ -2,7 +2,6 @@ package workers import ( "path" - "path/filepath" "time" "github.com/photoprism/photoprism/internal/entity" @@ -31,8 +30,11 @@ func (w *Sync) upload(a entity.Service) (complete bool, err error) { return true, nil } - client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) - existingDirs := make(map[string]string) + client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout)) + + if err != nil { + return false, err + } for _, file := range files { if mutex.SyncWorker.Canceled() { @@ -41,14 +43,10 @@ func (w *Sync) upload(a entity.Service) (complete bool, err error) { fileName := photoprism.FileName(file.FileRoot, file.FileName) remoteName := path.Join(a.SyncPath, file.FileName) - remoteDir := filepath.Dir(remoteName) + remoteDir := path.Dir(remoteName) - if _, ok := existingDirs[remoteDir]; !ok { - if err := client.CreateDir(remoteDir); err != nil { - log.Errorf("sync: failed creating remote folder %s", remoteDir) - continue // try again next time - } - } + // Ensure destination folder exists. + _ = client.MkdirAll(remoteDir) if err := client.Upload(fileName, remoteName); err != nil { w.logError(err) diff --git a/pkg/fs/fileinfo.go b/pkg/fs/fileinfo.go index 8bf416593..55e7315ad 100644 --- a/pkg/fs/fileinfo.go +++ b/pkg/fs/fileinfo.go @@ -2,11 +2,15 @@ package fs import ( "os" + "path" "path/filepath" "strings" "time" + + "github.com/emersion/go-webdav" ) +// FileInfo represents a file system entry. type FileInfo struct { Name string `json:"name"` Abs string `json:"abs"` @@ -15,25 +19,47 @@ type FileInfo struct { Dir bool `json:"dir"` } -func NewFileInfo(info os.FileInfo, dir string) FileInfo { - if dir != PathSeparator && len(dir) > 0 { - if dir[len(dir)-1:] == PathSeparator { +func fileDir(dir, sep string) string { + if dir != sep && len(dir) > 0 { + if dir[len(dir)-1:] == sep { dir = dir[:len(dir)-1] } - if dir[0:1] != PathSeparator { - dir = PathSeparator + dir + if dir[0:1] != sep { + dir = sep + dir } } else { - dir = PathSeparator + dir = sep } + return dir +} + +// NewFileInfo creates a FileInfo struct from the os.FileInfo record. +func NewFileInfo(file os.FileInfo, dir string) FileInfo { + dir = fileDir(dir, PathSeparator) + result := FileInfo{ - Name: info.Name(), - Abs: filepath.Join(dir, info.Name()), - Size: info.Size(), - Date: info.ModTime(), - Dir: info.IsDir(), + Name: file.Name(), + Abs: filepath.Join(dir, file.Name()), + Size: file.Size(), + Date: file.ModTime(), + Dir: file.IsDir(), + } + + return result +} + +// WebFileInfo creates a FileInfo struct from a webdav.FileInfo record. +func WebFileInfo(file webdav.FileInfo, dir string) FileInfo { + filePath := strings.Trim(file.Path, "/") + dir = strings.Trim(dir, "/") + result := FileInfo{ + Name: path.Base(filePath), + Abs: "/" + RelName(filePath, dir), + Size: file.Size, + Date: file.ModTime, + Dir: file.IsDir, } return result diff --git a/pkg/fs/name.go b/pkg/fs/name.go index c0eb40045..aa93dca44 100644 --- a/pkg/fs/name.go +++ b/pkg/fs/name.go @@ -7,7 +7,7 @@ import ( "strings" ) -// FileName returns the a relative filename with the same base and a given extension in a directory. +// FileName returns the relative filename with the same base and a given extension in a directory. func FileName(fileName, dirName, baseDir, fileExt string) string { fileDir := filepath.Dir(fileName)