|
|
|
|
|
|
|
|
|
package webdav |
|
|
|
import ( |
|
"container/heap" |
|
"errors" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"time" |
|
) |
|
|
|
var ( |
|
|
|
ErrConfirmationFailed = errors.New("webdav: confirmation failed") |
|
|
|
ErrForbidden = errors.New("webdav: forbidden") |
|
|
|
ErrLocked = errors.New("webdav: locked") |
|
|
|
ErrNoSuchLock = errors.New("webdav: no such lock") |
|
) |
|
|
|
|
|
|
|
type Condition struct { |
|
Not bool |
|
Token string |
|
ETag string |
|
} |
|
|
|
|
|
|
|
|
|
type LockSystem interface { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Create(now time.Time, details LockDetails) (token string, err error) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Unlock(now time.Time, token string) error |
|
} |
|
|
|
|
|
type LockDetails struct { |
|
|
|
|
|
Root string |
|
|
|
Duration time.Duration |
|
|
|
|
|
|
|
|
|
|
|
OwnerXML string |
|
|
|
|
|
ZeroDepth bool |
|
} |
|
|
|
|
|
func NewMemLS() LockSystem { |
|
return &memLS{ |
|
byName: make(map[string]*memLSNode), |
|
byToken: make(map[string]*memLSNode), |
|
gen: uint64(time.Now().Unix()), |
|
} |
|
} |
|
|
|
type memLS struct { |
|
mu sync.Mutex |
|
byName map[string]*memLSNode |
|
byToken map[string]*memLSNode |
|
gen uint64 |
|
|
|
|
|
byExpiry byExpiry |
|
} |
|
|
|
func (m *memLS) nextToken() string { |
|
m.gen++ |
|
return strconv.FormatUint(m.gen, 10) |
|
} |
|
|
|
func (m *memLS) collectExpiredNodes(now time.Time) { |
|
for len(m.byExpiry) > 0 { |
|
if now.Before(m.byExpiry[0].expiry) { |
|
break |
|
} |
|
m.remove(m.byExpiry[0]) |
|
} |
|
} |
|
|
|
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { |
|
m.mu.Lock() |
|
defer m.mu.Unlock() |
|
m.collectExpiredNodes(now) |
|
|
|
var n0, n1 *memLSNode |
|
if name0 != "" { |
|
if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil { |
|
return nil, ErrConfirmationFailed |
|
} |
|
} |
|
if name1 != "" { |
|
if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil { |
|
return nil, ErrConfirmationFailed |
|
} |
|
} |
|
|
|
|
|
if n1 == n0 { |
|
n1 = nil |
|
} |
|
|
|
if n0 != nil { |
|
m.hold(n0) |
|
} |
|
if n1 != nil { |
|
m.hold(n1) |
|
} |
|
return func() { |
|
m.mu.Lock() |
|
defer m.mu.Unlock() |
|
if n1 != nil { |
|
m.unhold(n1) |
|
} |
|
if n0 != nil { |
|
m.unhold(n0) |
|
} |
|
}, nil |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) { |
|
|
|
for _, c := range conditions { |
|
n = m.byToken[c.Token] |
|
if n == nil || n.held { |
|
continue |
|
} |
|
if name == n.details.Root { |
|
return n |
|
} |
|
if n.details.ZeroDepth { |
|
continue |
|
} |
|
if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") { |
|
return n |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (m *memLS) hold(n *memLSNode) { |
|
if n.held { |
|
panic("webdav: memLS inconsistent held state") |
|
} |
|
n.held = true |
|
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 { |
|
heap.Remove(&m.byExpiry, n.byExpiryIndex) |
|
} |
|
} |
|
|
|
func (m *memLS) unhold(n *memLSNode) { |
|
if !n.held { |
|
panic("webdav: memLS inconsistent held state") |
|
} |
|
n.held = false |
|
if n.details.Duration >= 0 { |
|
heap.Push(&m.byExpiry, n) |
|
} |
|
} |
|
|
|
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { |
|
m.mu.Lock() |
|
defer m.mu.Unlock() |
|
m.collectExpiredNodes(now) |
|
details.Root = slashClean(details.Root) |
|
|
|
if !m.canCreate(details.Root, details.ZeroDepth) { |
|
return "", ErrLocked |
|
} |
|
n := m.create(details.Root) |
|
n.token = m.nextToken() |
|
m.byToken[n.token] = n |
|
n.details = details |
|
if n.details.Duration >= 0 { |
|
n.expiry = now.Add(n.details.Duration) |
|
heap.Push(&m.byExpiry, n) |
|
} |
|
return n.token, nil |
|
} |
|
|
|
func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) { |
|
m.mu.Lock() |
|
defer m.mu.Unlock() |
|
m.collectExpiredNodes(now) |
|
|
|
n := m.byToken[token] |
|
if n == nil { |
|
return LockDetails{}, ErrNoSuchLock |
|
} |
|
if n.held { |
|
return LockDetails{}, ErrLocked |
|
} |
|
if n.byExpiryIndex >= 0 { |
|
heap.Remove(&m.byExpiry, n.byExpiryIndex) |
|
} |
|
n.details.Duration = duration |
|
if n.details.Duration >= 0 { |
|
n.expiry = now.Add(n.details.Duration) |
|
heap.Push(&m.byExpiry, n) |
|
} |
|
return n.details, nil |
|
} |
|
|
|
func (m *memLS) Unlock(now time.Time, token string) error { |
|
m.mu.Lock() |
|
defer m.mu.Unlock() |
|
m.collectExpiredNodes(now) |
|
|
|
n := m.byToken[token] |
|
if n == nil { |
|
return ErrNoSuchLock |
|
} |
|
if n.held { |
|
return ErrLocked |
|
} |
|
m.remove(n) |
|
return nil |
|
} |
|
|
|
func (m *memLS) canCreate(name string, zeroDepth bool) bool { |
|
return walkToRoot(name, func(name0 string, first bool) bool { |
|
n := m.byName[name0] |
|
if n == nil { |
|
return true |
|
} |
|
if first { |
|
if n.token != "" { |
|
|
|
return false |
|
} |
|
if !zeroDepth { |
|
|
|
|
|
return false |
|
} |
|
} else if n.token != "" && !n.details.ZeroDepth { |
|
|
|
return false |
|
} |
|
return true |
|
}) |
|
} |
|
|
|
func (m *memLS) create(name string) (ret *memLSNode) { |
|
walkToRoot(name, func(name0 string, first bool) bool { |
|
n := m.byName[name0] |
|
if n == nil { |
|
n = &memLSNode{ |
|
details: LockDetails{ |
|
Root: name0, |
|
}, |
|
byExpiryIndex: -1, |
|
} |
|
m.byName[name0] = n |
|
} |
|
n.refCount++ |
|
if first { |
|
ret = n |
|
} |
|
return true |
|
}) |
|
return ret |
|
} |
|
|
|
func (m *memLS) remove(n *memLSNode) { |
|
delete(m.byToken, n.token) |
|
n.token = "" |
|
walkToRoot(n.details.Root, func(name0 string, first bool) bool { |
|
x := m.byName[name0] |
|
x.refCount-- |
|
if x.refCount == 0 { |
|
delete(m.byName, name0) |
|
} |
|
return true |
|
}) |
|
if n.byExpiryIndex >= 0 { |
|
heap.Remove(&m.byExpiry, n.byExpiryIndex) |
|
} |
|
} |
|
|
|
func walkToRoot(name string, f func(name0 string, first bool) bool) bool { |
|
for first := true; ; first = false { |
|
if !f(name, first) { |
|
return false |
|
} |
|
if name == "/" { |
|
break |
|
} |
|
name = name[:strings.LastIndex(name, "/")] |
|
if name == "" { |
|
name = "/" |
|
} |
|
} |
|
return true |
|
} |
|
|
|
type memLSNode struct { |
|
|
|
|
|
details LockDetails |
|
|
|
|
|
token string |
|
|
|
refCount int |
|
|
|
expiry time.Time |
|
|
|
|
|
byExpiryIndex int |
|
|
|
held bool |
|
} |
|
|
|
type byExpiry []*memLSNode |
|
|
|
func (b *byExpiry) Len() int { |
|
return len(*b) |
|
} |
|
|
|
func (b *byExpiry) Less(i, j int) bool { |
|
return (*b)[i].expiry.Before((*b)[j].expiry) |
|
} |
|
|
|
func (b *byExpiry) Swap(i, j int) { |
|
(*b)[i], (*b)[j] = (*b)[j], (*b)[i] |
|
(*b)[i].byExpiryIndex = i |
|
(*b)[j].byExpiryIndex = j |
|
} |
|
|
|
func (b *byExpiry) Push(x interface{}) { |
|
n := x.(*memLSNode) |
|
n.byExpiryIndex = len(*b) |
|
*b = append(*b, n) |
|
} |
|
|
|
func (b *byExpiry) Pop() interface{} { |
|
i := len(*b) - 1 |
|
n := (*b)[i] |
|
(*b)[i] = nil |
|
n.byExpiryIndex = -1 |
|
*b = (*b)[:i] |
|
return n |
|
} |
|
|
|
const infiniteTimeout = -1 |
|
|
|
|
|
|
|
func parseTimeout(s string) (time.Duration, error) { |
|
if s == "" { |
|
return infiniteTimeout, nil |
|
} |
|
if i := strings.IndexByte(s, ','); i >= 0 { |
|
s = s[:i] |
|
} |
|
s = strings.TrimSpace(s) |
|
if s == "Infinite" { |
|
return infiniteTimeout, nil |
|
} |
|
const pre = "Second-" |
|
if !strings.HasPrefix(s, pre) { |
|
return 0, errInvalidTimeout |
|
} |
|
s = s[len(pre):] |
|
if s == "" || s[0] < '0' || '9' < s[0] { |
|
return 0, errInvalidTimeout |
|
} |
|
n, err := strconv.ParseInt(s, 10, 64) |
|
if err != nil || 1<<32-1 < n { |
|
return 0, errInvalidTimeout |
|
} |
|
return time.Duration(n) * time.Second, nil |
|
} |
|
|