-
Notifications
You must be signed in to change notification settings - Fork 0
/
dav.go
213 lines (191 loc) · 4.17 KB
/
dav.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package joint
import (
"io"
"io/fs"
"strings"
"sync"
"github.com/studio-b12/gowebdav"
)
type DavFileInfo = gowebdav.File
// davroot is global map of WebDAV servises root paths by services URLs.
// External links are external in all cases, so this object is singleton.
var davroot = map[string]string{}
var davmux sync.RWMutex
// GetDavRoot returns cached WebDAV root for given address.
func GetDavRoot(addr string) (root string, ok bool) {
davmux.RLock()
root, ok = davroot[addr]
davmux.RUnlock()
return
}
// SetDavRoot associates given WebDAV root with address without check up.
func SetDavRoot(addr, root string) {
davmux.Lock()
davroot[addr] = root
davmux.Unlock()
}
// FindDavRoot returns cached root for given address,
// or tries to find it from given path.
func FindDavRoot(addr, fpath string) (root string, ok bool) {
if root, ok = GetDavRoot(addr); ok {
return
}
fpath = "/" + fpath // check up empty root
var pos int
for {
var i = strings.IndexByte(fpath[pos:], '/')
if i != -1 {
root = fpath[:pos+i+1]
} else {
root = fpath
}
var client = gowebdav.NewClient(addr+root, "", "")
if fi, err := client.Stat(""); err == nil && fi.IsDir() {
SetDavRoot(addr, root)
ok = true
return
}
if i != -1 {
pos = i + 1
} else {
break
}
}
return
}
// DavJoint keeps gowebdav.Client object.
// Key is URL to service, address + service route,
// i.e. https://user:pass@example.com/webdav/.
type DavJoint struct {
client *gowebdav.Client
path string // truncated file path from full URL
files []fs.FileInfo
io.ReadCloser
pos int64
end int64
rdn int
}
func (j *DavJoint) Make(base Joint, urladdr string) (err error) {
j.client = gowebdav.NewClient(urladdr, "", "") // user & password gets from URL
err = j.client.Connect()
return
}
func (j *DavJoint) Cleanup() error {
var err1 error
if j.Busy() {
err1 = j.Close()
}
j.client = nil
return err1
}
func (j *DavJoint) Busy() bool {
return j.path != ""
}
func (j *DavJoint) Open(fpath string) (file fs.File, err error) {
if j.Busy() {
return nil, fs.ErrExist
}
j.path = fpath
j.files = nil // delete previous readdir result
j.rdn = 0 // start new sequence
return j, nil
}
func (j *DavJoint) Close() (err error) {
j.path = ""
if j.ReadCloser != nil {
err = j.ReadCloser.Close()
j.ReadCloser = nil
}
j.pos = 0
j.end = 0
return
}
func (j *DavJoint) Size() (int64, error) {
var fi, err = j.client.Stat(j.path)
if err != nil {
return 0, err
}
return fi.Size(), nil
}
func (j *DavJoint) ReadDir(n int) (list []fs.DirEntry, err error) {
if j.files == nil {
if j.files, err = j.client.ReadDir(j.path); err != nil {
return
}
}
if n < 0 {
n = len(j.files) - j.rdn
} else if n > len(j.files)-j.rdn {
n = len(j.files) - j.rdn
err = io.EOF
}
if n <= 0 { // on case all files readed or some deleted
return
}
list = make([]fs.DirEntry, n)
for i := 0; i < n; i++ {
list[i] = ToDirEntry(j.files[j.rdn+i])
}
j.rdn += n
return
}
func (j *DavJoint) Read(b []byte) (n int, err error) {
if j.ReadCloser == nil {
if j.ReadCloser, err = j.client.ReadStreamRange(j.path, j.pos, 0); err != nil {
return
}
}
n, err = j.ReadCloser.Read(b)
j.pos += int64(n)
return
}
func (j *DavJoint) Seek(offset int64, whence int) (abs int64, err error) {
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = j.pos + offset
case io.SeekEnd:
if j.end == 0 {
var fi fs.FileInfo
if fi, err = j.client.Stat(j.path); err != nil {
return
}
j.end = fi.Size()
}
abs = j.end + offset
default:
err = ErrFtpWhence
return
}
if abs < 0 {
err = ErrFtpNegPos
return
}
if abs != j.pos && j.ReadCloser != nil {
j.ReadCloser.Close()
j.ReadCloser = nil
}
j.pos = abs
return
}
func (j *DavJoint) ReadAt(b []byte, off int64) (n int, err error) {
if off < 0 {
err = ErrFtpNegPos
return
}
if off != j.pos && j.ReadCloser != nil {
j.ReadCloser.Close()
j.ReadCloser = nil
}
j.pos = off
return j.Read(b)
}
func (j *DavJoint) Stat() (fs.FileInfo, error) {
var fi, err = j.client.Stat(j.path)
return ToFileInfo(fi), err
}
func (j *DavJoint) Info(fpath string) (fs.FileInfo, error) {
var fi, err = j.client.Stat(fpath)
return ToFileInfo(fi), err
}