/
group.go
342 lines (292 loc) · 7.95 KB
/
group.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
339
340
341
342
// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package index
import (
"crypto/sha1"
"fmt"
"sort"
"strconv"
"tchaik.com/index/attr"
)
// Tracker is an interface which defines the Tracks method which returns a list
// of Tracks.
type Tracker interface {
Tracks() []Track
}
// Group is an interface which represents a named group of Tracks.
type Group interface {
Tracker
// Name returns the name of the group.
Name() string
// Field returns the value of a field.
Field(string) interface{}
}
// Collection is an interface which represents an ordered series of Groups.
type Collection interface {
Group
// Keys returns a slice of Keys which give an ordering for Groups in the Collection.
Keys() []Key
// Get returns the Group corresponding to the given Key.
Get(Key) Group
}
// Collect applies the Collector to the Tracker and returns the resulting Collection.
func Collect(t Tracker, c Collector) Collection {
return c.Collect(t)
}
// CollectionPaths creates a slice of Paths which contains the path for each immediate Group
// in the Collection.
func CollectionPaths(c Collection, root Path) []Path {
keys := c.Keys()
paths := make([]Path, len(keys))
for _, k := range keys {
p := make(Path, len(root)+1)
copy(p, root)
p[len(root)] = k
paths = append(paths, p)
}
return paths
}
// GroupFromPath returns the Group which represents the given Path.
func GroupFromPath(g Group, p Path) (Group, error) {
if c, ok := g.(Collection); ok {
ng := c.Get(p[0])
if ng == nil {
return nil, fmt.Errorf("invalid key: '%v'", p[0])
}
if len(p) > 1 {
return GroupFromPath(ng, p[1:])
}
return ng, nil
}
if len(p) > 1 {
return nil, fmt.Errorf("invalid path: reached leaf group and remaining path: %v", p)
}
return g, nil
}
// NewPathsCollection creates a new collection from a source collection `c` which will contain the groups
// represented by the given list of paths.
func NewPathsCollection(src Collection, paths []Path) Collection {
done := make(map[Key]bool)
keys := make([]Key, 0, len(paths))
for _, path := range paths {
k := path[1]
if !done[k] {
keys = append(keys, path[1])
done[k] = true
}
}
return pathsCollection{
Collection: src,
name: "paths collection",
keys: keys,
}
}
type pathsCollection struct {
Collection
name string
keys []Key
}
// Keys implements Collection.
func (c pathsCollection) Keys() []Key { return c.keys }
// Name implements Group.
func (c pathsCollection) Name() string { return c.name }
// Field implements Group.
func (c pathsCollection) Field(string) interface{} { return nil }
func (c pathsCollection) Tracks() []Track {
// TODO: Do something better here - this method shouldn't really get called
return nil
}
// col is a basic implementation of Collection. It assumes that all Groups have unique names
// and so uses Group names for the keys.
type col struct {
keys []Key
name string
grps map[Key]group
flds map[string]interface{}
}
func newCol(name string) col {
return col{
name: name,
grps: make(map[Key]group),
flds: make(map[string]interface{}),
}
}
func (c col) Keys() []Key { return c.keys }
func (c col) Name() string { return c.name }
func (c col) Get(k Key) Group { return c.grps[k] }
func (c col) Field(field string) interface{} { return c.flds[field] }
func (c col) Tracks() []Track {
return collectionTracks(c)
}
// addTrack adds the track t to the collection by adding it to a group with key k. If no such
// group exists in the collection, then a new group is created with name n.
func (c *col) addTrack(n string, k Key, t Track) {
if g, ok := c.grps[k]; ok {
g.tracks = append(g.tracks, t)
c.grps[k] = g
return
}
g := group{
name: n,
tracks: make([]Track, 1),
}
g.tracks[0] = t
c.grps[k] = g
c.keys = append(c.keys, k)
}
// add adds the track t to the collection, using the name n as the key.
func (c *col) add(n string, t Track) {
k := Key(fmt.Sprintf("%x", sha1.Sum([]byte(n)))[:6])
c.addTrack(n, k, t)
}
// collectionTracks iterates over all the tracks in all the groups of the collection to construct a
// slice of Tracks.
func collectionTracks(c Collection) []Track {
keys := c.Keys()
var tracks []Track
for _, k := range keys {
tracks = append(tracks, c.Get(k).Tracks()...)
}
return tracks
}
// Collector is an interface which defines the Collect method.
type Collector interface {
Collect(Tracker) Collection
}
// group is a basic implementation of Group
type group struct {
name string
tracks []Track
fields map[string]interface{}
}
// Name implements Group.
func (g group) Name() string { return g.name }
// Tracks implements Tracker.
func (g group) Tracks() []Track { return g.tracks }
// Field implements Group.
func (g group) Field(field string) interface{} { return g.fields[field] }
// subCol is a wrapper around an existing collection which overrides the Get
// method.
type subCol struct {
Collection
grps map[Key]Group
flds map[string]interface{}
}
// Get implements Collection.
func (sg subCol) Get(k Key) Group {
return sg.grps[k]
}
// Field implements Group.
func (sg subCol) Field(f string) interface{} {
if x, ok := sg.flds[f]; ok {
return x
}
return sg.Collection.Field(f)
}
// SubCollect applies the given Collector to each of the "leaf" Groups
// in the Collection.
func SubCollect(c Collection, r Collector) Collection {
keys := c.Keys()
nc := subCol{
Collection: c,
grps: make(map[Key]Group, len(keys)),
flds: make(map[string]interface{}),
}
for _, k := range keys {
g := c.Get(k)
if gc, ok := g.(Collection); ok {
nc.grps[k] = SubCollect(gc, r)
continue
}
nc.grps[k] = r.Collect(g)
}
return nc
}
// WalkFn is the type of the function called for each Track visited by Walk. Return
// non-nil error from Walk to stop the trasversal, and return the error from Walk.
type WalkFn func(Track, Path) error
func walkCollection(c Collection, p Path, f WalkFn) error {
for _, k := range c.Keys() {
g := c.Get(k)
np := make(Path, len(p)+1)
copy(np, p)
np[len(p)] = k
err := Walk(g, np, f)
if err != nil {
return err
}
}
return nil
}
// Walk transverses the Group and calls the WalkFn on each Track. The Path is assumed to be
// the path of the Group.
func Walk(g Group, p Path, f WalkFn) error {
if gc, ok := g.(Collection); ok {
return walkCollection(gc, p, f)
}
for i, t := range g.Tracks() {
np := make(Path, len(p)+1)
copy(np, p)
np[len(p)] = Key(strconv.Itoa(i))
err := f(t, np)
if err != nil {
return err
}
}
return nil
}
type trackPath struct {
t Track
p Path
}
type trackPathSorter struct {
tp []trackPath
fn LessFn
}
func (tps trackPathSorter) Len() int { return len(tps.tp) }
func (tps trackPathSorter) Swap(i, j int) { tps.tp[i], tps.tp[j] = tps.tp[j], tps.tp[i] }
func (tps trackPathSorter) Less(i, j int) bool { return tps.fn(tps.tp[i].t, tps.tp[j].t) }
// Recent returns a list of paths which are the n most recently added paths.
func Recent(c Collection, n int) []Path {
var trackPaths []trackPath
walkfn := func(t Track, p Path) error {
trackPaths = append(trackPaths, trackPath{t, p[:2]})
return nil
}
Walk(c, Path([]Key{"Root"}), walkfn)
sort.Sort(sort.Reverse(trackPathSorter{trackPaths, SortByTime("DateAdded")}))
dedup := make(map[string]bool, len(trackPaths))
result := make([]Path, 0, n)
for _, tp := range trackPaths {
e := tp.p.Encode()
if !dedup[e] {
dedup[e] = true
result = append(result, tp.p)
}
if len(result) == n {
break
}
}
return result
}
// By is a function which returns a Collector to group a collection using the given
// attribute.
func By(a attr.Interface) Collector {
return groupBy{a}
}
type groupBy struct {
attr.Interface
}
// Collect implements Collector
func (a groupBy) Collect(tracker Tracker) Collection {
name := "by " + a.Name()
if tg, ok := tracker.(Group); ok {
name = tg.Name()
}
gg := newCol(name)
for _, t := range tracker.Tracks() {
gg.add(fmt.Sprintf("%v", a.Value(t)), t)
}
return gg
}