-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpathelement.go
338 lines (283 loc) · 8.87 KB
/
pathelement.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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package whatnot
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/databeast/whatnot/mutex"
"github.com/pkg/errors"
)
// PathElement is an individual section of a complete path
type PathElement struct {
logsupport // construct in
// internal mutex for synchronizing modifications to this structure itself
mu *mutex.SmartMutex
// the individual name of this Path Element
section SubPath
// The parent Path Element
parent *PathElement
// sub Path-elements directly beneath this PathElement
children map[SubPath]*PathElement
// channel to notify parent element of changes to this element
// or any of its sub-elements
parentnotify chan elementChange
// channel for incoming change notifications from any of the
// children of this Path Elements
subevents chan elementChange
// channel for events directly on this element itself
selfnotify chan elementChange
// reslock is the mutex-like structure governing the leasable lock
// on the resources represented by this Path Element
reslock resourceLock
// additional keyval data attached to this pathelement
resval ElementValue
// Channel Multiplexer for sending watch events to subscriptions
// on this Path Element or any of its children
subscriberNotify *EventMultiplexer
// pruning support for shutting down unused areas of the namespace after a duration
prunetracker *pruningTracker
prunectx context.Context
prunefunc context.CancelFunc
// semaphore pool support
semaphores *SemaphorePool
}
// SubPath returns the name of this Path Element
// without the parent section of the path
// eg the 'file' portion of the path
func (p *PathElement) SubPath() (path SubPath) {
return p.section
}
// Parent returns the parent PathElement of this PathElement
func (p *PathElement) Parent() *PathElement {
return p.parent
}
// ParentChain returns a slice of this Path Elements
// parent Pathelements, in order of parentage
// i.e, the first item is this elements immediate parent
// the last item is always the top-level (leftmost) pathelement
func (p *PathElement) ParentChain() (parents []*PathElement) {
var nextParent *PathElement
if p.parent == nil {
return
} else {
nextParent = p.parent
}
for {
// dont return the root node
if nextParent.section == rootId {
break
}
parents = append([]*PathElement{nextParent}, parents...) // prepend it into the list, to the items are in path order
nextParent = nextParent.Parent()
}
return parents
}
// Chain returns the full Path of this Element, as a slice of individual PathElements
func (p *PathElement) Chain() (chain []*PathElement) {
return append(p.ParentChain(), p)
}
// AbsolutePath returns the full Path of this Element, as a single AbsolutePath instance
func (p *PathElement) AbsolutePath() (path AbsolutePath) {
for _, element := range p.Chain() {
path = append(path, element.SubPath())
}
return path
}
// fetchSubElement fetches named sub element, if it exists
// returns nil if no sub element by that name exists
func (p PathElement) fetchSubElement(path SubPath) *PathElement {
sub, ok := p.children[path]
if ok {
return sub
} else {
return nil
}
}
func (p *PathElement) logChange(e elementChange) {
if p.prunetracker != nil {
if e.change != ChangeDeleted {
if e.elem == p {
p.prunetracker.lastSelfUsed = time.Now()
} else {
p.prunetracker.lastChildUsed = time.Now()
}
}
}
switch e.change {
case ChangeAdded:
// TODO: call hook function
case ChangeEdited:
// TODO: call hook function
case ChangeLocked:
// TODO: call hook function
case ChangeUnlocked:
// TODO: call hook function
case ChangeDeleted:
// TODO: call hook function
case ChangeUnknown:
// subscriberStats for now - placeholder for later audit logging
}
}
// FetchClosestSubPathTail finds the last element in a path chain that most closely resembles the requested path
func (p PathElement) FetchClosestSubPathTail(subPath PathString) *PathElement {
elemChain := p.FetchClosestSubPath(subPath)
if len(elemChain) > 0 {
return elemChain[len(elemChain)-1]
} else {
return nil
}
}
// FetchClosestSubPath will attempt to find the final Path Element that has the
// leading subpath string - this is relative to the pathelement itself, and is not an absolute
// path.
func (p *PathElement) FetchClosestSubPath(subPath PathString) (pathchain []*PathElement) {
elems := splitPath(subPath)
var finalElement *PathElement
var currentElement = p
var nextElement *PathElement
for _, e := range elems {
nextElement = currentElement.fetchSubElement(SubPath(e))
if nextElement == nil { // this is the closest match we can get
finalElement = currentElement
break
} else {
currentElement = nextElement
}
}
if finalElement == nil { // we matched every element exactly
finalElement = currentElement
}
pathchain = finalElement.Chain()
return pathchain
}
// Add a Single subpath Element to this Element
func (p *PathElement) Add(path SubPath) (elem *PathElement, err error) {
err = path.Validate()
if err != nil {
return nil, err
}
p.mu.Lock()
if v, ok := p.children[path]; ok {
// be safely re-entry - element already exists, do not overwrite
elem = v
p.mu.Unlock()
return elem, nil
}
elem = &PathElement{
section: path,
parent: p,
parentnotify: p.subevents,
mu: mutex.New(fmt.Sprintf("internal mutex for %s", path)),
children: make(map[SubPath]*PathElement),
subevents: make(chan elementChange, 2),
selfnotify: make(chan elementChange, 2),
}
p.children[path] = elem
elem.reslock = resourceLock{
selfmu: &sync.Mutex{},
resmu: &sync.Mutex{},
recursive: false,
}
// begin the broadcaster for watch subscriptions to function
elem.initEventBroadcast()
p.mu.Unlock()
return elem, nil
}
// attach an existing PathElement to a parent PathElement
func (p *PathElement) attach(elem *PathElement) (err error) {
// check this is a properly formed element
if elem.children == nil {
elem.children = make(map[SubPath]*PathElement)
}
// propagate our pruning information down to this element as well
elem.prunetracker = p.prunetracker
p.mu.Lock()
p.children[elem.SubPath()] = elem
p.mu.Unlock()
return nil
}
func (p *PathElement) Delete() (err error) {
if p.mu.IsLocked() {
panic("recursive")
}
p.mu.Lock()
defer p.mu.Unlock()
// cascade the context-cancel signal that our event-watching goroutine needs to exit, along with that of all child elements
p.prunefunc()
deleteEvent := elementChange{id: randid.Uint64(), elem: p, change: ChangeDeleted}
p.parentnotify <- deleteEvent
p.selfnotify <- deleteEvent
// recursively delete all children
for _, elem := range p.children {
if elem != nil {
err = elem.Delete()
if err != nil {
return err // TODO: what happens in this half-deleted state?
}
}
}
return nil
}
// AppendRelativePath constructs an element-relative subpath, append it to an Existing PathElement,
// creating all PathElements along the way
func (p *PathElement) AppendRelativePath(subPath PathString) (*PathElement, error) {
// subpaths cannot be absolute, so they cannot start with the delimeter
if strings.HasPrefix(string(subPath), pathDelimeter) {
return nil, errors.Errorf("cannot use an absolute path as a subpath")
}
pathElems := subPath.ToRelativePath()
var cur = p
var err error
for _, e := range pathElems {
cur, err = p.Add(e)
if err != nil {
return nil, err
}
if cur == nil {
return nil, errors.Errorf("path does not exist")
}
}
return cur, nil
}
// remove the leading match portion of an Absolute Path, return only the portion that is SubPath to this Element
func (p *PathElement) subtractPathToSubPaths(path PathString) (newSubPath []SubPath) {
return
}
func (p *PathElement) FetchSubPath(subPath PathString) (*PathElement, error) {
// subpaths cannot be absolute, so they cannot start with the delimeter
if strings.HasPrefix(string(subPath), pathDelimeter) {
return nil, errors.Errorf("cannot use an absolute path as a subpath")
}
pathElems := subPath.ToRelativePath()
var cur = p
for _, e := range pathElems {
cur = cur.fetchSubElement(e)
if cur == nil {
return nil, errors.Errorf("path does not exist")
}
}
return cur, nil
}
// FetchAllSubPaths returns the SubPath location of all descendent PathElements
// underneath this PathElement
func (p *PathElement) FetchAllSubPaths() (allpaths [][]SubPath, err error) {
for _, s := range p.children {
elempaths := [][]SubPath{} // all Normalized SubPaths of this Element
elemsubs, err := s.FetchAllSubPaths()
if err != nil {
return nil, err
}
if len(elemsubs) == 0 { // this element is a terminal path, just add it directly
elempaths = append(elempaths, []SubPath{s.SubPath()})
} else {
for _, sp := range elemsubs { // each object is a []SubPath
adjustedpath := []SubPath{s.SubPath()}
completedpath := append(adjustedpath, sp...)
elempaths = append(elempaths, completedpath)
}
}
allpaths = append(allpaths, elempaths...)
}
return allpaths, nil
}