Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(3111)

Delta Between Two Patch Sets: webdav/lock.go

Issue 175140043: code review 175140043: x/net/webdav: part 1 of a memLS implementation.
Left Patch Set: Created 9 years, 4 months ago
Right Patch Set: diff -r 870131cdf8c51ae63ca232ba9a9e727403954629 https://code.google.com/p/go.net Created 9 years, 4 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | webdav/lock_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
(no file at all)
1 // Copyright 2014 The Go Authors. All rights reserved. 1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 package webdav 5 package webdav
6 6
7 import ( 7 import (
8 "errors" 8 "errors"
9 » "io" 9 » "path"
10 » "strconv"
11 » "strings"
12 » "sync"
10 "time" 13 "time"
11 ) 14 )
12 15
13 var ( 16 var (
17 // ErrConfirmationFailed is returned by a LockSystem's Confirm method.
14 ErrConfirmationFailed = errors.New("webdav: confirmation failed") 18 ErrConfirmationFailed = errors.New("webdav: confirmation failed")
15 » ErrForbidden = errors.New("webdav: forbidden") 19 » // ErrForbidden is returned by a LockSystem's Unlock method.
16 » ErrNoSuchLock = errors.New("webdav: no such lock") 20 » ErrForbidden = errors.New("webdav: forbidden")
21 » // ErrLocked is returned by a LockSystem's Create, Refresh and Unlock me thods.
22 » ErrLocked = errors.New("webdav: locked")
23 » // ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock method s.
24 » ErrNoSuchLock = errors.New("webdav: no such lock")
17 ) 25 )
18 26
19 // Condition can match a WebDAV resource, based on a token or ETag. 27 // Condition can match a WebDAV resource, based on a token or ETag.
20 // Exactly one of Token and ETag should be non-empty. 28 // Exactly one of Token and ETag should be non-empty.
21 type Condition struct { 29 type Condition struct {
22 Not bool 30 Not bool
23 Token string 31 Token string
24 ETag string 32 ETag string
25 } 33 }
26 34
35 // Releaser releases previously confirmed lock claims.
36 //
37 // Calling Release does not unlock the lock, in the WebDAV UNLOCK sense, but
38 // once LockSystem.Confirm has confirmed that a lock claim is valid, that lock
39 // cannot be Confirmed again until it has been Released.
40 type Releaser interface {
41 Release()
42 }
43
44 // LockSystem manages access to a collection of named resources. The elements
45 // in a lock name are separated by slash ('/', U+002F) characters, regardless
46 // of host operating system convention.
27 type LockSystem interface { 47 type LockSystem interface {
28 » // TODO: comment that the conditions should be ANDed together. 48 » // Confirm confirms that the caller can claim all of the locks specified by
29 » Confirm(path string, conditions ...Condition) (c io.Closer, err error) 49 » // the given conditions, and that holding the union of all of those lock s
30 » // TODO: comment that token should be an absolute URI as defined by RFC 3986, 50 » // gives exclusive access to the named resource.
31 » // Section 4.3. In particular, it should not contain whitespace. 51 » //
32 » Create(path string, now time.Time, ld LockDetails) (token string, c io.C loser, err error) 52 » // Exactly one of r and err will be non-nil. If r is non-nil, all of the
33 » Refresh(token string, now time.Time, duration time.Duration) (ld LockDet ails, c io.Closer, err error) 53 » // requested locks are held until r.Release is called.
34 » Unlock(token string) error 54 » //
35 } 55 » // If Confirm returns ErrConfirmationFailed then the Handler will contin ue
36 56 » // to try any other set of locks presented (a WebDAV HTTP request can
57 » // present more than one set of locks). If it returns any other non-nil
58 » // error, the Handler will write a "500 Internal Server Error" HTTP stat us.
59 » Confirm(now time.Time, name string, conditions ...Condition) (r Releaser , err error)
60
61 » // Create creates a lock with the given depth, duration, owner and root
62 » // (name). The depth will either be negative (meaning infinite) or zero.
63 » //
64 » // If Create returns ErrLocked then the Handler will write a "423 Locked "
65 » // HTTP status. If it returns any other non-nil error, the Handler will
66 » // write a "500 Internal Server Error" HTTP status.
67 » //
68 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
69 » // when to use each error.
70 » //
71 » // The token returned identifies the created lock. It should be an absol ute
72 » // URI as defined by RFC 3986, Section 4.3. In particular, it should not
73 » // contain whitespace.
74 » Create(now time.Time, details LockDetails) (token string, err error)
75
76 » // Refresh refreshes the lock with the given token.
77 » //
78 » // If Refresh returns ErrLocked then the Handler will write a "423 Locke d"
79 » // HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will w rite
80 » // a "412 Precondition Failed" HTTP Status. If it returns any other non- nil
81 » // error, the Handler will write a "500 Internal Server Error" HTTP stat us.
82 » //
83 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
84 » // when to use each error.
85 » Refresh(now time.Time, token string, duration time.Duration) (details Lo ckDetails, err error)
86
87 » // Unlock unlocks the lock with the given token.
88 » //
89 » // If Unlock returns ErrForbidden then the Handler will write a "403
90 » // Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler
91 » // will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLoc k
92 » // then the Handler will write a "409 Conflict" HTTP Status. If it retur ns
93 » // any other non-nil error, the Handler will write a "500 Internal Serve r
94 » // Error" HTTP status.
95 » //
96 » // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for
97 » // when to use each error.
98 » Unlock(now time.Time, token string) error
99 }
100
101 // LockDetails are a lock's metadata.
37 type LockDetails struct { 102 type LockDetails struct {
38 » Depth int // Negative means infinite depth. 103 » // Depth is the lock depth. A negative means infinite.
39 » Duration time.Duration // Negative means unlimited duration. 104 » Depth int
40 » OwnerXML string // Verbatim XML. 105 » // Duration is the lock timeout. A negative duration means infinite.
41 » Path string 106 » Duration time.Duration
42 } 107 » // OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
rostdotio 2014/11/17 14:55:39 the verbatim XML in OwnerXML does not preserve XML
43 108 » OwnerXML string
44 // TODO: a MemLS implementation. 109 » // Root is the root resource name being locked. For a zero-depth lock, t he
110 » // root is the only resource being locked.
111 » Root string
112 }
113
114 // NewMemLS returns a new in-memory LockSystem.
115 func NewMemLS() LockSystem {
116 » return &memLS{
117 » » byName: make(map[string]*memLSNode),
118 » » byToken: make(map[string]*memLSNode),
119 » }
120 }
121
122 type memLS struct {
123 » mu sync.Mutex
124 » byName map[string]*memLSNode
125 » byToken map[string]*memLSNode
126 » gen uint64
127 }
128
129 func (m *memLS) nextToken() string {
130 » // TODO: use a better token generator.
131 » m.gen++
132 » return strconv.FormatUint(m.gen, 10)
133 }
134
135 func (m *memLS) collectExpiredNodes(now time.Time) {
136 » // TODO: implement.
137 }
138
139 func (m *memLS) Confirm(now time.Time, name string, conditions ...Condition) (Re leaser, error) {
140 » m.mu.Lock()
141 » defer m.mu.Unlock()
142 » m.collectExpiredNodes(now)
143 » name = path.Clean("/" + name)
144
145 » // TODO: touch n.held.
146 » panic("todo")
147 }
148
149 func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
150 » m.mu.Lock()
151 » defer m.mu.Unlock()
152 » m.collectExpiredNodes(now)
153 » name := path.Clean("/" + details.Root)
154
155 » if !m.canCreate(name, details.Depth) {
156 » » return "", ErrLocked
157 » }
158 » n := m.create(name)
159 » n.token = m.nextToken()
160 » m.byToken[n.token] = n
161 » n.details = details
162 » // TODO: set n.expiry.
163 » return n.token, nil
164 }
165
166 func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (Lo ckDetails, error) {
167 » m.mu.Lock()
168 » defer m.mu.Unlock()
169 » m.collectExpiredNodes(now)
170
171 » n := m.byToken[token]
172 » if n == nil {
173 » » return LockDetails{}, ErrNoSuchLock
174 » }
175 » if n.held {
176 » » return LockDetails{}, ErrLocked
177 » }
178 » n.details.Duration = duration
179 » // TODO: update n.expiry.
180 » return n.details, nil
181 }
182
183 func (m *memLS) Unlock(now time.Time, token string) error {
184 » m.mu.Lock()
185 » defer m.mu.Unlock()
186 » m.collectExpiredNodes(now)
187
188 » n := m.byToken[token]
189 » if n == nil {
190 » » return ErrNoSuchLock
191 » }
192 » if n.held {
193 » » return ErrLocked
194 » }
195 » m.remove(n)
196 » return nil
197 }
198
199 func (m *memLS) canCreate(name string, depth int) bool {
200 » return walkToRoot(name, func(name0 string, first bool) bool {
201 » » n := m.byName[name0]
202 » » if n == nil {
203 » » » return true
204 » » }
205 » » if first {
206 » » » if n.token != "" {
207 » » » » // The target node is already locked.
208 » » » » return false
209 » » » }
210 » » » if depth < 0 {
211 » » » » // The requested lock depth is infinite, and the fact that n exists
212 » » » » // (n != nil) means that a descendent of the tar get node is locked.
213 » » » » return false
214 » » » }
215 » » } else if n.token != "" && n.details.Depth < 0 {
216 » » » // An ancestor of the target node is locked with infinit e depth.
217 » » » return false
218 » » }
219 » » return true
220 » })
221 }
222
223 func (m *memLS) create(name string) (ret *memLSNode) {
224 » walkToRoot(name, func(name0 string, first bool) bool {
225 » » n := m.byName[name0]
226 » » if n == nil {
227 » » » n = &memLSNode{
228 » » » » details: LockDetails{
229 » » » » » Root: name0,
230 » » » » },
231 » » » }
232 » » » m.byName[name0] = n
233 » » }
234 » » n.refCount++
235 » » if first {
236 » » » ret = n
237 » » }
238 » » return true
239 » })
240 » return ret
241 }
242
243 func (m *memLS) remove(n *memLSNode) {
244 » delete(m.byToken, n.token)
245 » n.token = ""
246 » walkToRoot(n.details.Root, func(name0 string, first bool) bool {
247 » » x := m.byName[name0]
248 » » x.refCount--
249 » » if x.refCount == 0 {
250 » » » delete(m.byName, name0)
251 » » }
252 » » return true
253 » })
254 }
255
256 func walkToRoot(name string, f func(name0 string, first bool) bool) bool {
257 » for first := true; ; first = false {
258 » » if !f(name, first) {
259 » » » return false
260 » » }
261 » » if name == "/" {
262 » » » break
263 » » }
264 » » name = name[:strings.LastIndex(name, "/")]
265 » » if name == "" {
266 » » » name = "/"
267 » » }
268 » }
269 » return true
270 }
271
272 type memLSNode struct {
273 » // details are the lock metadata. Even if this node's name is not explic itly locked,
274 » // details.Root will still equal the node's name.
275 » details LockDetails
276 » // token is the unique identifier for this node's lock. An empty token m eans that
277 » // this node is not explicitly locked.
278 » token string
279 » // refCount is the number of self-or-descendent nodes that are explicitl y locked.
280 » refCount int
281 » // expiry is when this node's lock expires.
282 » expiry time.Time
283 » // held is whether this node's lock is actively held by a Confirm call.
284 » held bool
285 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b