New upstream version 2.1.0
Drew Parsons
5 years ago
0 | The MIT License (MIT) | |
1 | ||
2 | Copyright (c) 2015 Matt Joiner | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | of this software and associated documentation files (the "Software"), to deal | |
6 | in the Software without restriction, including without limitation the rights | |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | copies of the Software, and to permit persons to whom the Software is | |
9 | furnished to do so, subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in | |
12 | all copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
20 | THE SOFTWARE. |
0 | # missinggo | |
1 | [![GoDoc](https://godoc.org/github.com/anacrolix/missinggo?status.svg)](https://godoc.org/github.com/anacrolix/missinggo) | |
2 | ||
3 | Stuff that supplements Go's stdlib, or isn't significant enough to be in its own repo. |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net" | |
4 | "strconv" | |
5 | ) | |
6 | ||
7 | // Extracts the port as an integer from an address string. | |
8 | func AddrPort(addr net.Addr) int { | |
9 | switch raw := addr.(type) { | |
10 | case *net.UDPAddr: | |
11 | return raw.Port | |
12 | case *net.TCPAddr: | |
13 | return raw.Port | |
14 | default: | |
15 | _, port, err := net.SplitHostPort(addr.String()) | |
16 | if err != nil { | |
17 | panic(err) | |
18 | } | |
19 | i64, err := strconv.ParseInt(port, 0, 0) | |
20 | if err != nil { | |
21 | panic(err) | |
22 | } | |
23 | return int(i64) | |
24 | } | |
25 | } | |
26 | ||
27 | func AddrIP(addr net.Addr) net.IP { | |
28 | if addr == nil { | |
29 | return nil | |
30 | } | |
31 | switch raw := addr.(type) { | |
32 | case *net.UDPAddr: | |
33 | return raw.IP | |
34 | case *net.TCPAddr: | |
35 | return raw.IP | |
36 | default: | |
37 | host, _, err := net.SplitHostPort(addr.String()) | |
38 | if err != nil { | |
39 | panic(err) | |
40 | } | |
41 | return net.ParseIP(host) | |
42 | } | |
43 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | // Extracts the access time from the FileInfo internals. | |
8 | func FileInfoAccessTime(fi os.FileInfo) time.Time { | |
9 | return fileInfoAccessTime(fi) | |
10 | } |
0 | // +build linux dragonfly openbsd solaris | |
1 | ||
2 | package missinggo | |
3 | ||
4 | import ( | |
5 | "os" | |
6 | "syscall" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | func fileInfoAccessTime(fi os.FileInfo) time.Time { | |
11 | ts := fi.Sys().(*syscall.Stat_t).Atim | |
12 | return time.Unix(int64(ts.Sec), int64(ts.Nsec)) | |
13 | } |
0 | // +build darwin freebsd netbsd | |
1 | ||
2 | package missinggo | |
3 | ||
4 | import ( | |
5 | "os" | |
6 | "syscall" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | func fileInfoAccessTime(fi os.FileInfo) time.Time { | |
11 | ts := fi.Sys().(*syscall.Stat_t).Atimespec | |
12 | return time.Unix(int64(ts.Sec), int64(ts.Nsec)) | |
13 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "syscall" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | func fileInfoAccessTime(fi os.FileInfo) time.Time { | |
9 | sec := fi.Sys().(*syscall.Dir).Atime | |
10 | return time.Unix(int64(sec), 0) | |
11 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "io/ioutil" | |
4 | "os" | |
5 | "testing" | |
6 | ||
7 | "github.com/stretchr/testify/assert" | |
8 | "github.com/stretchr/testify/require" | |
9 | ) | |
10 | ||
11 | func TestFileInfoAccessTime(t *testing.T) { | |
12 | f, err := ioutil.TempFile("", "") | |
13 | require.NoError(t, err) | |
14 | assert.NoError(t, f.Close()) | |
15 | name := f.Name() | |
16 | t.Log(name) | |
17 | defer func() { | |
18 | err := os.Remove(name) | |
19 | if err != nil { | |
20 | t.Log(err) | |
21 | } | |
22 | }() | |
23 | fi, err := os.Stat(name) | |
24 | require.NoError(t, err) | |
25 | t.Log(FileInfoAccessTime(fi)) | |
26 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "syscall" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | func fileInfoAccessTime(fi os.FileInfo) time.Time { | |
9 | ts := fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime | |
10 | return time.Unix(0, int64(ts.Nanoseconds())) | |
11 | } |
0 | // Package bitmap provides a []bool/bitmap implementation with standardized | |
1 | // iteration. Bitmaps are the equivalent of []bool, with improved compression | |
2 | // for runs of similar values, and faster operations on ranges and the like. | |
3 | package bitmap | |
4 | ||
5 | import ( | |
6 | "math" | |
7 | ||
8 | "github.com/RoaringBitmap/roaring" | |
9 | ||
10 | "github.com/anacrolix/missinggo/iter" | |
11 | ) | |
12 | ||
13 | const MaxInt = -1 | |
14 | ||
15 | type BitIndex = int | |
16 | ||
17 | type Interface interface { | |
18 | Len() int | |
19 | } | |
20 | ||
21 | // Bitmaps store the existence of values in [0,math.MaxUint32] more | |
22 | // efficiently than []bool. The empty value starts with no bits set. | |
23 | type Bitmap struct { | |
24 | RB *roaring.Bitmap | |
25 | } | |
26 | ||
27 | var ToEnd int = -1 | |
28 | ||
29 | // The number of set bits in the bitmap. Also known as cardinality. | |
30 | func (me *Bitmap) Len() int { | |
31 | if me.RB == nil { | |
32 | return 0 | |
33 | } | |
34 | return int(me.RB.GetCardinality()) | |
35 | } | |
36 | ||
37 | func (me Bitmap) ToSortedSlice() (ret []int) { | |
38 | if me.RB == nil { | |
39 | return | |
40 | } | |
41 | for _, ui32 := range me.RB.ToArray() { | |
42 | ret = append(ret, int(int32(ui32))) | |
43 | } | |
44 | return | |
45 | } | |
46 | ||
47 | func (me *Bitmap) lazyRB() *roaring.Bitmap { | |
48 | if me.RB == nil { | |
49 | me.RB = roaring.NewBitmap() | |
50 | } | |
51 | return me.RB | |
52 | } | |
53 | ||
54 | func (me Bitmap) Iter(cb iter.Callback) { | |
55 | me.IterTyped(func(i int) bool { | |
56 | return cb(i) | |
57 | }) | |
58 | } | |
59 | ||
60 | // Returns true if all values were traversed without early termination. | |
61 | func (me Bitmap) IterTyped(f func(int) bool) bool { | |
62 | if me.RB == nil { | |
63 | return true | |
64 | } | |
65 | it := me.RB.Iterator() | |
66 | for it.HasNext() { | |
67 | if !f(int(it.Next())) { | |
68 | return false | |
69 | } | |
70 | } | |
71 | return true | |
72 | } | |
73 | ||
74 | func checkInt(i BitIndex) { | |
75 | if i < math.MinInt32 || i > math.MaxInt32 { | |
76 | panic("out of bounds") | |
77 | } | |
78 | } | |
79 | ||
80 | func (me *Bitmap) Add(is ...BitIndex) { | |
81 | rb := me.lazyRB() | |
82 | for _, i := range is { | |
83 | checkInt(i) | |
84 | rb.AddInt(i) | |
85 | } | |
86 | } | |
87 | ||
88 | func (me *Bitmap) AddRange(begin, end BitIndex) { | |
89 | if begin >= end { | |
90 | return | |
91 | } | |
92 | me.lazyRB().AddRange(uint64(begin), uint64(end)) | |
93 | } | |
94 | ||
95 | func (me *Bitmap) Remove(i BitIndex) bool { | |
96 | if me.RB == nil { | |
97 | return false | |
98 | } | |
99 | return me.RB.CheckedRemove(uint32(i)) | |
100 | } | |
101 | ||
102 | func (me *Bitmap) Union(other Bitmap) { | |
103 | me.lazyRB().Or(other.lazyRB()) | |
104 | } | |
105 | ||
106 | func (me *Bitmap) Contains(i int) bool { | |
107 | if me.RB == nil { | |
108 | return false | |
109 | } | |
110 | return me.RB.Contains(uint32(i)) | |
111 | } | |
112 | ||
113 | func (me *Bitmap) Sub(other Bitmap) { | |
114 | if other.RB == nil { | |
115 | return | |
116 | } | |
117 | if me.RB == nil { | |
118 | return | |
119 | } | |
120 | me.RB.AndNot(other.RB) | |
121 | } | |
122 | ||
123 | func (me *Bitmap) Clear() { | |
124 | if me.RB == nil { | |
125 | return | |
126 | } | |
127 | me.RB.Clear() | |
128 | } | |
129 | ||
130 | func (me Bitmap) Copy() (ret Bitmap) { | |
131 | ret = me | |
132 | if ret.RB != nil { | |
133 | ret.RB = ret.RB.Clone() | |
134 | } | |
135 | return | |
136 | } | |
137 | ||
138 | func (me *Bitmap) FlipRange(begin, end BitIndex) { | |
139 | me.lazyRB().FlipInt(begin, end) | |
140 | } | |
141 | ||
142 | func (me *Bitmap) Get(bit BitIndex) bool { | |
143 | return me.RB != nil && me.RB.ContainsInt(bit) | |
144 | } | |
145 | ||
146 | func (me *Bitmap) Set(bit BitIndex, value bool) { | |
147 | if value { | |
148 | me.lazyRB().AddInt(bit) | |
149 | } else { | |
150 | if me.RB != nil { | |
151 | me.RB.Remove(uint32(bit)) | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | func (me *Bitmap) RemoveRange(begin, end BitIndex) *Bitmap { | |
157 | if me.RB == nil { | |
158 | return me | |
159 | } | |
160 | rangeEnd := uint64(end) | |
161 | if end == ToEnd { | |
162 | rangeEnd = 0x100000000 | |
163 | } | |
164 | me.RB.RemoveRange(uint64(begin), rangeEnd) | |
165 | return me | |
166 | } | |
167 | ||
168 | func (me Bitmap) IsEmpty() bool { | |
169 | return me.RB == nil || me.RB.IsEmpty() | |
170 | } |
0 | package bitmap | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | "testing" | |
5 | ||
6 | "github.com/RoaringBitmap/roaring" | |
7 | "github.com/stretchr/testify/assert" | |
8 | "github.com/stretchr/testify/require" | |
9 | ||
10 | "github.com/anacrolix/missinggo/iter" | |
11 | "github.com/anacrolix/missinggo/slices" | |
12 | ) | |
13 | ||
14 | func TestEmptyBitmap(t *testing.T) { | |
15 | var bm Bitmap | |
16 | assert.False(t, bm.Contains(0)) | |
17 | bm.Remove(0) | |
18 | it := iter.NewIterator(&bm) | |
19 | assert.Panics(t, func() { it.Value() }) | |
20 | assert.False(t, it.Next()) | |
21 | } | |
22 | ||
23 | func bitmapSlice(bm *Bitmap) (ret []int) { | |
24 | sl := iter.IterableAsSlice(bm) | |
25 | slices.MakeInto(&ret, sl) | |
26 | return | |
27 | } | |
28 | ||
29 | func TestSimpleBitmap(t *testing.T) { | |
30 | bm := new(Bitmap) | |
31 | assert.EqualValues(t, []int(nil), bitmapSlice(bm)) | |
32 | bm.Add(0) | |
33 | assert.True(t, bm.Contains(0)) | |
34 | assert.False(t, bm.Contains(1)) | |
35 | assert.EqualValues(t, 1, bm.Len()) | |
36 | bm.Add(3) | |
37 | assert.True(t, bm.Contains(0)) | |
38 | assert.True(t, bm.Contains(3)) | |
39 | assert.EqualValues(t, []int{0, 3}, bitmapSlice(bm)) | |
40 | assert.EqualValues(t, 2, bm.Len()) | |
41 | bm.Remove(0) | |
42 | assert.EqualValues(t, []int{3}, bitmapSlice(bm)) | |
43 | assert.EqualValues(t, 1, bm.Len()) | |
44 | } | |
45 | ||
46 | func TestSub(t *testing.T) { | |
47 | var left, right Bitmap | |
48 | left.Add(2, 5, 4) | |
49 | right.Add(3, 2, 6) | |
50 | assert.Equal(t, []int{4, 5}, Sub(left, right).ToSortedSlice()) | |
51 | assert.Equal(t, []int{3, 6}, Sub(right, left).ToSortedSlice()) | |
52 | } | |
53 | ||
54 | func TestSubUninited(t *testing.T) { | |
55 | var left, right Bitmap | |
56 | assert.EqualValues(t, []int(nil), Sub(left, right).ToSortedSlice()) | |
57 | } | |
58 | ||
59 | func TestAddRange(t *testing.T) { | |
60 | var bm Bitmap | |
61 | bm.AddRange(21, 26) | |
62 | bm.AddRange(9, 14) | |
63 | bm.AddRange(11, 16) | |
64 | bm.Remove(12) | |
65 | assert.EqualValues(t, []int{9, 10, 11, 13, 14, 15, 21, 22, 23, 24, 25}, bm.ToSortedSlice()) | |
66 | assert.EqualValues(t, 11, bm.Len()) | |
67 | bm.Clear() | |
68 | bm.AddRange(3, 7) | |
69 | bm.AddRange(0, 3) | |
70 | bm.AddRange(2, 4) | |
71 | bm.Remove(3) | |
72 | assert.EqualValues(t, []int{0, 1, 2, 4, 5, 6}, bm.ToSortedSlice()) | |
73 | assert.EqualValues(t, 6, bm.Len()) | |
74 | } | |
75 | ||
76 | func TestRemoveRange(t *testing.T) { | |
77 | var bm Bitmap | |
78 | bm.AddRange(3, 12) | |
79 | assert.EqualValues(t, 9, bm.Len()) | |
80 | bm.RemoveRange(14, -1) | |
81 | assert.EqualValues(t, 9, bm.Len()) | |
82 | bm.RemoveRange(2, 5) | |
83 | assert.EqualValues(t, 7, bm.Len()) | |
84 | bm.RemoveRange(10, -1) | |
85 | assert.EqualValues(t, 5, bm.Len()) | |
86 | } | |
87 | ||
88 | func TestLimits(t *testing.T) { | |
89 | var bm Bitmap | |
90 | assert.Panics(t, func() { bm.Add(math.MaxInt64) }) | |
91 | bm.Add(-1) | |
92 | assert.EqualValues(t, 1, bm.Len()) | |
93 | assert.EqualValues(t, []int{MaxInt}, bm.ToSortedSlice()) | |
94 | } | |
95 | ||
96 | func TestRoaringRangeEnd(t *testing.T) { | |
97 | r := roaring.New() | |
98 | r.Add(roaring.MaxUint32) | |
99 | require.EqualValues(t, 1, r.GetCardinality()) | |
100 | r.RemoveRange(0, roaring.MaxUint32) | |
101 | assert.EqualValues(t, 1, r.GetCardinality()) | |
102 | r.RemoveRange(0, math.MaxUint64) | |
103 | assert.EqualValues(t, 0, r.GetCardinality()) | |
104 | } |
0 | package bitmap | |
1 | ||
2 | import "github.com/RoaringBitmap/roaring" | |
3 | ||
4 | func Sub(left, right Bitmap) Bitmap { | |
5 | return Bitmap{ | |
6 | RB: roaring.AndNot(left.lazyRB(), right.lazyRB()), | |
7 | } | |
8 | } | |
9 | ||
10 | func Flip(bm Bitmap, start, end int) Bitmap { | |
11 | return Bitmap{ | |
12 | RB: roaring.FlipInt(bm.lazyRB(), start, end), | |
13 | } | |
14 | } |
0 | package bitmap | |
1 | ||
2 | import "github.com/RoaringBitmap/roaring" | |
3 | ||
4 | type Iter struct { | |
5 | ii roaring.IntIterable | |
6 | } | |
7 | ||
8 | func (me *Iter) Next() bool { | |
9 | if me == nil { | |
10 | return false | |
11 | } | |
12 | return me.ii.HasNext() | |
13 | } | |
14 | ||
15 | func (me *Iter) Value() interface{} { | |
16 | return me.ValueInt() | |
17 | } | |
18 | ||
19 | func (me *Iter) ValueInt() int { | |
20 | return int(me.ii.Next()) | |
21 | } | |
22 | ||
23 | func (me *Iter) Stop() {} |
0 | package cache | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "log" | |
5 | "sync" | |
6 | ||
7 | humanize "github.com/dustin/go-humanize" | |
8 | ) | |
9 | ||
10 | type Key = string | |
11 | ||
12 | type Cache struct { | |
13 | mu sync.Mutex | |
14 | filled int64 | |
15 | Policy Policy | |
16 | Items map[Key]ItemMeta | |
17 | } | |
18 | ||
19 | type ItemMeta struct { | |
20 | Size int64 | |
21 | CanEvict bool | |
22 | Usage | |
23 | } | |
24 | ||
25 | type Item struct { | |
26 | Key | |
27 | ItemMeta | |
28 | } | |
29 | ||
30 | func (me *Cache) Remove(k Key) { | |
31 | me.mu.Lock() | |
32 | i := me.Items[k] | |
33 | me.filled -= i.Size | |
34 | delete(me.Items, k) | |
35 | me.Policy.Forget(k) | |
36 | me.mu.Unlock() | |
37 | } | |
38 | ||
39 | func (me *Cache) Update(i Item) { | |
40 | me.mu.Lock() | |
41 | m := me.Items[i.Key] | |
42 | me.filled -= m.Size | |
43 | me.filled += i.Size | |
44 | if me.Items == nil { | |
45 | me.Items = make(map[Key]ItemMeta) | |
46 | } | |
47 | me.Items[i.Key] = i.ItemMeta | |
48 | if i.CanEvict { | |
49 | me.Policy.Update(i.Key, i.Usage) | |
50 | } else { | |
51 | me.Policy.Forget(i.Key) | |
52 | } | |
53 | me.mu.Unlock() | |
54 | } | |
55 | ||
56 | func (me *Cache) logState() { | |
57 | log.Print(me) | |
58 | } | |
59 | ||
60 | func (me *Cache) String() string { | |
61 | me.mu.Lock() | |
62 | defer me.mu.Unlock() | |
63 | return fmt.Sprintf( | |
64 | "%p: %d items, %v bytes used, lru: %s", | |
65 | me, len(me.Items), humanize.Bytes(uint64(me.filled)), | |
66 | func() string { | |
67 | k, ok := me.Policy.Candidate() | |
68 | if ok { | |
69 | i := me.Items[k] | |
70 | return fmt.Sprintf( | |
71 | "%q (%v: %v)", | |
72 | k, humanize.Bytes(uint64(i.Size)), i.Usage) | |
73 | } | |
74 | return "none" | |
75 | }(), | |
76 | ) | |
77 | } | |
78 | ||
79 | func (me *Cache) Used() int64 { | |
80 | return me.filled | |
81 | } | |
82 | ||
83 | func (me *Cache) NumItems() int { | |
84 | return len(me.Items) | |
85 | } | |
86 | ||
87 | func (me *Cache) Clear() { | |
88 | for k := range me.Items { | |
89 | delete(me.Items, k) | |
90 | me.Policy.Forget(k) | |
91 | } | |
92 | me.Items = nil | |
93 | me.filled = 0 | |
94 | } | |
95 | ||
96 | func (me *Cache) Filled() int64 { | |
97 | me.mu.Lock() | |
98 | defer me.mu.Unlock() | |
99 | return me.filled | |
100 | } | |
101 | ||
102 | func (me *Cache) Candidate() (Item, bool) { | |
103 | me.mu.Lock() | |
104 | defer me.mu.Unlock() | |
105 | k, ok := me.Policy.Candidate() | |
106 | return Item{ | |
107 | Key: k, | |
108 | ItemMeta: me.Items[k], | |
109 | }, ok | |
110 | } |
0 | package cache | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | ||
5 | "github.com/anacrolix/missinggo" | |
6 | "github.com/anacrolix/missinggo/orderedmap" | |
7 | ) | |
8 | ||
9 | type LruPolicy struct { | |
10 | mu sync.RWMutex | |
11 | sorted orderedmap.OrderedMap | |
12 | keys map[Key]Usage | |
13 | } | |
14 | ||
15 | type lruItem struct { | |
16 | Key | |
17 | Usage | |
18 | } | |
19 | ||
20 | var _ Policy = (*LruPolicy)(nil) | |
21 | ||
22 | func (me *LruPolicy) Candidate() (k Key, ok bool) { | |
23 | me.mu.RLock() | |
24 | defer me.mu.RUnlock() | |
25 | if me.sorted == nil { | |
26 | return | |
27 | } | |
28 | me.sorted.Iter(func(i interface{}) bool { | |
29 | k = i.(lruItem).Key | |
30 | ok = true | |
31 | return false | |
32 | }) | |
33 | return | |
34 | } | |
35 | ||
36 | func (me *LruPolicy) Forget(k Key) { | |
37 | me.mu.Lock() | |
38 | defer me.mu.Unlock() | |
39 | u, ok := me.keys[k] | |
40 | if !ok { | |
41 | return | |
42 | } | |
43 | me.sorted.Unset(lruItem{k, u}) | |
44 | delete(me.keys, k) | |
45 | } | |
46 | ||
47 | func (me *LruPolicy) NumItems() int { | |
48 | return len(me.keys) | |
49 | } | |
50 | ||
51 | func (me *LruPolicy) Update(k Key, u Usage) { | |
52 | me.mu.Lock() | |
53 | defer me.mu.Unlock() | |
54 | if me.sorted == nil { | |
55 | me.sorted = orderedmap.New(func(l, r interface{}) bool { | |
56 | _l := l.(lruItem) | |
57 | _r := r.(lruItem) | |
58 | var ml missinggo.MultiLess | |
59 | ml.NextBool(_l.Usage.Less(_r.Usage), _r.Usage.Less(_l.Usage)) | |
60 | ml.StrictNext(_l.Key == _r.Key, _l.Key < _r.Key) | |
61 | return ml.Less() | |
62 | }) | |
63 | } | |
64 | if u, ok := me.keys[k]; ok { | |
65 | me.sorted.Unset(lruItem{k, u}) | |
66 | } | |
67 | me.sorted.Set(lruItem{k, u}, struct{}{}) | |
68 | if me.keys == nil { | |
69 | me.keys = make(map[Key]Usage) | |
70 | } | |
71 | me.keys[k] = u | |
72 | } |
0 | package cache | |
1 | ||
2 | type Usage interface { | |
3 | Less(Usage) bool | |
4 | } | |
5 | ||
6 | type Policy interface { | |
7 | Candidate() (Key, bool) | |
8 | Update(Key, Usage) | |
9 | Forget(Key) | |
10 | NumItems() int | |
11 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "crypto/tls" | |
4 | "log" | |
5 | "os" | |
6 | "path/filepath" | |
7 | "strings" | |
8 | ) | |
9 | ||
10 | func LoadCertificateDir(dir string) (certs []tls.Certificate, err error) { | |
11 | d, err := os.Open(dir) | |
12 | if err != nil { | |
13 | return | |
14 | } | |
15 | defer d.Close() | |
16 | const defaultPEMFile = "default.pem" | |
17 | if p := filepath.Join(dir, defaultPEMFile); FilePathExists(p) { | |
18 | cert, err := tls.LoadX509KeyPair(p, p) | |
19 | if err == nil { | |
20 | certs = append(certs, cert) | |
21 | } else { | |
22 | log.Printf("error loading default certicate: %s", err) | |
23 | } | |
24 | } | |
25 | files, err := d.Readdir(-1) | |
26 | if err != nil { | |
27 | return | |
28 | } | |
29 | for _, f := range files { | |
30 | if f.Name() == defaultPEMFile { | |
31 | continue | |
32 | } | |
33 | if !strings.HasSuffix(f.Name(), ".pem") { | |
34 | continue | |
35 | } | |
36 | p := filepath.Join(dir, f.Name()) | |
37 | cert, err := tls.LoadX509KeyPair(p, p) | |
38 | if err != nil { | |
39 | log.Printf("error loading key pair from %q: %s", p, err) | |
40 | continue | |
41 | } | |
42 | certs = append(certs, cert) | |
43 | } | |
44 | return | |
45 | } |
0 | package missinggo | |
1 | ||
2 | import "sync" | |
3 | ||
4 | type ChanCond struct { | |
5 | mu sync.Mutex | |
6 | ch chan struct{} | |
7 | } | |
8 | ||
9 | func (me *ChanCond) Wait() <-chan struct{} { | |
10 | me.mu.Lock() | |
11 | defer me.mu.Unlock() | |
12 | if me.ch == nil { | |
13 | me.ch = make(chan struct{}) | |
14 | } | |
15 | return me.ch | |
16 | } | |
17 | ||
18 | func (me *ChanCond) Signal() { | |
19 | me.mu.Lock() | |
20 | defer me.mu.Unlock() | |
21 | select { | |
22 | case me.ch <- struct{}{}: | |
23 | default: | |
24 | } | |
25 | } | |
26 | ||
27 | func (me *ChanCond) Broadcast() { | |
28 | me.mu.Lock() | |
29 | defer me.mu.Unlock() | |
30 | if me.ch == nil { | |
31 | return | |
32 | } | |
33 | close(me.ch) | |
34 | me.ch = nil | |
35 | } |
0 | package chans | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | ) | |
5 | ||
6 | // Receives from any channel until it's closed. | |
7 | func Drain(ch interface{}) { | |
8 | chValue := reflect.ValueOf(ch) | |
9 | for { | |
10 | _, ok := chValue.Recv() | |
11 | if !ok { | |
12 | break | |
13 | } | |
14 | } | |
15 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "crypto/tls" | |
4 | "fmt" | |
5 | "io" | |
6 | "log" | |
7 | "net/http" | |
8 | "os" | |
9 | "regexp" | |
10 | "strconv" | |
11 | ||
12 | _ "github.com/anacrolix/envpprof" | |
13 | "github.com/anacrolix/tagflag" | |
14 | "github.com/dustin/go-humanize" | |
15 | ||
16 | "github.com/anacrolix/missinggo" | |
17 | "github.com/anacrolix/missinggo/filecache" | |
18 | ) | |
19 | ||
20 | var c *filecache.Cache | |
21 | ||
22 | func handleNewData(w http.ResponseWriter, path string, offset int64, r io.Reader) (served bool) { | |
23 | f, err := c.OpenFile(path, os.O_CREATE|os.O_WRONLY) | |
24 | if err != nil { | |
25 | log.Print(err) | |
26 | http.Error(w, "couldn't open file", http.StatusInternalServerError) | |
27 | return true | |
28 | } | |
29 | defer f.Close() | |
30 | f.Seek(offset, os.SEEK_SET) | |
31 | _, err = io.Copy(f, r) | |
32 | if err != nil { | |
33 | log.Print(err) | |
34 | c.Remove(path) | |
35 | http.Error(w, "didn't complete", http.StatusInternalServerError) | |
36 | return true | |
37 | } | |
38 | return | |
39 | } | |
40 | ||
41 | // Parses out the first byte from a Content-Range header. Returns 0 if it | |
42 | // isn't found, which is what is implied if there is no header. | |
43 | func parseContentRangeFirstByte(s string) int64 { | |
44 | matches := regexp.MustCompile(`(\d+)-`).FindStringSubmatch(s) | |
45 | if matches == nil { | |
46 | return 0 | |
47 | } | |
48 | ret, _ := strconv.ParseInt(matches[1], 0, 64) | |
49 | return ret | |
50 | } | |
51 | ||
52 | func handleDelete(w http.ResponseWriter, path string) { | |
53 | err := c.Remove(path) | |
54 | if err != nil { | |
55 | log.Print(err) | |
56 | http.Error(w, "didn't work", http.StatusInternalServerError) | |
57 | return | |
58 | } | |
59 | } | |
60 | ||
61 | func main() { | |
62 | log.SetFlags(log.Flags() | log.Lshortfile) | |
63 | args := struct { | |
64 | Capacity tagflag.Bytes `short:"c"` | |
65 | Addr string | |
66 | }{ | |
67 | Capacity: -1, | |
68 | Addr: "localhost:2076", | |
69 | } | |
70 | tagflag.Parse(&args) | |
71 | root, err := os.Getwd() | |
72 | if err != nil { | |
73 | log.Fatal(err) | |
74 | } | |
75 | log.Printf("cache root at %q", root) | |
76 | c, err = filecache.NewCache(root) | |
77 | if err != nil { | |
78 | log.Fatalf("error creating cache: %s", err) | |
79 | } | |
80 | if args.Capacity < 0 { | |
81 | log.Printf("no capacity set, no evictions will occur") | |
82 | } else { | |
83 | c.SetCapacity(args.Capacity.Int64()) | |
84 | log.Printf("setting capacity to %s bytes", humanize.Comma(args.Capacity.Int64())) | |
85 | } | |
86 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | |
87 | p := r.URL.Path[1:] | |
88 | switch r.Method { | |
89 | case "DELETE": | |
90 | log.Printf("%s %s", r.Method, r.RequestURI) | |
91 | handleDelete(w, p) | |
92 | return | |
93 | case "PUT", "PATCH", "POST": | |
94 | contentRange := r.Header.Get("Content-Range") | |
95 | firstByte := parseContentRangeFirstByte(contentRange) | |
96 | log.Printf("%s (%d-) %s", r.Method, firstByte, r.RequestURI) | |
97 | handleNewData(w, p, firstByte, r.Body) | |
98 | return | |
99 | } | |
100 | log.Printf("%s %s %s", r.Method, r.Header.Get("Range"), r.RequestURI) | |
101 | f, err := c.OpenFile(p, os.O_RDONLY) | |
102 | if os.IsNotExist(err) { | |
103 | http.NotFound(w, r) | |
104 | return | |
105 | } | |
106 | if err != nil { | |
107 | log.Printf("couldn't open requested file: %s", err) | |
108 | http.Error(w, "couldn't open file", http.StatusInternalServerError) | |
109 | return | |
110 | } | |
111 | defer func() { | |
112 | go f.Close() | |
113 | }() | |
114 | info, _ := f.Stat() | |
115 | w.Header().Set("Content-Range", fmt.Sprintf("*/%d", info.Size())) | |
116 | http.ServeContent(w, r, p, info.ModTime(), f) | |
117 | }) | |
118 | http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { | |
119 | info := c.Info() | |
120 | fmt.Fprintf(w, "Capacity: %d\n", info.Capacity) | |
121 | fmt.Fprintf(w, "Current Size: %d\n", info.Filled) | |
122 | fmt.Fprintf(w, "Item Count: %d\n", info.NumItems) | |
123 | }) | |
124 | http.HandleFunc("/lru", func(w http.ResponseWriter, r *http.Request) { | |
125 | c.WalkItems(func(item filecache.ItemInfo) { | |
126 | fmt.Fprintf(w, "%s\t%d\t%s\n", item.Accessed, item.Size, item.Path) | |
127 | }) | |
128 | }) | |
129 | cert, err := missinggo.NewSelfSignedCertificate() | |
130 | if err != nil { | |
131 | log.Fatal(err) | |
132 | } | |
133 | srv := http.Server{ | |
134 | Addr: args.Addr, | |
135 | TLSConfig: &tls.Config{ | |
136 | Certificates: []tls.Certificate{cert}, | |
137 | }, | |
138 | } | |
139 | log.Fatal(srv.ListenAndServeTLS("", "")) | |
140 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | func TestFailedPutDeletesFile(t *testing.T) { | |
7 | // TODO | |
8 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "encoding/base32" | |
4 | "encoding/hex" | |
5 | "fmt" | |
6 | "io" | |
7 | "io/ioutil" | |
8 | "net/url" | |
9 | "os" | |
10 | "strings" | |
11 | ) | |
12 | ||
13 | func escape(encoding string) { | |
14 | switch { | |
15 | case strings.HasPrefix("query", encoding): | |
16 | b, err := ioutil.ReadAll(os.Stdin) | |
17 | if err != nil { | |
18 | fmt.Fprintln(os.Stderr, err) | |
19 | os.Exit(1) | |
20 | } | |
21 | os.Stdout.Write([]byte(url.QueryEscape(string(b)))) | |
22 | case strings.HasPrefix("hex", encoding): | |
23 | b, err := ioutil.ReadAll(os.Stdin) | |
24 | if err != nil { | |
25 | fmt.Fprintln(os.Stderr, err) | |
26 | os.Exit(1) | |
27 | } | |
28 | os.Stdout.Write([]byte(hex.EncodeToString(b))) | |
29 | default: | |
30 | fmt.Fprintf(os.Stderr, "unknown escape encoding: %q\n", encoding) | |
31 | os.Exit(2) | |
32 | } | |
33 | } | |
34 | ||
35 | func unescape(encoding string) { | |
36 | switch { | |
37 | case strings.HasPrefix("query", encoding): | |
38 | b, err := ioutil.ReadAll(os.Stdin) | |
39 | if err != nil { | |
40 | fmt.Fprintln(os.Stderr, err) | |
41 | os.Exit(1) | |
42 | } | |
43 | s, err := url.QueryUnescape(string(b)) | |
44 | if err != nil { | |
45 | fmt.Fprintln(os.Stderr, err) | |
46 | os.Exit(1) | |
47 | } | |
48 | os.Stdout.Write([]byte(s)) | |
49 | case strings.HasPrefix("b32", encoding): | |
50 | d := base32.NewDecoder(base32.StdEncoding, os.Stdin) | |
51 | io.Copy(os.Stdout, d) | |
52 | default: | |
53 | fmt.Fprintf(os.Stderr, "unknown unescape encoding: %q\n", encoding) | |
54 | } | |
55 | } | |
56 | ||
57 | func main() { | |
58 | if len(os.Args) != 3 { | |
59 | fmt.Fprintf(os.Stderr, "expected two arguments: <mode> <encoding>: got %d\n", len(os.Args)-1) | |
60 | os.Exit(2) | |
61 | } | |
62 | mode := os.Args[1] | |
63 | switch { | |
64 | case strings.HasPrefix("escape", mode): | |
65 | escape(os.Args[2]) | |
66 | case strings.HasPrefix("unescape", mode) || strings.HasPrefix("decode", mode): | |
67 | unescape(os.Args[2]) | |
68 | default: | |
69 | fmt.Fprintf(os.Stderr, "unknown mode: %q\n", mode) | |
70 | os.Exit(2) | |
71 | } | |
72 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "os" | |
5 | ) | |
6 | ||
7 | func main() { | |
8 | for _, v := range os.Environ() { | |
9 | fmt.Printf("%s\n", v) | |
10 | } | |
11 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "log" | |
4 | "net" | |
5 | "net/http" | |
6 | "os" | |
7 | ||
8 | "github.com/anacrolix/tagflag" | |
9 | ) | |
10 | ||
11 | func setIfGetHeader(w http.ResponseWriter, r *http.Request, set, get string) { | |
12 | h := r.Header.Get(get) | |
13 | if h == "" { | |
14 | return | |
15 | } | |
16 | w.Header().Set(set, h) | |
17 | } | |
18 | ||
19 | func allowCORS(h http.Handler) http.Handler { | |
20 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
21 | if origin := r.Header.Get("Origin"); origin != "" { | |
22 | w.Header().Set("Access-Control-Allow-Origin", origin) | |
23 | w.Header().Set("Access-Control-Allow-Credentials", "true") | |
24 | setIfGetHeader(w, r, "Access-Control-Allow-Methods", "Access-Control-Request-Method") | |
25 | setIfGetHeader(w, r, "Access-Control-Allow-Headers", "Access-Control-Request-Headers") | |
26 | w.Header().Set("Access-Control-Expose-Headers", "Content-Type") | |
27 | } | |
28 | h.ServeHTTP(w, r) | |
29 | }) | |
30 | } | |
31 | ||
32 | func main() { | |
33 | var flags = struct { | |
34 | Addr string | |
35 | }{ | |
36 | Addr: "localhost:8080", | |
37 | } | |
38 | tagflag.Parse(&flags) | |
39 | dir, err := os.Getwd() | |
40 | if err != nil { | |
41 | log.Fatal(err) | |
42 | } | |
43 | l, err := net.Listen("tcp", flags.Addr) | |
44 | if err != nil { | |
45 | log.Fatal(err) | |
46 | } | |
47 | defer l.Close() | |
48 | addr := l.Addr() | |
49 | log.Printf("serving %q at %s", dir, addr) | |
50 | log.Fatal(http.Serve(l, allowCORS(http.FileServer(http.Dir(dir))))) | |
51 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net/url" | |
5 | "os" | |
6 | ) | |
7 | ||
8 | func main() { | |
9 | fmt.Println(url.QueryEscape(os.Args[1])) | |
10 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net/url" | |
5 | "os" | |
6 | ) | |
7 | ||
8 | func main() { | |
9 | fmt.Println(url.QueryUnescape(os.Args[1])) | |
10 | } |
0 | package conntrack | |
1 | ||
2 | import "expvar" | |
3 | ||
4 | type Protocol = string | |
5 | ||
6 | type Endpoint = string | |
7 | ||
8 | type Entry struct { | |
9 | Protocol | |
10 | LocalAddr Endpoint | |
11 | RemoteAddr Endpoint | |
12 | } | |
13 | ||
14 | var expvars = expvar.NewMap("conntrack") |
0 | package conntrack | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "math" | |
5 | "strconv" | |
6 | "sync" | |
7 | "testing" | |
8 | "time" | |
9 | ||
10 | "github.com/stretchr/testify/assert" | |
11 | ||
12 | _ "github.com/anacrolix/envpprof" | |
13 | "github.com/bradfitz/iter" | |
14 | ) | |
15 | ||
16 | func entry(id int) Entry { | |
17 | return Entry{"", "", strconv.FormatInt(int64(id), 10)} | |
18 | } | |
19 | ||
20 | func TestWaitingForSameEntry(t *testing.T) { | |
21 | i := NewInstance() | |
22 | i.SetMaxEntries(1) | |
23 | i.Timeout = func(Entry) time.Duration { | |
24 | return 0 | |
25 | } | |
26 | e1h1 := i.WaitDefault(context.Background(), entry(1)) | |
27 | gotE2s := make(chan struct{}) | |
28 | for range iter.N(2) { | |
29 | go func() { | |
30 | i.WaitDefault(context.Background(), entry(2)) | |
31 | gotE2s <- struct{}{} | |
32 | }() | |
33 | } | |
34 | gotE1 := make(chan struct{}) | |
35 | var e1h2 *EntryHandle | |
36 | go func() { | |
37 | e1h2 = i.WaitDefault(context.Background(), entry(1)) | |
38 | gotE1 <- struct{}{} | |
39 | }() | |
40 | select { | |
41 | case <-gotE1: | |
42 | case <-gotE2s: | |
43 | t.FailNow() | |
44 | } | |
45 | go e1h1.Done() | |
46 | go e1h2.Done() | |
47 | <-gotE2s | |
48 | <-gotE2s | |
49 | } | |
50 | ||
51 | func TestInstanceSetNoMaxEntries(t *testing.T) { | |
52 | i := NewInstance() | |
53 | i.SetMaxEntries(0) | |
54 | var wg sync.WaitGroup | |
55 | wait := func(e Entry, p priority) { | |
56 | i.Wait(context.Background(), e, "", p) | |
57 | wg.Done() | |
58 | } | |
59 | for _, e := range []Entry{entry(0), entry(1)} { | |
60 | for _, p := range []priority{math.MinInt32, math.MaxInt32} { | |
61 | wg.Add(1) | |
62 | go wait(e, p) | |
63 | } | |
64 | } | |
65 | waitForNumWaiters := func(num int) { | |
66 | i.mu.Lock() | |
67 | for len(i.waiters) != num { | |
68 | i.numWaitersChanged.Wait() | |
69 | } | |
70 | i.mu.Unlock() | |
71 | } | |
72 | waitForNumWaiters(4) | |
73 | i.SetNoMaxEntries() | |
74 | waitForNumWaiters(0) | |
75 | wg.Wait() | |
76 | } | |
77 | ||
78 | func TestWaitReturnsNilContextCompleted(t *testing.T) { | |
79 | i := NewInstance() | |
80 | i.SetMaxEntries(0) | |
81 | ctx, cancel := context.WithCancel(context.Background()) | |
82 | cancel() | |
83 | assert.Nil(t, i.WaitDefault(ctx, entry(0))) | |
84 | ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) | |
85 | assert.Nil(t, i.WaitDefault(ctx, entry(1))) | |
86 | cancel() | |
87 | } | |
88 | ||
89 | func TestWaitContextCanceledButRoomForEntry(t *testing.T) { | |
90 | i := NewInstance() | |
91 | i.SetMaxEntries(1) | |
92 | ctx, cancel := context.WithCancel(context.Background()) | |
93 | go cancel() | |
94 | eh := i.WaitDefault(ctx, entry(0)) | |
95 | if eh == nil { | |
96 | assert.Error(t, ctx.Err()) | |
97 | } else { | |
98 | eh.Done() | |
99 | } | |
100 | } | |
101 | ||
102 | func TestUnlimitedInstance(t *testing.T) { | |
103 | i := NewInstance() | |
104 | i.SetNoMaxEntries() | |
105 | i.Timeout = func(Entry) time.Duration { return 0 } | |
106 | eh := i.WaitDefault(context.Background(), entry(0)) | |
107 | assert.NotNil(t, eh) | |
108 | i.mu.Lock() | |
109 | assert.Len(t, i.entries[eh.e], 1) | |
110 | i.mu.Unlock() | |
111 | eh.Done() | |
112 | i.mu.Lock() | |
113 | assert.Nil(t, i.entries[eh.e]) | |
114 | i.mu.Unlock() | |
115 | } | |
116 | ||
117 | func TestUnlimitedInstanceContextCanceled(t *testing.T) { | |
118 | i := NewInstance() | |
119 | i.SetNoMaxEntries() | |
120 | i.Timeout = func(Entry) time.Duration { return 0 } | |
121 | ctx, cancel := context.WithCancel(context.Background()) | |
122 | cancel() | |
123 | eh := i.WaitDefault(ctx, entry(0)) | |
124 | assert.NotNil(t, eh) | |
125 | i.mu.Lock() | |
126 | assert.Len(t, i.entries[eh.e], 1) | |
127 | i.mu.Unlock() | |
128 | eh.Done() | |
129 | i.mu.Lock() | |
130 | assert.Nil(t, i.entries[eh.e]) | |
131 | i.mu.Unlock() | |
132 | } | |
133 | ||
134 | func TestContextCancelledWhileWaiting(t *testing.T) { | |
135 | i := NewInstance() | |
136 | i.SetMaxEntries(0) | |
137 | ctx, cancel := context.WithCancel(context.Background()) | |
138 | i.mu.Lock() | |
139 | assert.Len(t, i.waiters, 0) | |
140 | i.mu.Unlock() | |
141 | waitReturned := make(chan struct{}) | |
142 | go func() { | |
143 | eh := i.WaitDefault(ctx, entry(0)) | |
144 | assert.Nil(t, eh) | |
145 | close(waitReturned) | |
146 | }() | |
147 | for { | |
148 | i.mu.Lock() | |
149 | if len(i.waiters) == 1 { | |
150 | i.mu.Unlock() | |
151 | break | |
152 | } | |
153 | i.mu.Unlock() | |
154 | time.Sleep(time.Millisecond) | |
155 | } | |
156 | cancel() | |
157 | <-waitReturned | |
158 | assert.Len(t, i.entries, 0) | |
159 | assert.Len(t, i.waiters, 0) | |
160 | } | |
161 | ||
162 | func TestRaceWakeAndContextCompletion(t *testing.T) { | |
163 | i := NewInstance() | |
164 | i.SetMaxEntries(1) | |
165 | eh0 := i.WaitDefault(context.Background(), entry(0)) | |
166 | ctx, cancel := context.WithCancel(context.Background()) | |
167 | waitReturned := make(chan struct{}) | |
168 | go func() { | |
169 | eh1 := i.WaitDefault(ctx, entry(1)) | |
170 | if eh1 != nil { | |
171 | eh1.Forget() | |
172 | } | |
173 | close(waitReturned) | |
174 | }() | |
175 | go cancel() | |
176 | go eh0.Forget() | |
177 | <-waitReturned | |
178 | cancel() | |
179 | eh0.Forget() | |
180 | i.mu.Lock() | |
181 | assert.Len(t, i.entries, 0) | |
182 | assert.Len(t, i.waiters, 0) | |
183 | i.mu.Unlock() | |
184 | } |
0 | package conntrack | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | type EntryHandle struct { | |
8 | reason string | |
9 | e Entry | |
10 | priority priority | |
11 | i *Instance | |
12 | expires time.Time | |
13 | created time.Time | |
14 | wake sync.Mutex | |
15 | } | |
16 | ||
17 | func (eh *EntryHandle) Done() { | |
18 | expvars.Add("entry handles done", 1) | |
19 | timeout := eh.timeout() | |
20 | eh.expires = time.Now().Add(timeout) | |
21 | if timeout <= 0 { | |
22 | eh.remove() | |
23 | } else { | |
24 | time.AfterFunc(eh.timeout(), eh.remove) | |
25 | } | |
26 | } | |
27 | ||
28 | func (eh *EntryHandle) Forget() { | |
29 | expvars.Add("entry handles forgotten", 1) | |
30 | eh.remove() | |
31 | } | |
32 | ||
33 | func (eh *EntryHandle) remove() { | |
34 | eh.i.remove(eh) | |
35 | } | |
36 | ||
37 | func (eh *EntryHandle) timeout() time.Duration { | |
38 | return eh.i.Timeout(eh.e) | |
39 | } |
0 | package conntrack | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "fmt" | |
5 | "io" | |
6 | "sync" | |
7 | "text/tabwriter" | |
8 | "time" | |
9 | ||
10 | "github.com/anacrolix/missinggo/orderedmap" | |
11 | ) | |
12 | ||
13 | type reason = string | |
14 | ||
15 | type Instance struct { | |
16 | maxEntries int | |
17 | noMaxEntries bool | |
18 | Timeout func(Entry) time.Duration | |
19 | ||
20 | mu sync.Mutex | |
21 | // Occupied slots | |
22 | entries map[Entry]handles | |
23 | ||
24 | // priority to entryHandleSet, ordered by priority ascending | |
25 | waitersByPriority orderedmap.OrderedMap | |
26 | waitersByReason map[reason]entryHandleSet | |
27 | waitersByEntry map[Entry]entryHandleSet | |
28 | waiters entryHandleSet | |
29 | numWaitersChanged sync.Cond | |
30 | } | |
31 | ||
32 | type ( | |
33 | entryHandleSet = map[*EntryHandle]struct{} | |
34 | waitersByPriorityValue = entryHandleSet | |
35 | priority int | |
36 | handles = map[*EntryHandle]struct{} | |
37 | ) | |
38 | ||
39 | func NewInstance() *Instance { | |
40 | i := &Instance{ | |
41 | // A quarter of the commonly quoted absolute max on a Linux system. | |
42 | maxEntries: 1 << 14, | |
43 | Timeout: func(e Entry) time.Duration { | |
44 | // udp is the main offender, and the default is allegedly 30s. | |
45 | return 30 * time.Second | |
46 | }, | |
47 | entries: make(map[Entry]handles), | |
48 | waitersByPriority: orderedmap.New(func(_l, _r interface{}) bool { | |
49 | return _l.(priority) > _r.(priority) | |
50 | }), | |
51 | waitersByReason: make(map[reason]entryHandleSet), | |
52 | waitersByEntry: make(map[Entry]entryHandleSet), | |
53 | waiters: make(entryHandleSet), | |
54 | } | |
55 | i.numWaitersChanged.L = &i.mu | |
56 | return i | |
57 | } | |
58 | ||
59 | func (i *Instance) SetNoMaxEntries() { | |
60 | i.mu.Lock() | |
61 | defer i.mu.Unlock() | |
62 | i.noMaxEntries = true | |
63 | i.wakeAll() | |
64 | } | |
65 | ||
66 | func (i *Instance) SetMaxEntries(max int) { | |
67 | i.mu.Lock() | |
68 | defer i.mu.Unlock() | |
69 | i.noMaxEntries = false | |
70 | prev := i.maxEntries | |
71 | i.maxEntries = max | |
72 | for j := prev; j < max; j++ { | |
73 | i.wakeOne() | |
74 | } | |
75 | } | |
76 | ||
77 | func (i *Instance) remove(eh *EntryHandle) { | |
78 | i.mu.Lock() | |
79 | defer i.mu.Unlock() | |
80 | hs := i.entries[eh.e] | |
81 | delete(hs, eh) | |
82 | if len(hs) == 0 { | |
83 | delete(i.entries, eh.e) | |
84 | i.wakeOne() | |
85 | } | |
86 | } | |
87 | ||
88 | // Wakes all waiters. | |
89 | func (i *Instance) wakeAll() { | |
90 | for len(i.waiters) != 0 { | |
91 | i.wakeOne() | |
92 | } | |
93 | } | |
94 | ||
95 | // Wakes the highest priority waiter. | |
96 | func (i *Instance) wakeOne() { | |
97 | i.waitersByPriority.Iter(func(key interface{}) bool { | |
98 | value := i.waitersByPriority.Get(key).(entryHandleSet) | |
99 | for eh := range value { | |
100 | i.wakeEntry(eh.e) | |
101 | break | |
102 | } | |
103 | return false | |
104 | }) | |
105 | } | |
106 | ||
107 | func (i *Instance) deleteWaiter(eh *EntryHandle) { | |
108 | delete(i.waiters, eh) | |
109 | p := i.waitersByPriority.Get(eh.priority).(entryHandleSet) | |
110 | delete(p, eh) | |
111 | if len(p) == 0 { | |
112 | i.waitersByPriority.Unset(eh.priority) | |
113 | } | |
114 | r := i.waitersByReason[eh.reason] | |
115 | delete(r, eh) | |
116 | if len(r) == 0 { | |
117 | delete(i.waitersByReason, eh.reason) | |
118 | } | |
119 | e := i.waitersByEntry[eh.e] | |
120 | delete(e, eh) | |
121 | if len(e) == 0 { | |
122 | delete(i.waitersByEntry, eh.e) | |
123 | } | |
124 | i.numWaitersChanged.Broadcast() | |
125 | } | |
126 | ||
127 | func (i *Instance) addWaiter(eh *EntryHandle) { | |
128 | p, ok := i.waitersByPriority.GetOk(eh.priority) | |
129 | if ok { | |
130 | p.(entryHandleSet)[eh] = struct{}{} | |
131 | } else { | |
132 | i.waitersByPriority.Set(eh.priority, entryHandleSet{eh: struct{}{}}) | |
133 | } | |
134 | if r := i.waitersByReason[eh.reason]; r == nil { | |
135 | i.waitersByReason[eh.reason] = entryHandleSet{eh: struct{}{}} | |
136 | } else { | |
137 | r[eh] = struct{}{} | |
138 | } | |
139 | if e := i.waitersByEntry[eh.e]; e == nil { | |
140 | i.waitersByEntry[eh.e] = entryHandleSet{eh: struct{}{}} | |
141 | } else { | |
142 | e[eh] = struct{}{} | |
143 | } | |
144 | i.waiters[eh] = struct{}{} | |
145 | i.numWaitersChanged.Broadcast() | |
146 | } | |
147 | ||
148 | // Wakes all waiters on an entry. Note that the entry is also woken | |
149 | // immediately, the waiters are all let through. | |
150 | func (i *Instance) wakeEntry(e Entry) { | |
151 | if _, ok := i.entries[e]; ok { | |
152 | panic(e) | |
153 | } | |
154 | i.entries[e] = make(handles, len(i.waitersByEntry[e])) | |
155 | for eh := range i.waitersByEntry[e] { | |
156 | i.entries[e][eh] = struct{}{} | |
157 | i.deleteWaiter(eh) | |
158 | eh.wake.Unlock() | |
159 | } | |
160 | if i.waitersByEntry[e] != nil { | |
161 | panic(i.waitersByEntry[e]) | |
162 | } | |
163 | } | |
164 | ||
165 | func (i *Instance) WaitDefault(ctx context.Context, e Entry) *EntryHandle { | |
166 | return i.Wait(ctx, e, "", 0) | |
167 | } | |
168 | ||
169 | // Nil returns are due to context completion. | |
170 | func (i *Instance) Wait(ctx context.Context, e Entry, reason string, p priority) (eh *EntryHandle) { | |
171 | eh = &EntryHandle{ | |
172 | reason: reason, | |
173 | e: e, | |
174 | i: i, | |
175 | priority: p, | |
176 | created: time.Now(), | |
177 | } | |
178 | i.mu.Lock() | |
179 | hs, ok := i.entries[eh.e] | |
180 | if ok { | |
181 | hs[eh] = struct{}{} | |
182 | i.mu.Unlock() | |
183 | expvars.Add("waits for existing entry", 1) | |
184 | return | |
185 | } | |
186 | if i.noMaxEntries || len(i.entries) < i.maxEntries { | |
187 | i.entries[eh.e] = handles{ | |
188 | eh: struct{}{}, | |
189 | } | |
190 | i.mu.Unlock() | |
191 | expvars.Add("waits with space in table", 1) | |
192 | return | |
193 | } | |
194 | // Lock the mutex, so that a following Lock will block until it's unlocked by a wake event. | |
195 | eh.wake.Lock() | |
196 | i.addWaiter(eh) | |
197 | i.mu.Unlock() | |
198 | expvars.Add("waits that blocked", 1) | |
199 | ctx, cancel := context.WithCancel(ctx) | |
200 | defer cancel() | |
201 | go func() { | |
202 | <-ctx.Done() | |
203 | i.mu.Lock() | |
204 | if _, ok := i.waiters[eh]; ok { | |
205 | i.deleteWaiter(eh) | |
206 | eh.wake.Unlock() | |
207 | } | |
208 | i.mu.Unlock() | |
209 | }() | |
210 | // Blocks until woken by an Unlock. | |
211 | eh.wake.Lock() | |
212 | i.mu.Lock() | |
213 | if _, ok := i.entries[eh.e][eh]; !ok { | |
214 | eh = nil | |
215 | } | |
216 | i.mu.Unlock() | |
217 | return | |
218 | } | |
219 | ||
220 | func (i *Instance) PrintStatus(w io.Writer) { | |
221 | tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) | |
222 | i.mu.Lock() | |
223 | fmt.Fprintf(w, "num entries: %d\n", len(i.entries)) | |
224 | fmt.Fprintln(w) | |
225 | fmt.Fprintf(w, "%d waiters:\n", len(i.waiters)) | |
226 | fmt.Fprintf(tw, "num\treason\n") | |
227 | for r, ws := range i.waitersByReason { | |
228 | fmt.Fprintf(tw, "%d\t%q\n", len(ws), r) | |
229 | } | |
230 | tw.Flush() | |
231 | fmt.Fprintln(w) | |
232 | fmt.Fprintln(w, "handles:") | |
233 | fmt.Fprintf(tw, "protocol\tlocal\tremote\treason\texpires\tcreated\n") | |
234 | for e, hs := range i.entries { | |
235 | for h := range hs { | |
236 | fmt.Fprintf(tw, | |
237 | "%q\t%q\t%q\t%q\t%s\t%v ago\n", | |
238 | e.Protocol, e.LocalAddr, e.RemoteAddr, h.reason, | |
239 | func() interface{} { | |
240 | if h.expires.IsZero() { | |
241 | return "not done" | |
242 | } else { | |
243 | return time.Until(h.expires) | |
244 | } | |
245 | }(), | |
246 | time.Since(h.created), | |
247 | ) | |
248 | } | |
249 | } | |
250 | i.mu.Unlock() | |
251 | tw.Flush() | |
252 | } |
0 | package xheap | |
1 | ||
2 | import ( | |
3 | "container/heap" | |
4 | "sort" | |
5 | ) | |
6 | ||
7 | type pushPopper interface { | |
8 | Push(interface{}) | |
9 | Pop() interface{} | |
10 | } | |
11 | ||
12 | func Flipped(h heap.Interface) heap.Interface { | |
13 | return struct { | |
14 | sort.Interface | |
15 | pushPopper | |
16 | }{ | |
17 | sort.Reverse(h), | |
18 | h, | |
19 | } | |
20 | } | |
21 | ||
22 | // type top struct { | |
23 | // k int | |
24 | // heap.Interface | |
25 | // } | |
26 | ||
27 | // func (me top) Push(x interface{}) { | |
28 | // heap.Push(me.Interface, x) | |
29 | // if me.Len() > me.k { | |
30 | // heap.Pop(me) | |
31 | // } | |
32 | // } | |
33 | ||
34 | // func Bounded(k int, h heap.Interface) heap.Interface { | |
35 | // return top{k, Flipped(h)} | |
36 | // } | |
37 | ||
38 | type slice struct { | |
39 | Slice *[]interface{} | |
40 | Lesser func(l, r interface{}) bool | |
41 | } | |
42 | ||
43 | func (me slice) Len() int { return len(*me.Slice) } | |
44 | ||
45 | func (me slice) Less(i, j int) bool { | |
46 | return me.Lesser((*me.Slice)[i], (*me.Slice)[j]) | |
47 | } | |
48 | ||
49 | func (me slice) Pop() (ret interface{}) { | |
50 | i := me.Len() - 1 | |
51 | ret = (*me.Slice)[i] | |
52 | *me.Slice = (*me.Slice)[:i] | |
53 | return | |
54 | } | |
55 | ||
56 | func (me slice) Push(x interface{}) { | |
57 | *me.Slice = append(*me.Slice, x) | |
58 | } | |
59 | ||
60 | func (me slice) Swap(i, j int) { | |
61 | sl := *me.Slice | |
62 | sl[i], sl[j] = sl[j], sl[i] | |
63 | } | |
64 | ||
65 | func Slice(sl *[]interface{}, lesser func(l, r interface{}) bool) heap.Interface { | |
66 | return slice{sl, lesser} | |
67 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "reflect" | |
5 | ) | |
6 | ||
7 | // Copy elements from src to dst. Panics if the length of src and dst are | |
8 | // different. | |
9 | func CopyExact(dest interface{}, src interface{}) { | |
10 | dV := reflect.ValueOf(dest) | |
11 | sV := reflect.ValueOf(src) | |
12 | if dV.Kind() == reflect.Ptr { | |
13 | dV = dV.Elem() | |
14 | } | |
15 | if dV.Kind() == reflect.Array && !dV.CanAddr() { | |
16 | panic(fmt.Sprintf("dest not addressable: %T", dest)) | |
17 | } | |
18 | if sV.Kind() == reflect.Ptr { | |
19 | sV = sV.Elem() | |
20 | } | |
21 | if sV.Kind() == reflect.String { | |
22 | sV = sV.Convert(reflect.SliceOf(dV.Type().Elem())) | |
23 | } | |
24 | if !sV.IsValid() { | |
25 | panic("invalid source, probably nil") | |
26 | } | |
27 | if dV.Len() != sV.Len() { | |
28 | panic(fmt.Sprintf("dest len (%d) != src len (%d)", dV.Len(), sV.Len())) | |
29 | } | |
30 | if dV.Len() != reflect.Copy(dV, sV) { | |
31 | panic("dammit") | |
32 | } | |
33 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "strings" | |
5 | "testing" | |
6 | ) | |
7 | ||
8 | func TestCopyToArray(t *testing.T) { | |
9 | var arr [3]byte | |
10 | bb := []byte{1, 2, 3} | |
11 | CopyExact(&arr, bb) | |
12 | if !bytes.Equal(arr[:], bb) { | |
13 | t.FailNow() | |
14 | } | |
15 | } | |
16 | ||
17 | func TestCopyToSlicedArray(t *testing.T) { | |
18 | var arr [5]byte | |
19 | CopyExact(arr[:], "hello") | |
20 | if !bytes.Equal(arr[:], []byte("hello")) { | |
21 | t.FailNow() | |
22 | } | |
23 | } | |
24 | ||
25 | func TestCopyDestNotAddr(t *testing.T) { | |
26 | defer func() { | |
27 | r := recover() | |
28 | if r == nil { | |
29 | t.FailNow() | |
30 | } | |
31 | t.Log(r) | |
32 | }() | |
33 | var arr [3]byte | |
34 | CopyExact(arr, "nope") | |
35 | } | |
36 | ||
37 | func TestCopyLenMismatch(t *testing.T) { | |
38 | defer func() { | |
39 | r := recover() | |
40 | if r == nil { | |
41 | t.FailNow() | |
42 | } | |
43 | t.Log(r) | |
44 | }() | |
45 | CopyExact(make([]byte, 2), "abc") | |
46 | } | |
47 | ||
48 | func TestCopySrcString(t *testing.T) { | |
49 | dest := make([]byte, 3) | |
50 | CopyExact(dest, "lol") | |
51 | if string(dest) != "lol" { | |
52 | t.FailNow() | |
53 | } | |
54 | func() { | |
55 | defer func() { | |
56 | r := recover() | |
57 | if r == nil { | |
58 | t.FailNow() | |
59 | } | |
60 | }() | |
61 | CopyExact(dest, "rofl") | |
62 | }() | |
63 | var arr [5]byte | |
64 | CopyExact(&arr, interface{}("hello")) | |
65 | if string(arr[:]) != "hello" { | |
66 | t.FailNow() | |
67 | } | |
68 | } | |
69 | ||
70 | func TestCopySrcNilInterface(t *testing.T) { | |
71 | var arr [3]byte | |
72 | defer func() { | |
73 | r := recover().(string) | |
74 | if !strings.Contains(r, "invalid source") { | |
75 | t.FailNow() | |
76 | } | |
77 | }() | |
78 | CopyExact(&arr, nil) | |
79 | } | |
80 | ||
81 | func TestCopySrcPtr(t *testing.T) { | |
82 | var bigDst [1024]byte | |
83 | var bigSrc [1024]byte = [1024]byte{'h', 'i'} | |
84 | CopyExact(&bigDst, &bigSrc) | |
85 | if !bytes.Equal(bigDst[:], bigSrc[:]) { | |
86 | t.FailNow() | |
87 | } | |
88 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "os" | |
5 | ) | |
6 | ||
7 | func Unchomp(s string) string { | |
8 | if len(s) > 0 && s[len(s)-1] == '\n' { | |
9 | return s | |
10 | } | |
11 | return s + "\n" | |
12 | } | |
13 | ||
14 | func Fatal(msg interface{}) { | |
15 | os.Stderr.WriteString(Unchomp(fmt.Sprint(msg))) | |
16 | os.Exit(1) | |
17 | } |
0 | package ctrlflow | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ) | |
5 | ||
6 | type valueWrapper struct { | |
7 | value interface{} | |
8 | } | |
9 | ||
10 | func (me valueWrapper) String() string { | |
11 | return fmt.Sprint(me.value) | |
12 | } | |
13 | ||
14 | func Panic(val interface{}) { | |
15 | panic(valueWrapper{val}) | |
16 | } | |
17 | ||
18 | func Recover(handler func(interface{}) bool) { | |
19 | r := recover() | |
20 | if r == nil { | |
21 | return | |
22 | } | |
23 | if vw, ok := r.(valueWrapper); ok { | |
24 | if handler(vw.value) { | |
25 | return | |
26 | } | |
27 | } | |
28 | panic(r) | |
29 | } |
0 | // Package missinggo contains miscellaneous helpers used in many of anacrolix' | |
1 | // projects. | |
2 | package missinggo |
0 | package docopt | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "os" | |
5 | ||
6 | "github.com/docopt/docopt-go" | |
7 | ) | |
8 | ||
9 | func Parse(doc string) (opts map[string]interface{}) { | |
10 | opts, err := docopt.Parse(doc, nil, true, "1.2.3", false, false) | |
11 | if ue, ok := err.(*docopt.UserError); ok { | |
12 | if ue.Error() != "" { | |
13 | fmt.Fprintf(os.Stderr, "\n%s\n", ue) | |
14 | } | |
15 | os.Exit(2) | |
16 | } | |
17 | if err != nil { | |
18 | fmt.Fprintf(os.Stderr, "error parsing docopt: %#v\n", err) | |
19 | os.Exit(1) | |
20 | } | |
21 | return | |
22 | } |
0 | package missinggo | |
1 | ||
2 | import "reflect" | |
3 | ||
4 | func IsZeroValue(i interface{}) bool { | |
5 | return IsEmptyValue(reflect.ValueOf(i)) | |
6 | } | |
7 | ||
8 | // Returns whether the value represents the empty value for its type. Used for | |
9 | // example to determine if complex types satisfy the common "omitempty" tag | |
10 | // option for marshalling. Taken from | |
11 | // http://stackoverflow.com/a/23555352/149482. | |
12 | func IsEmptyValue(v reflect.Value) bool { | |
13 | switch v.Kind() { | |
14 | case reflect.Func, reflect.Map, reflect.Slice: | |
15 | return v.IsNil() | |
16 | case reflect.Array: | |
17 | z := true | |
18 | for i := 0; i < v.Len(); i++ { | |
19 | z = z && IsEmptyValue(v.Index(i)) | |
20 | } | |
21 | return z | |
22 | case reflect.Struct: | |
23 | z := true | |
24 | for i := 0; i < v.NumField(); i++ { | |
25 | z = z && IsEmptyValue(v.Field(i)) | |
26 | } | |
27 | return z | |
28 | } | |
29 | // Compare other types directly: | |
30 | z := reflect.Zero(v.Type()) | |
31 | return v.Interface() == z.Interface() | |
32 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func TestEmptyValue(t *testing.T) { | |
10 | assert.True(t, IsEmptyValue(reflect.ValueOf(false))) | |
11 | assert.False(t, IsEmptyValue(reflect.ValueOf(true))) | |
12 | } |
0 | package missinggo | |
1 | ||
2 | // An interface for "encoding/base64".Encoder | |
3 | type Encoding interface { | |
4 | EncodeToString([]byte) string | |
5 | DecodeString(string) ([]byte, error) | |
6 | } | |
7 | ||
8 | // An encoding that does nothing. | |
9 | type IdentityEncoding struct{} | |
10 | ||
11 | var _ Encoding = IdentityEncoding{} | |
12 | ||
13 | func (IdentityEncoding) EncodeToString(b []byte) string { return string(b) } | |
14 | func (IdentityEncoding) DecodeString(s string) ([]byte, error) { return []byte(s), nil } |
0 | package missinggo | |
1 | ||
2 | import "sync" | |
3 | ||
4 | // Events are boolean flags that provide a channel that's closed when true. | |
5 | // This could go in the sync package, but that's more of a debug wrapper on | |
6 | // the standard library sync. | |
7 | type Event struct { | |
8 | ch chan struct{} | |
9 | closed bool | |
10 | } | |
11 | ||
12 | func (me *Event) LockedChan(lock sync.Locker) <-chan struct{} { | |
13 | lock.Lock() | |
14 | ch := me.C() | |
15 | lock.Unlock() | |
16 | return ch | |
17 | } | |
18 | ||
19 | // Returns a chan that is closed when the event is true. | |
20 | func (me *Event) C() <-chan struct{} { | |
21 | if me.ch == nil { | |
22 | me.ch = make(chan struct{}) | |
23 | } | |
24 | return me.ch | |
25 | } | |
26 | ||
27 | // TODO: Merge into Set. | |
28 | func (me *Event) Clear() { | |
29 | if me.closed { | |
30 | me.ch = nil | |
31 | me.closed = false | |
32 | } | |
33 | } | |
34 | ||
35 | // Set the event to true/on. | |
36 | func (me *Event) Set() (first bool) { | |
37 | if me.closed { | |
38 | return false | |
39 | } | |
40 | if me.ch == nil { | |
41 | me.ch = make(chan struct{}) | |
42 | } | |
43 | close(me.ch) | |
44 | me.closed = true | |
45 | return true | |
46 | } | |
47 | ||
48 | // TODO: Change to Get. | |
49 | func (me *Event) IsSet() bool { | |
50 | return me.closed | |
51 | } | |
52 | ||
53 | func (me *Event) Wait() { | |
54 | <-me.C() | |
55 | } | |
56 | ||
57 | // TODO: Merge into Set. | |
58 | func (me *Event) SetBool(b bool) { | |
59 | if b { | |
60 | me.Set() | |
61 | } else { | |
62 | me.Clear() | |
63 | } | |
64 | } |
0 | package missinggo | |
1 | ||
2 | import "sync" | |
3 | ||
4 | type SynchronizedEvent struct { | |
5 | mu sync.Mutex | |
6 | e Event | |
7 | } | |
8 | ||
9 | func (me *SynchronizedEvent) Set() { | |
10 | me.mu.Lock() | |
11 | me.e.Set() | |
12 | me.mu.Unlock() | |
13 | } | |
14 | ||
15 | func (me *SynchronizedEvent) Clear() { | |
16 | me.mu.Lock() | |
17 | me.e.Clear() | |
18 | me.mu.Unlock() | |
19 | } | |
20 | ||
21 | func (me *SynchronizedEvent) C() <-chan struct{} { | |
22 | me.mu.Lock() | |
23 | defer me.mu.Unlock() | |
24 | return me.e.C() | |
25 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestSetEvent(t *testing.T) { | |
9 | var e Event | |
10 | e.Set() | |
11 | } | |
12 | ||
13 | func TestEventIsSet(t *testing.T) { | |
14 | var e Event | |
15 | assert.False(t, e.IsSet()) | |
16 | e.Set() | |
17 | assert.True(t, e.IsSet()) | |
18 | } |
0 | package expect // import "github.com/anacrolix/missinggo/expect" | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "fmt" | |
5 | "reflect" | |
6 | ) | |
7 | ||
8 | func Nil(x interface{}) { | |
9 | if x != nil { | |
10 | panic(fmt.Sprintf("expected nil; got %v", x)) | |
11 | } | |
12 | } | |
13 | ||
14 | func NotNil(x interface{}) { | |
15 | if x == nil { | |
16 | panic(x) | |
17 | } | |
18 | } | |
19 | ||
20 | func Equal(x, y interface{}) { | |
21 | if x == y { | |
22 | return | |
23 | } | |
24 | yAsXType := reflect.ValueOf(y).Convert(reflect.TypeOf(x)).Interface() | |
25 | if !reflect.DeepEqual(x, yAsXType) { | |
26 | panic(fmt.Sprintf("%v != %v", x, y)) | |
27 | } | |
28 | } | |
29 | ||
30 | func StrictlyEqual(x, y interface{}) { | |
31 | if x != y { | |
32 | panic(fmt.Sprintf("%s != %s", x, y)) | |
33 | } | |
34 | } | |
35 | ||
36 | func OneRowAffected(r sql.Result) { | |
37 | count, err := r.RowsAffected() | |
38 | Nil(err) | |
39 | if count != 1 { | |
40 | panic(count) | |
41 | } | |
42 | } | |
43 | ||
44 | func True(b bool) { | |
45 | if !b { | |
46 | panic(b) | |
47 | } | |
48 | } | |
49 | ||
50 | var Ok = True | |
51 | ||
52 | func False(b bool) { | |
53 | if b { | |
54 | panic(b) | |
55 | } | |
56 | } | |
57 | ||
58 | func Zero(x interface{}) { | |
59 | if x != reflect.Zero(reflect.TypeOf(x)).Interface() { | |
60 | panic(x) | |
61 | } | |
62 | } |
0 | package expect | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestEqualDifferentIntTypes(t *testing.T) { | |
9 | var a int = 1 | |
10 | var b int64 = 1 | |
11 | assert.EqualValues(t, a, b) | |
12 | assert.NotEqual(t, a, b) | |
13 | assert.NotPanics(t, func() { Equal(a, b) }) | |
14 | assert.Panics(t, func() { StrictlyEqual(a, b) }) | |
15 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "expvar" | |
5 | "fmt" | |
6 | ) | |
7 | ||
8 | type IndentMap struct { | |
9 | expvar.Map | |
10 | } | |
11 | ||
12 | var _ expvar.Var = (*IndentMap)(nil) | |
13 | ||
14 | func NewExpvarIndentMap(name string) *IndentMap { | |
15 | v := new(IndentMap) | |
16 | v.Init() | |
17 | expvar.Publish(name, v) | |
18 | return v | |
19 | } | |
20 | ||
21 | func (v *IndentMap) String() string { | |
22 | var b bytes.Buffer | |
23 | fmt.Fprintf(&b, "{") | |
24 | first := true | |
25 | v.Do(func(kv expvar.KeyValue) { | |
26 | if !first { | |
27 | fmt.Fprintf(&b, ",") | |
28 | } | |
29 | fmt.Fprintf(&b, "\n\t%q: %v", kv.Key, kv.Value) | |
30 | first = false | |
31 | }) | |
32 | fmt.Fprintf(&b, "}") | |
33 | return b.String() | |
34 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "log" | |
5 | "os" | |
6 | "path" | |
7 | "path/filepath" | |
8 | "sync" | |
9 | "time" | |
10 | ||
11 | "github.com/anacrolix/missinggo/pproffd" | |
12 | "github.com/anacrolix/missinggo/resource" | |
13 | ) | |
14 | ||
15 | const ( | |
16 | dirPerm = 0755 | |
17 | filePerm = 0644 | |
18 | ) | |
19 | ||
20 | type Cache struct { | |
21 | root string | |
22 | mu sync.Mutex | |
23 | capacity int64 | |
24 | filled int64 | |
25 | policy Policy | |
26 | items map[key]itemState | |
27 | } | |
28 | ||
29 | type CacheInfo struct { | |
30 | Capacity int64 | |
31 | Filled int64 | |
32 | NumItems int | |
33 | } | |
34 | ||
35 | type ItemInfo struct { | |
36 | Path key | |
37 | Accessed time.Time | |
38 | Size int64 | |
39 | } | |
40 | ||
41 | // Calls the function for every item known to be in the cache. | |
42 | func (me *Cache) WalkItems(cb func(ItemInfo)) { | |
43 | me.mu.Lock() | |
44 | defer me.mu.Unlock() | |
45 | for k, ii := range me.items { | |
46 | cb(ItemInfo{ | |
47 | Path: k, | |
48 | Accessed: ii.Accessed, | |
49 | Size: ii.Size, | |
50 | }) | |
51 | } | |
52 | } | |
53 | ||
54 | func (me *Cache) Info() (ret CacheInfo) { | |
55 | me.mu.Lock() | |
56 | defer me.mu.Unlock() | |
57 | ret.Capacity = me.capacity | |
58 | ret.Filled = me.filled | |
59 | ret.NumItems = len(me.items) | |
60 | return | |
61 | } | |
62 | ||
63 | // Setting a negative capacity means unlimited. | |
64 | func (me *Cache) SetCapacity(capacity int64) { | |
65 | me.mu.Lock() | |
66 | defer me.mu.Unlock() | |
67 | me.capacity = capacity | |
68 | } | |
69 | ||
70 | func NewCache(root string) (ret *Cache, err error) { | |
71 | root, err = filepath.Abs(root) | |
72 | ret = &Cache{ | |
73 | root: root, | |
74 | capacity: -1, // unlimited | |
75 | } | |
76 | ret.mu.Lock() | |
77 | go func() { | |
78 | defer ret.mu.Unlock() | |
79 | ret.rescan() | |
80 | }() | |
81 | return | |
82 | } | |
83 | ||
84 | // An empty return path is an error. | |
85 | func sanitizePath(p string) (ret key) { | |
86 | if p == "" { | |
87 | return | |
88 | } | |
89 | ret = key(path.Clean("/" + p)) | |
90 | if ret[0] == '/' { | |
91 | ret = ret[1:] | |
92 | } | |
93 | return | |
94 | } | |
95 | ||
96 | // Leaf is a descendent of root. | |
97 | func pruneEmptyDirs(root string, leaf string) (err error) { | |
98 | rootInfo, err := os.Stat(root) | |
99 | if err != nil { | |
100 | return | |
101 | } | |
102 | for { | |
103 | var leafInfo os.FileInfo | |
104 | leafInfo, err = os.Stat(leaf) | |
105 | if os.IsNotExist(err) { | |
106 | goto parent | |
107 | } | |
108 | if err != nil { | |
109 | return | |
110 | } | |
111 | if !leafInfo.IsDir() { | |
112 | return | |
113 | } | |
114 | if os.SameFile(rootInfo, leafInfo) { | |
115 | return | |
116 | } | |
117 | if os.Remove(leaf) != nil { | |
118 | return | |
119 | } | |
120 | parent: | |
121 | leaf = filepath.Dir(leaf) | |
122 | } | |
123 | } | |
124 | ||
125 | func (me *Cache) Remove(path string) error { | |
126 | me.mu.Lock() | |
127 | defer me.mu.Unlock() | |
128 | return me.remove(sanitizePath(path)) | |
129 | } | |
130 | ||
131 | var ( | |
132 | ErrBadPath = errors.New("bad path") | |
133 | ErrIsDir = errors.New("is directory") | |
134 | ) | |
135 | ||
136 | func (me *Cache) StatFile(path string) (os.FileInfo, error) { | |
137 | return os.Stat(me.realpath(sanitizePath(path))) | |
138 | } | |
139 | ||
140 | func (me *Cache) OpenFile(path string, flag int) (ret *File, err error) { | |
141 | key := sanitizePath(path) | |
142 | if key == "" { | |
143 | err = ErrIsDir | |
144 | return | |
145 | } | |
146 | f, err := os.OpenFile(me.realpath(key), flag, filePerm) | |
147 | if flag&os.O_CREATE != 0 && os.IsNotExist(err) { | |
148 | // Ensure intermediate directories and try again. | |
149 | dirErr := os.MkdirAll(filepath.Dir(me.realpath(key)), dirPerm) | |
150 | f, err = os.OpenFile(me.realpath(key), flag, filePerm) | |
151 | if dirErr != nil && os.IsNotExist(err) { | |
152 | return nil, dirErr | |
153 | } | |
154 | if err != nil { | |
155 | go me.pruneEmptyDirs(key) | |
156 | } | |
157 | } | |
158 | if err != nil { | |
159 | return | |
160 | } | |
161 | ret = &File{ | |
162 | path: key, | |
163 | f: pproffd.WrapOSFile(f), | |
164 | onRead: func(n int) { | |
165 | me.mu.Lock() | |
166 | defer me.mu.Unlock() | |
167 | me.updateItem(key, func(i *itemState, ok bool) bool { | |
168 | i.Accessed = time.Now() | |
169 | return ok | |
170 | }) | |
171 | }, | |
172 | afterWrite: func(endOff int64) { | |
173 | me.mu.Lock() | |
174 | defer me.mu.Unlock() | |
175 | me.updateItem(key, func(i *itemState, ok bool) bool { | |
176 | i.Accessed = time.Now() | |
177 | if endOff > i.Size { | |
178 | i.Size = endOff | |
179 | } | |
180 | return ok | |
181 | }) | |
182 | }, | |
183 | } | |
184 | me.mu.Lock() | |
185 | defer me.mu.Unlock() | |
186 | me.updateItem(key, func(i *itemState, ok bool) bool { | |
187 | if !ok { | |
188 | *i, ok = me.statKey(key) | |
189 | } | |
190 | i.Accessed = time.Now() | |
191 | return ok | |
192 | }) | |
193 | return | |
194 | } | |
195 | ||
196 | func (me *Cache) rescan() { | |
197 | me.filled = 0 | |
198 | me.policy = new(lru) | |
199 | me.items = make(map[key]itemState) | |
200 | err := filepath.Walk(me.root, func(path string, info os.FileInfo, err error) error { | |
201 | if os.IsNotExist(err) { | |
202 | return nil | |
203 | } | |
204 | if err != nil { | |
205 | return err | |
206 | } | |
207 | if info.IsDir() { | |
208 | return nil | |
209 | } | |
210 | path, err = filepath.Rel(me.root, path) | |
211 | if err != nil { | |
212 | log.Print(err) | |
213 | return nil | |
214 | } | |
215 | key := sanitizePath(path) | |
216 | me.updateItem(key, func(i *itemState, ok bool) bool { | |
217 | if ok { | |
218 | panic("scanned duplicate items") | |
219 | } | |
220 | *i, ok = me.statKey(key) | |
221 | return ok | |
222 | }) | |
223 | return nil | |
224 | }) | |
225 | if err != nil { | |
226 | panic(err) | |
227 | } | |
228 | } | |
229 | ||
230 | func (me *Cache) statKey(k key) (i itemState, ok bool) { | |
231 | fi, err := os.Stat(me.realpath(k)) | |
232 | if os.IsNotExist(err) { | |
233 | return | |
234 | } | |
235 | if err != nil { | |
236 | panic(err) | |
237 | } | |
238 | i.FromOSFileInfo(fi) | |
239 | ok = true | |
240 | return | |
241 | } | |
242 | ||
243 | func (me *Cache) updateItem(k key, u func(*itemState, bool) bool) { | |
244 | ii, ok := me.items[k] | |
245 | me.filled -= ii.Size | |
246 | if u(&ii, ok) { | |
247 | me.filled += ii.Size | |
248 | me.policy.Used(k, ii.Accessed) | |
249 | me.items[k] = ii | |
250 | } else { | |
251 | me.policy.Forget(k) | |
252 | delete(me.items, k) | |
253 | } | |
254 | me.trimToCapacity() | |
255 | } | |
256 | ||
257 | func (me *Cache) realpath(path key) string { | |
258 | return filepath.Join(me.root, filepath.FromSlash(string(path))) | |
259 | } | |
260 | ||
261 | func (me *Cache) TrimToCapacity() { | |
262 | me.mu.Lock() | |
263 | defer me.mu.Unlock() | |
264 | me.trimToCapacity() | |
265 | } | |
266 | ||
267 | func (me *Cache) pruneEmptyDirs(path key) { | |
268 | pruneEmptyDirs(me.root, me.realpath(path)) | |
269 | } | |
270 | ||
271 | func (me *Cache) remove(path key) error { | |
272 | err := os.Remove(me.realpath(path)) | |
273 | if os.IsNotExist(err) { | |
274 | err = nil | |
275 | } | |
276 | if err != nil { | |
277 | return err | |
278 | } | |
279 | me.pruneEmptyDirs(path) | |
280 | me.updateItem(path, func(*itemState, bool) bool { | |
281 | return false | |
282 | }) | |
283 | return nil | |
284 | } | |
285 | ||
286 | func (me *Cache) trimToCapacity() { | |
287 | if me.capacity < 0 { | |
288 | return | |
289 | } | |
290 | for me.filled > me.capacity { | |
291 | me.remove(me.policy.Choose().(key)) | |
292 | } | |
293 | } | |
294 | ||
295 | // TODO: Do I need this? | |
296 | func (me *Cache) pathInfo(p string) itemState { | |
297 | return me.items[sanitizePath(p)] | |
298 | } | |
299 | ||
300 | func (me *Cache) Rename(from, to string) (err error) { | |
301 | _from := sanitizePath(from) | |
302 | _to := sanitizePath(to) | |
303 | me.mu.Lock() | |
304 | defer me.mu.Unlock() | |
305 | err = os.MkdirAll(filepath.Dir(me.realpath(_to)), dirPerm) | |
306 | if err != nil { | |
307 | return | |
308 | } | |
309 | err = os.Rename(me.realpath(_from), me.realpath(_to)) | |
310 | if err != nil { | |
311 | return | |
312 | } | |
313 | // We can do a dance here to copy the state from the old item, but lets | |
314 | // just stat the new item for now. | |
315 | me.updateItem(_from, func(i *itemState, ok bool) bool { | |
316 | return false | |
317 | }) | |
318 | me.updateItem(_to, func(i *itemState, ok bool) bool { | |
319 | *i, ok = me.statKey(_to) | |
320 | return ok | |
321 | }) | |
322 | return | |
323 | } | |
324 | ||
325 | func (me *Cache) Stat(path string) (os.FileInfo, error) { | |
326 | return os.Stat(me.realpath(sanitizePath(path))) | |
327 | } | |
328 | ||
329 | func (me *Cache) AsResourceProvider() resource.Provider { | |
330 | return &uniformResourceProvider{me} | |
331 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "io/ioutil" | |
5 | "os" | |
6 | "path/filepath" | |
7 | "testing" | |
8 | ||
9 | "github.com/bradfitz/iter" | |
10 | "github.com/stretchr/testify/assert" | |
11 | "github.com/stretchr/testify/require" | |
12 | ||
13 | "github.com/anacrolix/missinggo" | |
14 | ) | |
15 | ||
16 | func TestCache(t *testing.T) { | |
17 | td, err := ioutil.TempDir("", "gotest") | |
18 | require.NoError(t, err) | |
19 | defer os.RemoveAll(td) | |
20 | ||
21 | c, err := NewCache(filepath.Join(td, "cache")) | |
22 | require.NoError(t, err) | |
23 | assert.EqualValues(t, CacheInfo{ | |
24 | Filled: 0, | |
25 | Capacity: -1, | |
26 | NumItems: 0, | |
27 | }, c.Info()) | |
28 | ||
29 | c.WalkItems(func(i ItemInfo) {}) | |
30 | ||
31 | _, err = c.OpenFile("/", os.O_CREATE) | |
32 | assert.NotNil(t, err) | |
33 | ||
34 | _, err = c.OpenFile("", os.O_CREATE) | |
35 | assert.NotNil(t, err) | |
36 | ||
37 | c.WalkItems(func(i ItemInfo) {}) | |
38 | ||
39 | require.Equal(t, CacheInfo{ | |
40 | Filled: 0, | |
41 | Capacity: -1, | |
42 | NumItems: 0, | |
43 | }, c.Info()) | |
44 | ||
45 | _, err = c.OpenFile("notexist", 0) | |
46 | assert.True(t, os.IsNotExist(err), err) | |
47 | ||
48 | _, err = c.OpenFile("/notexist", 0) | |
49 | assert.True(t, os.IsNotExist(err), err) | |
50 | ||
51 | _, err = c.OpenFile("/dir/notexist", 0) | |
52 | assert.True(t, os.IsNotExist(err), err) | |
53 | ||
54 | f, err := c.OpenFile("dir/blah", os.O_CREATE) | |
55 | require.NoError(t, err) | |
56 | defer f.Close() | |
57 | require.Equal(t, CacheInfo{ | |
58 | Filled: 0, | |
59 | Capacity: -1, | |
60 | NumItems: 1, | |
61 | }, c.Info()) | |
62 | ||
63 | c.WalkItems(func(i ItemInfo) {}) | |
64 | ||
65 | assert.True(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/blah")))) | |
66 | assert.True(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/")))) | |
67 | assert.Equal(t, 1, c.Info().NumItems) | |
68 | ||
69 | c.Remove("dir/blah") | |
70 | assert.False(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/blah")))) | |
71 | assert.False(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/")))) | |
72 | _, err = f.ReadAt(nil, 0) | |
73 | assert.NotEqual(t, io.EOF, err) | |
74 | ||
75 | a, err := c.OpenFile("/a", os.O_CREATE|os.O_WRONLY) | |
76 | defer a.Close() | |
77 | require.NoError(t, err) | |
78 | b, err := c.OpenFile("b", os.O_CREATE|os.O_WRONLY) | |
79 | defer b.Close() | |
80 | require.NoError(t, err) | |
81 | c.mu.Lock() | |
82 | assert.False(t, c.pathInfo("a").Accessed.After(c.pathInfo("b").Accessed)) | |
83 | c.mu.Unlock() | |
84 | n, err := a.WriteAt([]byte("hello"), 0) | |
85 | assert.NoError(t, err) | |
86 | assert.EqualValues(t, 5, n) | |
87 | assert.EqualValues(t, CacheInfo{ | |
88 | Filled: 5, | |
89 | Capacity: -1, | |
90 | NumItems: 2, | |
91 | }, c.Info()) | |
92 | assert.False(t, c.pathInfo("b").Accessed.After(c.pathInfo("a").Accessed)) | |
93 | ||
94 | // Reopen a, to check that the info values remain correct. | |
95 | assert.NoError(t, a.Close()) | |
96 | a, err = c.OpenFile("a", 0) | |
97 | require.NoError(t, err) | |
98 | require.EqualValues(t, CacheInfo{ | |
99 | Filled: 5, | |
100 | Capacity: -1, | |
101 | NumItems: 2, | |
102 | }, c.Info()) | |
103 | ||
104 | c.SetCapacity(5) | |
105 | require.EqualValues(t, CacheInfo{ | |
106 | Filled: 5, | |
107 | Capacity: 5, | |
108 | NumItems: 2, | |
109 | }, c.Info()) | |
110 | ||
111 | n, err = a.WriteAt([]byte(" world"), 5) | |
112 | assert.Error(t, err) | |
113 | n, err = b.WriteAt([]byte("boom!"), 0) | |
114 | // "a" and "b" have been evicted. | |
115 | require.NoError(t, err) | |
116 | require.EqualValues(t, 5, n) | |
117 | require.EqualValues(t, CacheInfo{ | |
118 | Filled: 5, | |
119 | Capacity: 5, | |
120 | NumItems: 1, | |
121 | }, c.Info()) | |
122 | } | |
123 | ||
124 | func TestSanitizePath(t *testing.T) { | |
125 | assert.EqualValues(t, "", sanitizePath("////")) | |
126 | assert.EqualValues(t, "", sanitizePath("/../..")) | |
127 | assert.EqualValues(t, "a", sanitizePath("/a//b/..")) | |
128 | assert.EqualValues(t, "a", sanitizePath("../a")) | |
129 | assert.EqualValues(t, "a", sanitizePath("./a")) | |
130 | } | |
131 | ||
132 | func BenchmarkCacheOpenFile(t *testing.B) { | |
133 | td, err := ioutil.TempDir("", "") | |
134 | require.NoError(t, err) | |
135 | defer os.RemoveAll(td) | |
136 | c, err := NewCache(td) | |
137 | for range iter.N(t.N) { | |
138 | func() { | |
139 | f, err := c.OpenFile("a", os.O_CREATE|os.O_RDWR) | |
140 | require.NoError(t, err) | |
141 | assert.NoError(t, f.Close()) | |
142 | }() | |
143 | } | |
144 | } | |
145 | ||
146 | func TestFileReadWrite(t *testing.T) { | |
147 | td, err := ioutil.TempDir("", "") | |
148 | require.NoError(t, err) | |
149 | defer os.RemoveAll(td) | |
150 | ||
151 | c, err := NewCache(td) | |
152 | require.NoError(t, err) | |
153 | ||
154 | a, err := c.OpenFile("a", os.O_CREATE|os.O_EXCL|os.O_RDWR) | |
155 | require.NoError(t, err) | |
156 | defer a.Close() | |
157 | ||
158 | for off, c := range []byte("herp") { | |
159 | n, err := a.WriteAt([]byte{c}, int64(off)) | |
160 | assert.NoError(t, err) | |
161 | require.EqualValues(t, 1, n) | |
162 | } | |
163 | for off, c := range []byte("herp") { | |
164 | var b [1]byte | |
165 | n, err := a.ReadAt(b[:], int64(off)) | |
166 | require.EqualValues(t, 1, n) | |
167 | require.NoError(t, err) | |
168 | assert.EqualValues(t, []byte{c}, b[:]) | |
169 | } | |
170 | ||
171 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "os" | |
5 | "sync" | |
6 | ||
7 | "github.com/anacrolix/missinggo/pproffd" | |
8 | ) | |
9 | ||
10 | type File struct { | |
11 | path key | |
12 | f pproffd.OSFile | |
13 | afterWrite func(endOff int64) | |
14 | onRead func(n int) | |
15 | mu sync.Mutex | |
16 | offset int64 | |
17 | } | |
18 | ||
19 | func (me *File) Seek(offset int64, whence int) (ret int64, err error) { | |
20 | ret, err = me.f.Seek(offset, whence) | |
21 | if err != nil { | |
22 | return | |
23 | } | |
24 | me.offset = ret | |
25 | return | |
26 | } | |
27 | ||
28 | var ( | |
29 | ErrFileTooLarge = errors.New("file too large for cache") | |
30 | ErrFileDisappeared = errors.New("file disappeared") | |
31 | ) | |
32 | ||
33 | func (me *File) Write(b []byte) (n int, err error) { | |
34 | n, err = me.f.Write(b) | |
35 | me.offset += int64(n) | |
36 | me.afterWrite(me.offset) | |
37 | return | |
38 | } | |
39 | ||
40 | func (me *File) WriteAt(b []byte, off int64) (n int, err error) { | |
41 | n, err = me.f.WriteAt(b, off) | |
42 | me.afterWrite(off + int64(n)) | |
43 | return | |
44 | } | |
45 | ||
46 | func (me *File) Close() error { | |
47 | return me.f.Close() | |
48 | } | |
49 | ||
50 | func (me *File) Stat() (os.FileInfo, error) { | |
51 | return me.f.Stat() | |
52 | } | |
53 | ||
54 | func (me *File) Read(b []byte) (n int, err error) { | |
55 | n, err = me.f.Read(b) | |
56 | me.onRead(n) | |
57 | return | |
58 | } | |
59 | ||
60 | func (me *File) ReadAt(b []byte, off int64) (n int, err error) { | |
61 | n, err = me.f.ReadAt(b, off) | |
62 | me.onRead(n) | |
63 | return | |
64 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "time" | |
5 | ||
6 | "github.com/anacrolix/missinggo" | |
7 | ) | |
8 | ||
9 | type itemState struct { | |
10 | Accessed time.Time | |
11 | Size int64 | |
12 | } | |
13 | ||
14 | func (i *itemState) FromOSFileInfo(fi os.FileInfo) { | |
15 | i.Size = fi.Size() | |
16 | i.Accessed = missinggo.FileInfoAccessTime(fi) | |
17 | if fi.ModTime().After(i.Accessed) { | |
18 | i.Accessed = fi.ModTime() | |
19 | } | |
20 | } |
0 | package filecache | |
1 | ||
2 | type key string | |
3 | ||
4 | func (me key) Before(other policyItemKey) bool { | |
5 | return me < other.(key) | |
6 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/anacrolix/missinggo/orderedmap" | |
6 | ) | |
7 | ||
8 | type lru struct { | |
9 | o orderedmap.OrderedMap | |
10 | oKeys map[policyItemKey]lruKey | |
11 | } | |
12 | ||
13 | type lruKey struct { | |
14 | item policyItemKey | |
15 | used time.Time | |
16 | } | |
17 | ||
18 | func (me lruKey) Before(other lruKey) bool { | |
19 | if me.used.Equal(other.used) { | |
20 | return me.item.Before(other.item) | |
21 | } | |
22 | return me.used.Before(other.used) | |
23 | } | |
24 | ||
25 | var _ Policy = (*lru)(nil) | |
26 | ||
27 | func (me *lru) Choose() (ret policyItemKey) { | |
28 | any := false | |
29 | me.o.Iter(func(i interface{}) bool { | |
30 | ret = i.(lruKey).item | |
31 | any = true | |
32 | return false | |
33 | }) | |
34 | if !any { | |
35 | panic("cache empty") | |
36 | } | |
37 | return | |
38 | } | |
39 | ||
40 | func (me *lru) Used(k policyItemKey, at time.Time) { | |
41 | if me.o == nil { | |
42 | me.o = orderedmap.NewGoogleBTree(func(l, r interface{}) bool { | |
43 | return l.(lruKey).Before(r.(lruKey)) | |
44 | }) | |
45 | } else { | |
46 | me.o.Unset(me.oKeys[k]) | |
47 | } | |
48 | lk := lruKey{k, at} | |
49 | me.o.Set(lk, lk) | |
50 | if me.oKeys == nil { | |
51 | me.oKeys = make(map[policyItemKey]lruKey) | |
52 | } | |
53 | me.oKeys[k] = lk | |
54 | } | |
55 | ||
56 | func (me *lru) Forget(k policyItemKey) { | |
57 | if me.o != nil { | |
58 | me.o.Unset(me.oKeys[k]) | |
59 | } | |
60 | delete(me.oKeys, k) | |
61 | } | |
62 | ||
63 | func (me *lru) NumItems() int { | |
64 | return len(me.oKeys) | |
65 | } |
0 | package filecache | |
1 | ||
2 | import "testing" | |
3 | ||
4 | func TestLRU(t *testing.T) { | |
5 | testPolicy(t, &lru{}) | |
6 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func TestLruDuplicateAccessTimes(t *testing.T) { | |
10 | var li Policy = new(lru) | |
11 | now := time.Now() | |
12 | li.Used(key("a"), now) | |
13 | li.Used(key("b"), now) | |
14 | assert.EqualValues(t, 2, li.NumItems()) | |
15 | } |
0 | package filecache | |
1 | ||
2 | import "time" | |
3 | ||
4 | type policyItemKey interface { | |
5 | Before(policyItemKey) bool | |
6 | } | |
7 | ||
8 | type Policy interface { | |
9 | Choose() policyItemKey | |
10 | Used(k policyItemKey, at time.Time) | |
11 | Forget(k policyItemKey) | |
12 | NumItems() int | |
13 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func testChooseForgottenKey(t *testing.T, p Policy) { | |
10 | assert.Equal(t, 0, p.NumItems()) | |
11 | assert.Panics(t, func() { p.Choose() }) | |
12 | p.Used(key("a"), time.Now()) | |
13 | assert.Equal(t, 1, p.NumItems()) | |
14 | p.Used(key("a"), time.Now().Add(1)) | |
15 | assert.Equal(t, 1, p.NumItems()) | |
16 | p.Forget(key("a")) | |
17 | assert.Equal(t, 0, p.NumItems()) | |
18 | assert.Panics(t, func() { p.Choose() }) | |
19 | } | |
20 | ||
21 | func testPolicy(t *testing.T, p Policy) { | |
22 | testChooseForgottenKey(t, p) | |
23 | } |
0 | package filecache | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "os" | |
5 | ||
6 | "github.com/anacrolix/missinggo/resource" | |
7 | ) | |
8 | ||
9 | type uniformResourceProvider struct { | |
10 | *Cache | |
11 | } | |
12 | ||
13 | var _ resource.Provider = &uniformResourceProvider{} | |
14 | ||
15 | func (me *uniformResourceProvider) NewInstance(loc string) (resource.Instance, error) { | |
16 | return &uniformResource{me.Cache, loc}, nil | |
17 | } | |
18 | ||
19 | type uniformResource struct { | |
20 | Cache *Cache | |
21 | Location string | |
22 | } | |
23 | ||
24 | func (me *uniformResource) Get() (io.ReadCloser, error) { | |
25 | return me.Cache.OpenFile(me.Location, os.O_RDONLY) | |
26 | } | |
27 | ||
28 | func (me *uniformResource) Put(r io.Reader) (err error) { | |
29 | f, err := me.Cache.OpenFile(me.Location, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) | |
30 | if err != nil { | |
31 | return | |
32 | } | |
33 | defer f.Close() | |
34 | _, err = io.Copy(f, r) | |
35 | return | |
36 | } | |
37 | ||
38 | func (me *uniformResource) ReadAt(b []byte, off int64) (n int, err error) { | |
39 | f, err := me.Cache.OpenFile(me.Location, os.O_RDONLY) | |
40 | if err != nil { | |
41 | return | |
42 | } | |
43 | defer f.Close() | |
44 | return f.ReadAt(b, off) | |
45 | } | |
46 | ||
47 | func (me *uniformResource) WriteAt(b []byte, off int64) (n int, err error) { | |
48 | f, err := me.Cache.OpenFile(me.Location, os.O_CREATE|os.O_WRONLY) | |
49 | if err != nil { | |
50 | return | |
51 | } | |
52 | defer f.Close() | |
53 | return f.WriteAt(b, off) | |
54 | } | |
55 | ||
56 | func (me *uniformResource) Stat() (fi os.FileInfo, err error) { | |
57 | return me.Cache.Stat(me.Location) | |
58 | } | |
59 | ||
60 | func (me *uniformResource) Delete() error { | |
61 | return me.Cache.Remove(me.Location) | |
62 | } |
0 | package missinggo | |
1 | ||
2 | import "sync" | |
3 | ||
4 | // Flag represents a boolean value, that signals sync.Cond's when it changes. | |
5 | // It's not concurrent safe by intention. | |
6 | type Flag struct { | |
7 | Conds map[*sync.Cond]struct{} | |
8 | value bool | |
9 | } | |
10 | ||
11 | func (me *Flag) Set(value bool) { | |
12 | if value != me.value { | |
13 | me.broadcastChange() | |
14 | } | |
15 | me.value = value | |
16 | } | |
17 | ||
18 | func (me *Flag) Get() bool { | |
19 | return me.value | |
20 | } | |
21 | ||
22 | func (me *Flag) broadcastChange() { | |
23 | for cond := range me.Conds { | |
24 | cond.Broadcast() | |
25 | } | |
26 | } | |
27 | ||
28 | func (me *Flag) addCond(c *sync.Cond) { | |
29 | if me.Conds == nil { | |
30 | me.Conds = make(map[*sync.Cond]struct{}) | |
31 | } | |
32 | me.Conds[c] = struct{}{} | |
33 | } | |
34 | ||
35 | // Adds the sync.Cond to all the given Flag's. | |
36 | func AddCondToFlags(cond *sync.Cond, flags ...*Flag) { | |
37 | for _, f := range flags { | |
38 | f.addCond(cond) | |
39 | } | |
40 | } |
0 | package futures | |
1 | ||
2 | import "time" | |
3 | ||
4 | func timeoutFuture(timeout time.Duration) *F { | |
5 | return Start(func() (interface{}, error) { | |
6 | time.Sleep(timeout) | |
7 | return nil, nil | |
8 | }) | |
9 | } | |
10 | ||
11 | type Delayed struct { | |
12 | Delay time.Duration | |
13 | Fs []*F | |
14 | } |
0 | package futures | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "fmt" | |
5 | "testing" | |
6 | "time" | |
7 | ||
8 | "github.com/bradfitz/iter" | |
9 | "github.com/stretchr/testify/assert" | |
10 | ) | |
11 | ||
12 | // Delay unit, high enough that system slowness doesn't affect timing, but low | |
13 | // enough to ensure tests are fast. | |
14 | const u = 20 * time.Millisecond | |
15 | ||
16 | func TestAsCompletedDelayed(t *testing.T) { | |
17 | t.Parallel() | |
18 | var fs []*F | |
19 | s := time.Now() | |
20 | for i := range iter.N(10) { | |
21 | f := timeoutFuture(time.Duration(i) * u) | |
22 | f.SetName(fmt.Sprintf("%d", i)) | |
23 | fs = append(fs, f) | |
24 | } | |
25 | as := AsCompletedDelayed( | |
26 | context.Background(), | |
27 | []*F{fs[0], fs[2]}, | |
28 | []Delayed{ | |
29 | {u, []*F{fs[1]}}, | |
30 | {3 * u, []*F{fs[0]}}, | |
31 | }, | |
32 | ) | |
33 | a := func(f, when time.Duration) { | |
34 | t.Helper() | |
35 | assert.Equal(t, fs[f], <-as) | |
36 | if time.Since(s) < when*u { | |
37 | t.Errorf("%d completed too soon", f) | |
38 | } | |
39 | if time.Since(s) >= (when+1)*u { | |
40 | t.Errorf("%d completed too late", f) | |
41 | } | |
42 | } | |
43 | a(0, 0) | |
44 | a(1, 1) | |
45 | a(2, 2) | |
46 | a(0, 2) | |
47 | _, ok := <-as | |
48 | assert.False(t, ok) | |
49 | assert.True(t, time.Since(s) < 4*u) | |
50 | } | |
51 | ||
52 | func TestAsCompletedDelayedContextCanceled(t *testing.T) { | |
53 | t.Parallel() | |
54 | var fs []*F | |
55 | s := time.Now() | |
56 | for i := range iter.N(10) { | |
57 | f := timeoutFuture(time.Duration(i) * u) | |
58 | f.SetName(fmt.Sprintf("%d", i)) | |
59 | fs = append(fs, f) | |
60 | } | |
61 | ctx, cancel := context.WithCancel(context.Background()) | |
62 | as := AsCompletedDelayed( | |
63 | ctx, | |
64 | []*F{fs[0], fs[2]}, | |
65 | []Delayed{ | |
66 | {u, []*F{fs[1]}}, | |
67 | {3 * u, []*F{fs[0]}}, | |
68 | }, | |
69 | ) | |
70 | a := func(f, when time.Duration) { | |
71 | t.Helper() | |
72 | assert.Equal(t, fs[f], <-as) | |
73 | if time.Since(s) < when*u { | |
74 | t.Errorf("%d completed too soon", f) | |
75 | } | |
76 | if time.Since(s) >= (when+1)*u { | |
77 | t.Errorf("%d completed too late", f) | |
78 | } | |
79 | } | |
80 | a(0, 0) | |
81 | cancel() | |
82 | _, ok := <-as | |
83 | assert.False(t, ok) | |
84 | assert.True(t, time.Since(s) < 1*u) | |
85 | } |
0 | package futures | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "sync" | |
5 | "time" | |
6 | ||
7 | "github.com/bradfitz/iter" | |
8 | ||
9 | "github.com/anacrolix/missinggo/slices" | |
10 | ) | |
11 | ||
12 | // Sends each future as it completes on the returned chan, closing it when | |
13 | // everything has been sent. | |
14 | func AsCompleted(fs ...*F) <-chan *F { | |
15 | ret := make(chan *F, len(fs)) | |
16 | var wg sync.WaitGroup | |
17 | for _, f := range fs { | |
18 | wg.Add(1) | |
19 | go func(f *F) { | |
20 | defer wg.Done() | |
21 | <-f.Done() | |
22 | ret <- f | |
23 | }(f) | |
24 | } | |
25 | go func() { | |
26 | wg.Wait() | |
27 | close(ret) | |
28 | }() | |
29 | return ret | |
30 | } | |
31 | ||
32 | // Additional state maintained for each delayed element. | |
33 | type delayedState struct { | |
34 | timeout *F | |
35 | added bool | |
36 | } | |
37 | ||
38 | // Returns futures as they complete. Delayed futures are not released until | |
39 | // their timeout has passed, or all prior delayed futures, and the initial set | |
40 | // have completed. One use case is to prefer the value in some futures over | |
41 | // others, such as hitting several origin servers where some are better | |
42 | // informed than others. | |
43 | func AsCompletedDelayed(ctx context.Context, initial []*F, delayed []Delayed) <-chan *F { | |
44 | ret := make(chan *F, func() int { | |
45 | l := len(initial) | |
46 | for _, d := range delayed { | |
47 | l += len(d.Fs) | |
48 | } | |
49 | return l | |
50 | }()) | |
51 | go func() { | |
52 | defer close(ret) | |
53 | var ( | |
54 | dss []delayedState | |
55 | timeouts = map[*F]struct{}{} // Pending timeouts | |
56 | ) | |
57 | for i := range delayed { | |
58 | func(i int) { | |
59 | f := Start(func() (interface{}, error) { | |
60 | select { | |
61 | case <-time.After(delayed[i].Delay): | |
62 | return i, nil | |
63 | case <-ctx.Done(): | |
64 | return nil, ctx.Err() | |
65 | } | |
66 | }) | |
67 | timeouts[f] = struct{}{} | |
68 | dss = append(dss, delayedState{timeout: f}) | |
69 | }(i) | |
70 | } | |
71 | // Number of pending sends for a future. | |
72 | results := map[*F]int{} | |
73 | for _, f := range initial { | |
74 | results[f]++ | |
75 | } | |
76 | start: | |
77 | // A slice of futures we want to send when they complete. | |
78 | resultsSlice := func() (ret []*F) { | |
79 | for f, left := range results { | |
80 | for range iter.N(left) { | |
81 | ret = append(ret, f) | |
82 | } | |
83 | } | |
84 | return | |
85 | }() | |
86 | if len(resultsSlice) == 0 { | |
87 | for i, ds := range dss { | |
88 | if ds.added { | |
89 | continue | |
90 | } | |
91 | // Add this delayed block prematurely. | |
92 | delete(timeouts, ds.timeout) | |
93 | for _, f := range delayed[i].Fs { | |
94 | results[f]++ | |
95 | } | |
96 | dss[i].added = true | |
97 | // We need to recompute the results slice. | |
98 | goto start | |
99 | } | |
100 | } | |
101 | as := AsCompleted(append( | |
102 | resultsSlice, | |
103 | slices.FromMapKeys(timeouts).([]*F)..., | |
104 | )...) | |
105 | for { | |
106 | select { | |
107 | case <-ctx.Done(): | |
108 | return | |
109 | case f, ok := <-as: | |
110 | if !ok { | |
111 | return | |
112 | } | |
113 | if _, ok := timeouts[f]; ok { | |
114 | if ctx.Err() != nil { | |
115 | break | |
116 | } | |
117 | i := f.MustResult().(int) | |
118 | for _, f := range delayed[i].Fs { | |
119 | results[f]++ | |
120 | } | |
121 | delete(timeouts, f) | |
122 | dss[i].added = true | |
123 | goto start | |
124 | } | |
125 | select { | |
126 | case ret <- f: | |
127 | results[f]-- | |
128 | if results[f] == 0 { | |
129 | delete(results, f) | |
130 | } | |
131 | if len(results) == 0 { | |
132 | goto start | |
133 | } | |
134 | case <-ctx.Done(): | |
135 | return | |
136 | } | |
137 | } | |
138 | } | |
139 | }() | |
140 | return ret | |
141 | } |
0 | package futures | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "reflect" | |
5 | "sync" | |
6 | ) | |
7 | ||
8 | func Start(fn func() (interface{}, error)) *F { | |
9 | f := &F{ | |
10 | done: make(chan struct{}), | |
11 | } | |
12 | go func() { | |
13 | f.setResult(fn()) | |
14 | }() | |
15 | return f | |
16 | } | |
17 | ||
18 | func StartNoError(fn func() interface{}) *F { | |
19 | return Start(func() (interface{}, error) { | |
20 | return fn(), nil | |
21 | }) | |
22 | } | |
23 | ||
24 | type F struct { | |
25 | name string | |
26 | mu sync.Mutex | |
27 | result interface{} | |
28 | err error | |
29 | done chan struct{} | |
30 | } | |
31 | ||
32 | func (f *F) String() string { | |
33 | if f.name != "" { | |
34 | return f.name | |
35 | } | |
36 | return fmt.Sprintf("future %p", f) | |
37 | } | |
38 | ||
39 | func (f *F) SetName(s string) { | |
40 | f.name = s | |
41 | } | |
42 | ||
43 | func (f *F) Err() error { | |
44 | <-f.done | |
45 | return f.err | |
46 | } | |
47 | ||
48 | // TODO: Just return value. | |
49 | func (f *F) Result() (interface{}, error) { | |
50 | <-f.done | |
51 | f.mu.Lock() | |
52 | defer f.mu.Unlock() | |
53 | return f.result, f.err | |
54 | } | |
55 | ||
56 | func (f *F) MustResult() interface{} { | |
57 | val, err := f.Result() | |
58 | if err != nil { | |
59 | panic(err) | |
60 | } | |
61 | return val | |
62 | } | |
63 | ||
64 | func (f *F) Done() <-chan struct{} { | |
65 | return f.done | |
66 | } | |
67 | ||
68 | func (f *F) setResult(result interface{}, err error) { | |
69 | f.mu.Lock() | |
70 | defer f.mu.Unlock() | |
71 | f.result = result | |
72 | f.err = err | |
73 | close(f.done) | |
74 | } | |
75 | ||
76 | func (f *F) ScanResult(res interface{}) error { | |
77 | _res, err := f.Result() | |
78 | reflect.ValueOf(res).Elem().Set(reflect.ValueOf(_res)) | |
79 | return err | |
80 | } |
0 | module github.com/anacrolix/missinggo/v2 | |
1 | ||
2 | require ( | |
3 | github.com/RoaringBitmap/roaring v0.4.17 | |
4 | github.com/anacrolix/envpprof v1.0.0 | |
5 | github.com/anacrolix/missinggo v1.1.0 | |
6 | github.com/anacrolix/tagflag v1.0.0 | |
7 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c | |
8 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 | |
9 | github.com/dustin/go-humanize v1.0.0 | |
10 | github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e // indirect | |
11 | github.com/google/btree v1.0.0 | |
12 | github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9 // indirect | |
13 | github.com/huandu/xstrings v1.2.0 | |
14 | github.com/pkg/errors v0.8.1 // indirect | |
15 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 | |
16 | github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect | |
17 | github.com/stretchr/testify v1.3.0 | |
18 | go.opencensus.io v0.20.2 | |
19 | ) |
0 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |
1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |
3 | github.com/RoaringBitmap/roaring v0.4.7 h1:eGUudvFzvF7Kxh7JjYvXfI1f7l22/2duFby7r5+d4oc= | |
4 | github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= | |
5 | github.com/RoaringBitmap/roaring v0.4.17 h1:oCYFIFEMSQZrLHpywH7919esI1VSrQZ0pJXkZPGIJ78= | |
6 | github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= | |
7 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= | |
8 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= | |
9 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | |
10 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | |
11 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa h1:xCaATLKmn39QqLs3tUZYr6eKvezJV+FYvVOLTklxK6U= | |
12 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= | |
13 | github.com/anacrolix/envpprof v1.0.0 h1:AwZ+mBP4rQ5f7JSsrsN3h7M2xDW/xSE66IPVOqlnuUc= | |
14 | github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= | |
15 | github.com/anacrolix/missinggo v1.1.0 h1:0lZbaNa6zTR1bELAIzCNmRGAtkHuLDPJqTiTtXoAIx8= | |
16 | github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= | |
17 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0 h1:xcd2GmlPWBsGNjdbwriHXvJJtagl1AnbjTPhJTksJDQ= | |
18 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= | |
19 | github.com/anacrolix/tagflag v1.0.0 h1:NoxBVyke6iEtXfSY/n3lY3jNCBjQDu7aTvwHJxNLJAQ= | |
20 | github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= | |
21 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= | |
22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | |
23 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 h1:1B/+1BcRhOMG1KH/YhNIU8OppSWk5d/NGyfRla88CuY= | |
24 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= | |
25 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0= | |
26 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= | |
27 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | |
28 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |
29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
32 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= | |
33 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= | |
34 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e h1:Fw7ZmgiklsLh5EQWyHh1sumKSCG1+yjEctIpGKib87s= | |
35 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | |
36 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | |
37 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | |
38 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | |
39 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | |
40 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | |
41 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | |
42 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd h1:r04MMPyLHj/QwZuMJ5+7tJcBr1AQjpiAK/rZWRrQT7o= | |
43 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= | |
44 | github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= | |
45 | github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= | |
46 | github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 h1:OTanQnFt0bi5iLFSdbEVA/idR6Q2WhCm+deb7ir2CcM= | |
47 | github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= | |
48 | github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e h1:SiEs4J3BKVIeaWrH3tKaz3QLZhJ68iJ/A4xrzIoE5+Y= | |
49 | github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= | |
50 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | |
51 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | |
52 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | |
53 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | |
54 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | |
55 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | |
56 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | |
57 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
58 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= | |
59 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | |
60 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= | |
61 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | |
62 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu4i+jD32SE9jQXyfnOvwhHqlT0= | |
63 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | |
64 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= | |
65 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | |
66 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | |
67 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |
68 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= | |
69 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |
70 | github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9 h1:Z0f701LpR4dqO92bP6TnIe3ZURClzJtBhds8R8u1HBE= | |
71 | github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |
72 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | |
73 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | |
74 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= | |
75 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | |
76 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | |
77 | github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk= | |
78 | github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= | |
79 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= | |
80 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= | |
81 | github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= | |
82 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |
83 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | |
84 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |
85 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | |
86 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |
87 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | |
88 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | |
89 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | |
90 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= | |
91 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= | |
92 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | |
93 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |
94 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |
95 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | |
96 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= | |
97 | github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= | |
98 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= | |
99 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= | |
100 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
101 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |
102 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
103 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
104 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
105 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | |
106 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= | |
107 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | |
108 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | |
109 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | |
110 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | |
111 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | |
112 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= | |
113 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= | |
114 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= | |
115 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | |
116 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | |
117 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | |
118 | github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= | |
119 | github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | |
120 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= | |
121 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= | |
122 | github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= | |
123 | github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= | |
124 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
125 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
126 | github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= | |
127 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
128 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
129 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | |
130 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |
131 | github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8= | |
132 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= | |
133 | github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= | |
134 | github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= | |
135 | github.com/willf/bitset v1.1.9 h1:GBtFynGY9ZWZmEC9sWuu41/7VBXPFCOAbCbqTflOg9c= | |
136 | github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | |
137 | github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= | |
138 | github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | |
139 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= | |
140 | go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk= | |
141 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= | |
142 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |
143 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
144 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | |
145 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |
146 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | |
147 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |
148 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
149 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
150 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
151 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
152 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
153 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
154 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
155 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
156 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |
157 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | |
158 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
159 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
160 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
161 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
162 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
163 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
164 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
165 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
166 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
167 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
168 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
169 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
170 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
171 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | |
172 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |
173 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= | |
174 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | |
175 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |
176 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | |
177 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | |
178 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= | |
179 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | |
180 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | |
181 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
182 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | |
183 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | |
184 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
185 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |
186 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net" | |
4 | "strconv" | |
5 | "strings" | |
6 | ) | |
7 | ||
8 | // Represents a split host port. | |
9 | type HostMaybePort struct { | |
10 | Host string // Just the host, with no port. | |
11 | Port int // The port if NoPort is false. | |
12 | NoPort bool // Whether a port is specified. | |
13 | Err error // The error returned from net.SplitHostPort. | |
14 | } | |
15 | ||
16 | func (me *HostMaybePort) String() string { | |
17 | if me.NoPort { | |
18 | return me.Host | |
19 | } | |
20 | return net.JoinHostPort(me.Host, strconv.FormatInt(int64(me.Port), 10)) | |
21 | } | |
22 | ||
23 | // Parse a "hostport" string, a concept that floats around the stdlib a lot | |
24 | // and is painful to work with. If no port is present, what's usually present | |
25 | // is just the host. | |
26 | func SplitHostMaybePort(hostport string) HostMaybePort { | |
27 | host, portStr, err := net.SplitHostPort(hostport) | |
28 | if err != nil { | |
29 | if strings.Contains(err.Error(), "missing port") { | |
30 | return HostMaybePort{ | |
31 | Host: hostport, | |
32 | NoPort: true, | |
33 | } | |
34 | } | |
35 | return HostMaybePort{ | |
36 | Err: err, | |
37 | } | |
38 | } | |
39 | portI64, err := strconv.ParseInt(portStr, 0, 0) | |
40 | if err != nil { | |
41 | return HostMaybePort{ | |
42 | Host: host, | |
43 | Port: -1, | |
44 | Err: err, | |
45 | } | |
46 | } | |
47 | return HostMaybePort{ | |
48 | Host: host, | |
49 | Port: int(portI64), | |
50 | } | |
51 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestSplitHostMaybePortNoPort(t *testing.T) { | |
9 | hmp := SplitHostMaybePort("some.domain") | |
10 | assert.Equal(t, "some.domain", hmp.Host) | |
11 | assert.True(t, hmp.NoPort) | |
12 | assert.NoError(t, hmp.Err) | |
13 | } | |
14 | ||
15 | func TestSplitHostMaybePortPort(t *testing.T) { | |
16 | hmp := SplitHostMaybePort("some.domain:123") | |
17 | assert.Equal(t, "some.domain", hmp.Host) | |
18 | assert.Equal(t, 123, hmp.Port) | |
19 | assert.False(t, hmp.NoPort) | |
20 | assert.NoError(t, hmp.Err) | |
21 | } | |
22 | ||
23 | func TestSplitHostMaybePortBadPort(t *testing.T) { | |
24 | hmp := SplitHostMaybePort("some.domain:wat") | |
25 | assert.Equal(t, "some.domain", hmp.Host) | |
26 | assert.Equal(t, -1, hmp.Port) | |
27 | assert.False(t, hmp.NoPort) | |
28 | assert.Error(t, hmp.Err) | |
29 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net" | |
4 | "strconv" | |
5 | ) | |
6 | ||
7 | func ParseHostPort(hostport string) (host string, port int, err error) { | |
8 | host, portStr, err := net.SplitHostPort(hostport) | |
9 | if err != nil { | |
10 | return | |
11 | } | |
12 | port64, err := strconv.ParseInt(portStr, 0, 0) | |
13 | if err != nil { | |
14 | return | |
15 | } | |
16 | port = int(port64) | |
17 | return | |
18 | } |
0 | package httpfile | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | ) | |
5 | ||
6 | var DefaultFS = &FS{ | |
7 | Client: http.DefaultClient, | |
8 | } | |
9 | ||
10 | // Returns the length of the resource in bytes. | |
11 | func GetLength(url string) (ret int64, err error) { | |
12 | return DefaultFS.GetLength(url) | |
13 | } |
0 | package httpfile | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "errors" | |
5 | "fmt" | |
6 | "io" | |
7 | "net/http" | |
8 | "os" | |
9 | "strconv" | |
10 | ||
11 | "github.com/anacrolix/missinggo" | |
12 | "github.com/anacrolix/missinggo/httptoo" | |
13 | ) | |
14 | ||
15 | type File struct { | |
16 | off int64 | |
17 | r io.ReadCloser | |
18 | rOff int64 | |
19 | length int64 | |
20 | url string | |
21 | flags int | |
22 | fs *FS | |
23 | } | |
24 | ||
25 | func (me *File) headLength() (err error) { | |
26 | l, err := me.fs.GetLength(me.url) | |
27 | if err != nil { | |
28 | return | |
29 | } | |
30 | if l != -1 { | |
31 | me.length = l | |
32 | } | |
33 | return | |
34 | } | |
35 | ||
36 | func (me *File) prepareReader() (err error) { | |
37 | if me.r != nil && me.off != me.rOff { | |
38 | me.r.Close() | |
39 | me.r = nil | |
40 | } | |
41 | if me.r != nil { | |
42 | return nil | |
43 | } | |
44 | if me.flags&missinggo.O_ACCMODE == os.O_WRONLY { | |
45 | err = errors.New("read flags missing") | |
46 | return | |
47 | } | |
48 | req, err := http.NewRequest("GET", me.url, nil) | |
49 | if err != nil { | |
50 | return | |
51 | } | |
52 | if me.off != 0 { | |
53 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-", me.off)) | |
54 | } | |
55 | resp, err := me.fs.Client.Do(req) | |
56 | if err != nil { | |
57 | return | |
58 | } | |
59 | switch resp.StatusCode { | |
60 | case http.StatusPartialContent: | |
61 | cr, ok := httptoo.ParseBytesContentRange(resp.Header.Get("Content-Range")) | |
62 | if !ok || cr.First != me.off { | |
63 | err = errors.New("bad response") | |
64 | resp.Body.Close() | |
65 | return | |
66 | } | |
67 | me.length = cr.Length | |
68 | case http.StatusOK: | |
69 | if me.off != 0 { | |
70 | err = errors.New("bad response") | |
71 | resp.Body.Close() | |
72 | return | |
73 | } | |
74 | if h := resp.Header.Get("Content-Length"); h != "" { | |
75 | var cl uint64 | |
76 | cl, err = strconv.ParseUint(h, 10, 64) | |
77 | if err != nil { | |
78 | resp.Body.Close() | |
79 | return | |
80 | } | |
81 | me.length = int64(cl) | |
82 | } | |
83 | case http.StatusNotFound: | |
84 | err = ErrNotFound | |
85 | resp.Body.Close() | |
86 | return | |
87 | default: | |
88 | err = errors.New(resp.Status) | |
89 | resp.Body.Close() | |
90 | return | |
91 | } | |
92 | me.r = resp.Body | |
93 | me.rOff = me.off | |
94 | return | |
95 | } | |
96 | ||
97 | func (me *File) Read(b []byte) (n int, err error) { | |
98 | err = me.prepareReader() | |
99 | if err != nil { | |
100 | return | |
101 | } | |
102 | n, err = me.r.Read(b) | |
103 | me.off += int64(n) | |
104 | me.rOff += int64(n) | |
105 | return | |
106 | } | |
107 | ||
108 | func (me *File) Seek(offset int64, whence int) (ret int64, err error) { | |
109 | switch whence { | |
110 | case os.SEEK_SET: | |
111 | ret = offset | |
112 | case os.SEEK_CUR: | |
113 | ret = me.off + offset | |
114 | case os.SEEK_END: | |
115 | // Try to update the resource length. | |
116 | err = me.headLength() | |
117 | if err != nil { | |
118 | if me.length == -1 { | |
119 | // Don't even have an old value. | |
120 | return | |
121 | } | |
122 | err = nil | |
123 | } | |
124 | ret = me.length + offset | |
125 | default: | |
126 | err = fmt.Errorf("unhandled whence: %d", whence) | |
127 | return | |
128 | } | |
129 | me.off = ret | |
130 | return | |
131 | } | |
132 | ||
133 | func (me *File) Write(b []byte) (n int, err error) { | |
134 | if me.flags&(os.O_WRONLY|os.O_RDWR) == 0 || me.flags&os.O_CREATE == 0 { | |
135 | err = errors.New("cannot write without write and create flags") | |
136 | return | |
137 | } | |
138 | req, err := http.NewRequest("PATCH", me.url, bytes.NewReader(b)) | |
139 | if err != nil { | |
140 | return | |
141 | } | |
142 | req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-", me.off)) | |
143 | req.ContentLength = int64(len(b)) | |
144 | resp, err := me.fs.Client.Do(req) | |
145 | if err != nil { | |
146 | return | |
147 | } | |
148 | resp.Body.Close() | |
149 | if resp.StatusCode != http.StatusPartialContent { | |
150 | err = errors.New(resp.Status) | |
151 | return | |
152 | } | |
153 | n = len(b) | |
154 | me.off += int64(n) | |
155 | return | |
156 | } | |
157 | ||
158 | func (me *File) Close() error { | |
159 | me.url = "" | |
160 | me.length = -1 | |
161 | if me.r != nil { | |
162 | me.r.Close() | |
163 | me.r = nil | |
164 | } | |
165 | return nil | |
166 | } |
0 | package httpfile | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "io" | |
5 | "net/http" | |
6 | "os" | |
7 | ) | |
8 | ||
9 | type FS struct { | |
10 | Client *http.Client | |
11 | } | |
12 | ||
13 | func (fs *FS) Delete(urlStr string) (err error) { | |
14 | req, err := http.NewRequest("DELETE", urlStr, nil) | |
15 | if err != nil { | |
16 | return | |
17 | } | |
18 | resp, err := fs.Client.Do(req) | |
19 | if err != nil { | |
20 | return | |
21 | } | |
22 | resp.Body.Close() | |
23 | if resp.StatusCode == http.StatusNotFound { | |
24 | err = ErrNotFound | |
25 | return | |
26 | } | |
27 | if resp.StatusCode != 200 { | |
28 | err = fmt.Errorf("response: %s", resp.Status) | |
29 | } | |
30 | return | |
31 | } | |
32 | ||
33 | func (fs *FS) GetLength(url string) (ret int64, err error) { | |
34 | resp, err := fs.Client.Head(url) | |
35 | if err != nil { | |
36 | return | |
37 | } | |
38 | resp.Body.Close() | |
39 | if resp.StatusCode == http.StatusNotFound { | |
40 | err = ErrNotFound | |
41 | return | |
42 | } | |
43 | return instanceLength(resp) | |
44 | } | |
45 | ||
46 | func (fs *FS) OpenSectionReader(url string, off, n int64) (ret io.ReadCloser, err error) { | |
47 | req, err := http.NewRequest("GET", url, nil) | |
48 | if err != nil { | |
49 | return | |
50 | } | |
51 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+n-1)) | |
52 | resp, err := fs.Client.Do(req) | |
53 | if err != nil { | |
54 | return | |
55 | } | |
56 | if resp.StatusCode == http.StatusNotFound { | |
57 | err = ErrNotFound | |
58 | resp.Body.Close() | |
59 | return | |
60 | } | |
61 | if resp.StatusCode != http.StatusPartialContent { | |
62 | err = fmt.Errorf("bad response status: %s", resp.Status) | |
63 | resp.Body.Close() | |
64 | return | |
65 | } | |
66 | ret = resp.Body | |
67 | return | |
68 | } | |
69 | ||
70 | func (fs *FS) Open(url string, flags int) (ret *File, err error) { | |
71 | ret = &File{ | |
72 | url: url, | |
73 | flags: flags, | |
74 | length: -1, | |
75 | fs: fs, | |
76 | } | |
77 | if flags&os.O_CREATE == 0 { | |
78 | err = ret.headLength() | |
79 | } | |
80 | return | |
81 | } |
0 | package httpfile | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "net/http" | |
5 | "os" | |
6 | "strconv" | |
7 | ||
8 | "github.com/anacrolix/missinggo/httptoo" | |
9 | ) | |
10 | ||
11 | var ( | |
12 | ErrNotFound = os.ErrNotExist | |
13 | ) | |
14 | ||
15 | // ok is false if the response just doesn't specify anything we handle. | |
16 | func instanceLength(r *http.Response) (l int64, err error) { | |
17 | switch r.StatusCode { | |
18 | case http.StatusOK: | |
19 | l, err = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64) | |
20 | return | |
21 | case http.StatusPartialContent: | |
22 | cr, parseOk := httptoo.ParseBytesContentRange(r.Header.Get("Content-Range")) | |
23 | l = cr.Length | |
24 | if !parseOk { | |
25 | err = errors.New("error parsing Content-Range") | |
26 | } | |
27 | return | |
28 | default: | |
29 | err = errors.New("unhandled status code") | |
30 | return | |
31 | } | |
32 | } |
0 | package httpmux | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "fmt" | |
5 | "net/http" | |
6 | "path" | |
7 | "regexp" | |
8 | "strings" | |
9 | ||
10 | "go.opencensus.io/trace" | |
11 | ) | |
12 | ||
13 | var pathParamContextKey = new(struct{}) | |
14 | ||
15 | type Mux struct { | |
16 | handlers []Handler | |
17 | } | |
18 | ||
19 | func New() *Mux { | |
20 | return new(Mux) | |
21 | } | |
22 | ||
23 | type Handler struct { | |
24 | path *regexp.Regexp | |
25 | userHandler http.Handler | |
26 | } | |
27 | ||
28 | func (h Handler) Pattern() string { | |
29 | return h.path.String() | |
30 | } | |
31 | ||
32 | func (mux *Mux) GetHandler(r *http.Request) *Handler { | |
33 | matches := mux.matchingHandlers(r) | |
34 | if len(matches) == 0 { | |
35 | return nil | |
36 | } | |
37 | return &matches[0].Handler | |
38 | } | |
39 | ||
40 | func (me *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
41 | matches := me.matchingHandlers(r) | |
42 | if len(matches) == 0 { | |
43 | http.NotFound(w, r) | |
44 | return | |
45 | } | |
46 | m := matches[0] | |
47 | ctx := context.WithValue(r.Context(), pathParamContextKey, &PathParams{m}) | |
48 | ctx, span := trace.StartSpan(ctx, m.Handler.path.String(), trace.WithSpanKind(trace.SpanKindServer)) | |
49 | defer span.End() | |
50 | r = r.WithContext(ctx) | |
51 | defer func() { | |
52 | r := recover() | |
53 | if r == http.ErrAbortHandler { | |
54 | panic(r) | |
55 | } | |
56 | if r == nil { | |
57 | return | |
58 | } | |
59 | panic(fmt.Sprintf("while handling %q: %s", m.Handler.path.String(), r)) | |
60 | }() | |
61 | m.Handler.userHandler.ServeHTTP(w, r) | |
62 | } | |
63 | ||
64 | type match struct { | |
65 | Handler Handler | |
66 | submatches []string | |
67 | } | |
68 | ||
69 | func (me *Mux) matchingHandlers(r *http.Request) (ret []match) { | |
70 | for _, h := range me.handlers { | |
71 | subs := h.path.FindStringSubmatch(r.URL.Path) | |
72 | if subs == nil { | |
73 | continue | |
74 | } | |
75 | ret = append(ret, match{h, subs}) | |
76 | } | |
77 | return | |
78 | } | |
79 | ||
80 | func (me *Mux) distinctHandlerRegexp(r *regexp.Regexp) bool { | |
81 | for _, h := range me.handlers { | |
82 | if h.path.String() == r.String() { | |
83 | return false | |
84 | } | |
85 | } | |
86 | return true | |
87 | } | |
88 | ||
89 | func (me *Mux) Handle(path string, h http.Handler) { | |
90 | expr := "^" + path | |
91 | if !strings.HasSuffix(expr, "$") { | |
92 | expr += "$" | |
93 | } | |
94 | re, err := regexp.Compile(expr) | |
95 | if err != nil { | |
96 | panic(err) | |
97 | } | |
98 | if !me.distinctHandlerRegexp(re) { | |
99 | panic(fmt.Sprintf("path %q is not distinct", path)) | |
100 | } | |
101 | me.handlers = append(me.handlers, Handler{re, h}) | |
102 | } | |
103 | ||
104 | func (me *Mux) HandleFunc(path string, hf func(http.ResponseWriter, *http.Request)) { | |
105 | me.Handle(path, http.HandlerFunc(hf)) | |
106 | } | |
107 | ||
108 | func Path(parts ...string) string { | |
109 | return path.Join(parts...) | |
110 | } | |
111 | ||
112 | type PathParams struct { | |
113 | match match | |
114 | } | |
115 | ||
116 | func (me *PathParams) ByName(name string) string { | |
117 | for i, sn := range me.match.Handler.path.SubexpNames()[1:] { | |
118 | if sn == name { | |
119 | return me.match.submatches[i+1] | |
120 | } | |
121 | } | |
122 | return "" | |
123 | } | |
124 | ||
125 | func RequestPathParams(r *http.Request) *PathParams { | |
126 | ctx := r.Context() | |
127 | return ctx.Value(pathParamContextKey).(*PathParams) | |
128 | } | |
129 | ||
130 | func PathRegexpParam(name string, re string) string { | |
131 | return fmt.Sprintf("(?P<%s>%s)", name, re) | |
132 | } | |
133 | ||
134 | func Param(name string) string { | |
135 | return fmt.Sprintf("(?P<%s>[^/]+)", name) | |
136 | } | |
137 | ||
138 | func RestParam(name string) string { | |
139 | return fmt.Sprintf("(?P<%s>.*)$", name) | |
140 | } | |
141 | ||
142 | func NonEmptyRestParam(name string) string { | |
143 | return fmt.Sprintf("(?P<%s>.+)$", name) | |
144 | } |
0 | package missinggo | |
1 | ||
2 | // todo move to httptoo as ResponseRecorder | |
3 | ||
4 | import ( | |
5 | "bufio" | |
6 | "net" | |
7 | "net/http" | |
8 | "time" | |
9 | ) | |
10 | ||
11 | // A http.ResponseWriter that tracks the status of the response. The status | |
12 | // code, and number of bytes written for example. | |
13 | type StatusResponseWriter struct { | |
14 | http.ResponseWriter | |
15 | Code int | |
16 | BytesWritten int64 | |
17 | Started time.Time | |
18 | TimeToFirstByte time.Duration // Time to first byte | |
19 | GotFirstByte bool | |
20 | WroteHeader Event | |
21 | Hijacked bool | |
22 | } | |
23 | ||
24 | var _ interface { | |
25 | http.ResponseWriter | |
26 | http.Hijacker | |
27 | } = (*StatusResponseWriter)(nil) | |
28 | ||
29 | func (me *StatusResponseWriter) Write(b []byte) (n int, err error) { | |
30 | // Exactly how it's done in the standard library. This ensures Code is | |
31 | // correct. | |
32 | if !me.WroteHeader.IsSet() { | |
33 | me.WriteHeader(http.StatusOK) | |
34 | } | |
35 | if me.Started.IsZero() { | |
36 | panic("Started was not initialized") | |
37 | } | |
38 | timeBeforeWrite := time.Now() | |
39 | n, err = me.ResponseWriter.Write(b) | |
40 | if n > 0 && !me.GotFirstByte { | |
41 | me.TimeToFirstByte = timeBeforeWrite.Sub(me.Started) | |
42 | me.GotFirstByte = true | |
43 | } | |
44 | me.BytesWritten += int64(n) | |
45 | return | |
46 | } | |
47 | ||
48 | func (me *StatusResponseWriter) WriteHeader(code int) { | |
49 | me.ResponseWriter.WriteHeader(code) | |
50 | if !me.WroteHeader.IsSet() { | |
51 | me.Code = code | |
52 | me.WroteHeader.Set() | |
53 | } | |
54 | } | |
55 | ||
56 | func (me *StatusResponseWriter) Hijack() (c net.Conn, b *bufio.ReadWriter, err error) { | |
57 | me.Hijacked = true | |
58 | c, b, err = me.ResponseWriter.(http.Hijacker).Hijack() | |
59 | if b.Writer.Buffered() != 0 { | |
60 | panic("unexpected buffered writes") | |
61 | } | |
62 | c = responseConn{c, me} | |
63 | return | |
64 | } | |
65 | ||
66 | type responseConn struct { | |
67 | net.Conn | |
68 | s *StatusResponseWriter | |
69 | } | |
70 | ||
71 | func (me responseConn) Write(b []byte) (n int, err error) { | |
72 | n, err = me.Conn.Write(b) | |
73 | me.s.BytesWritten += int64(n) | |
74 | return | |
75 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "strconv" | |
5 | "strings" | |
6 | ||
7 | "github.com/anacrolix/missinggo/mime" | |
8 | ) | |
9 | ||
10 | func ParseAccept(line string) (parsed AcceptDirectives, err error) { | |
11 | dirs := strings.Split(line, ",") | |
12 | for _, d := range dirs { | |
13 | p := AcceptDirective{ | |
14 | Q: 1, | |
15 | } | |
16 | ss := strings.Split(d, ";") | |
17 | switch len(ss) { | |
18 | case 2: | |
19 | p.Q, err = strconv.ParseFloat(ss[1], 32) | |
20 | if err != nil { | |
21 | return | |
22 | } | |
23 | fallthrough | |
24 | case 1: | |
25 | p.MimeType.FromString(ss[0]) | |
26 | default: | |
27 | err = fmt.Errorf("error parsing %q", d) | |
28 | return | |
29 | } | |
30 | parsed = append(parsed, p) | |
31 | } | |
32 | return | |
33 | } | |
34 | ||
35 | type ( | |
36 | AcceptDirectives []AcceptDirective | |
37 | AcceptDirective struct { | |
38 | MimeType mime.Type | |
39 | Q float64 | |
40 | } | |
41 | ) |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "math" | |
5 | "regexp" | |
6 | "strconv" | |
7 | "strings" | |
8 | ) | |
9 | ||
10 | type BytesContentRange struct { | |
11 | First, Last, Length int64 | |
12 | } | |
13 | ||
14 | type BytesRange struct { | |
15 | First, Last int64 | |
16 | } | |
17 | ||
18 | func (me BytesRange) String() string { | |
19 | if me.Last == math.MaxInt64 { | |
20 | return fmt.Sprintf("bytes=%d-", me.First) | |
21 | } | |
22 | return fmt.Sprintf("bytes=%d-%d", me.First, me.Last) | |
23 | } | |
24 | ||
25 | var ( | |
26 | httpBytesRangeRegexp = regexp.MustCompile(`bytes[ =](\d+)-(\d*)`) | |
27 | ) | |
28 | ||
29 | func ParseBytesRange(s string) (ret BytesRange, ok bool) { | |
30 | ss := httpBytesRangeRegexp.FindStringSubmatch(s) | |
31 | if ss == nil { | |
32 | return | |
33 | } | |
34 | var err error | |
35 | ret.First, err = strconv.ParseInt(ss[1], 10, 64) | |
36 | if err != nil { | |
37 | return | |
38 | } | |
39 | if ss[2] == "" { | |
40 | ret.Last = math.MaxInt64 | |
41 | } else { | |
42 | ret.Last, err = strconv.ParseInt(ss[2], 10, 64) | |
43 | if err != nil { | |
44 | return | |
45 | } | |
46 | } | |
47 | ok = true | |
48 | return | |
49 | } | |
50 | ||
51 | func parseUnitRanges(s string) (unit, ranges string) { | |
52 | s = strings.TrimSpace(s) | |
53 | i := strings.IndexAny(s, " =") | |
54 | if i == -1 { | |
55 | return | |
56 | } | |
57 | unit = s[:i] | |
58 | ranges = s[i+1:] | |
59 | return | |
60 | } | |
61 | ||
62 | func parseFirstLast(s string) (first, last int64) { | |
63 | ss := strings.SplitN(s, "-", 2) | |
64 | first, err := strconv.ParseInt(ss[0], 10, 64) | |
65 | if err != nil { | |
66 | panic(err) | |
67 | } | |
68 | last, err = strconv.ParseInt(ss[1], 10, 64) | |
69 | if err != nil { | |
70 | panic(err) | |
71 | } | |
72 | return | |
73 | } | |
74 | ||
75 | func parseContentRange(s string) (ret BytesContentRange) { | |
76 | ss := strings.SplitN(s, "/", 2) | |
77 | firstLast := strings.TrimSpace(ss[0]) | |
78 | if firstLast == "*" { | |
79 | ret.First = -1 | |
80 | ret.Last = -1 | |
81 | } else { | |
82 | ret.First, ret.Last = parseFirstLast(firstLast) | |
83 | } | |
84 | il := strings.TrimSpace(ss[1]) | |
85 | if il == "*" { | |
86 | ret.Length = -1 | |
87 | } else { | |
88 | var err error | |
89 | ret.Length, err = strconv.ParseInt(il, 10, 64) | |
90 | if err != nil { | |
91 | panic(err) | |
92 | } | |
93 | } | |
94 | return | |
95 | } | |
96 | ||
97 | func ParseBytesContentRange(s string) (ret BytesContentRange, ok bool) { | |
98 | unit, ranges := parseUnitRanges(s) | |
99 | if unit != "bytes" { | |
100 | return | |
101 | } | |
102 | ret = parseContentRange(ranges) | |
103 | ok = true | |
104 | return | |
105 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestParseHTTPContentRange(t *testing.T) { | |
9 | for _, _case := range []struct { | |
10 | h string | |
11 | cr *BytesContentRange | |
12 | }{ | |
13 | {"", nil}, | |
14 | {"1-2/*", nil}, | |
15 | {"bytes=1-2/3", &BytesContentRange{1, 2, 3}}, | |
16 | {"bytes=12-34/*", &BytesContentRange{12, 34, -1}}, | |
17 | {" bytes=12-34/*", &BytesContentRange{12, 34, -1}}, | |
18 | {" bytes 12-34/56", &BytesContentRange{12, 34, 56}}, | |
19 | {" bytes=*/56", &BytesContentRange{-1, -1, 56}}, | |
20 | } { | |
21 | ret, ok := ParseBytesContentRange(_case.h) | |
22 | assert.Equal(t, _case.cr != nil, ok) | |
23 | if _case.cr != nil { | |
24 | assert.Equal(t, *_case.cr, ret) | |
25 | } | |
26 | } | |
27 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "crypto/tls" | |
4 | "net/http" | |
5 | ) | |
6 | ||
7 | // Returns the http.Client's TLS Config, traversing and generating any | |
8 | // defaults along the way to get it. | |
9 | func ClientTLSConfig(cl *http.Client) *tls.Config { | |
10 | if cl.Transport == nil { | |
11 | cl.Transport = http.DefaultTransport | |
12 | } | |
13 | tr := cl.Transport.(*http.Transport) | |
14 | if tr.TLSClientConfig == nil { | |
15 | tr.TLSClientConfig = &tls.Config{} | |
16 | } | |
17 | return tr.TLSClientConfig | |
18 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | "os" | |
5 | ) | |
6 | ||
7 | // Wraps a http.FileSystem, disabling directory listings, per the commonly | |
8 | // requested feature at https://groups.google.com/forum/#!topic/golang- | |
9 | // nuts/bStLPdIVM6w . | |
10 | type JustFilesFilesystem struct { | |
11 | Fs http.FileSystem | |
12 | } | |
13 | ||
14 | func (fs JustFilesFilesystem) Open(name string) (http.File, error) { | |
15 | f, err := fs.Fs.Open(name) | |
16 | if err != nil { | |
17 | return nil, err | |
18 | } | |
19 | d, err := f.Stat() | |
20 | if err != nil { | |
21 | f.Close() | |
22 | return nil, err | |
23 | } | |
24 | if d.IsDir() { | |
25 | f.Close() | |
26 | // This triggers http.FileServer to show a 404. | |
27 | return nil, os.ErrNotExist | |
28 | } | |
29 | return f, nil | |
30 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "compress/gzip" | |
4 | "io" | |
5 | "net/http" | |
6 | "strings" | |
7 | ) | |
8 | ||
9 | type gzipResponseWriter struct { | |
10 | io.Writer | |
11 | http.ResponseWriter | |
12 | haveWritten bool | |
13 | } | |
14 | ||
15 | var _ http.ResponseWriter = &gzipResponseWriter{} | |
16 | ||
17 | func (w *gzipResponseWriter) Write(b []byte) (int, error) { | |
18 | if w.haveWritten { | |
19 | goto write | |
20 | } | |
21 | w.haveWritten = true | |
22 | if w.Header().Get("Content-Type") != "" { | |
23 | goto write | |
24 | } | |
25 | if type_ := http.DetectContentType(b); type_ != "application/octet-stream" { | |
26 | w.Header().Set("Content-Type", type_) | |
27 | } | |
28 | write: | |
29 | return w.Writer.Write(b) | |
30 | } | |
31 | ||
32 | // Gzips response body if the request says it'll allow it. | |
33 | func GzipHandler(h http.Handler) http.Handler { | |
34 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
35 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") || w.Header().Get("Content-Encoding") != "" || w.Header().Get("Vary") != "" { | |
36 | h.ServeHTTP(w, r) | |
37 | return | |
38 | } | |
39 | w.Header().Set("Content-Encoding", "gzip") | |
40 | w.Header().Set("Vary", "Accept-Encoding") | |
41 | gz := gzip.NewWriter(w) | |
42 | defer gz.Close() | |
43 | h.ServeHTTP(&gzipResponseWriter{ | |
44 | Writer: gz, | |
45 | ResponseWriter: w, | |
46 | }, r) | |
47 | }) | |
48 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "compress/gzip" | |
4 | "io/ioutil" | |
5 | "net/http" | |
6 | "net/http/httptest" | |
7 | "testing" | |
8 | ||
9 | "github.com/stretchr/testify/assert" | |
10 | "github.com/stretchr/testify/require" | |
11 | ) | |
12 | ||
13 | const helloWorld = "hello, world\n" | |
14 | ||
15 | func helloWorldHandler(w http.ResponseWriter, r *http.Request) { | |
16 | // w.Header().Set("Content-Length", strconv.FormatInt(int64(len(helloWorld)), 10)) | |
17 | w.Write([]byte(helloWorld)) | |
18 | } | |
19 | ||
20 | func requestResponse(h http.Handler, r *http.Request) (*http.Response, error) { | |
21 | s := httptest.NewServer(h) | |
22 | defer s.Close() | |
23 | return http.DefaultClient.Do(r) | |
24 | } | |
25 | ||
26 | func TestGzipHandler(t *testing.T) { | |
27 | rr := httptest.NewRecorder() | |
28 | helloWorldHandler(rr, nil) | |
29 | assert.EqualValues(t, helloWorld, rr.Body.String()) | |
30 | ||
31 | rr = httptest.NewRecorder() | |
32 | GzipHandler(http.HandlerFunc(helloWorldHandler)).ServeHTTP(rr, new(http.Request)) | |
33 | assert.EqualValues(t, helloWorld, rr.Body.String()) | |
34 | ||
35 | rr = httptest.NewRecorder() | |
36 | r, err := http.NewRequest("GET", "/", nil) | |
37 | require.NoError(t, err) | |
38 | r.Header.Set("Accept-Encoding", "gzip") | |
39 | GzipHandler(http.HandlerFunc(helloWorldHandler)).ServeHTTP(rr, r) | |
40 | gr, err := gzip.NewReader(rr.Body) | |
41 | require.NoError(t, err) | |
42 | defer gr.Close() | |
43 | b, err := ioutil.ReadAll(gr) | |
44 | require.NoError(t, err) | |
45 | assert.EqualValues(t, helloWorld, b) | |
46 | ||
47 | s := httptest.NewServer(nil) | |
48 | s.Config.Handler = GzipHandler(http.HandlerFunc(helloWorldHandler)) | |
49 | req, err := http.NewRequest("GET", s.URL, nil) | |
50 | req.Header.Set("Accept-Encoding", "gzip") | |
51 | resp, err := http.DefaultClient.Do(req) | |
52 | require.NoError(t, err) | |
53 | gr.Close() | |
54 | gr, err = gzip.NewReader(resp.Body) | |
55 | require.NoError(t, err) | |
56 | defer gr.Close() | |
57 | b, err = ioutil.ReadAll(gr) | |
58 | require.NoError(t, err) | |
59 | assert.EqualValues(t, helloWorld, b) | |
60 | assert.EqualValues(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type")) | |
61 | assert.EqualValues(t, "gzip", resp.Header.Get("Content-Encoding")) | |
62 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "strings" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | type Visibility int | |
9 | ||
10 | const ( | |
11 | Default = 0 | |
12 | Public = 1 | |
13 | Private = 2 | |
14 | ) | |
15 | ||
16 | type CacheControlHeader struct { | |
17 | MaxAge time.Duration | |
18 | Caching Visibility | |
19 | NoStore bool | |
20 | } | |
21 | ||
22 | func (me *CacheControlHeader) caching() []string { | |
23 | switch me.Caching { | |
24 | case Public: | |
25 | return []string{"public"} | |
26 | case Private: | |
27 | return []string{"private"} | |
28 | default: | |
29 | return nil | |
30 | } | |
31 | } | |
32 | ||
33 | func (me *CacheControlHeader) maxAge() []string { | |
34 | if me.MaxAge == 0 { | |
35 | return nil | |
36 | } | |
37 | d := me.MaxAge | |
38 | if d < 0 { | |
39 | d = 0 | |
40 | } | |
41 | return []string{fmt.Sprintf("max-age=%d", d/time.Second)} | |
42 | } | |
43 | ||
44 | func (me *CacheControlHeader) noStore() []string { | |
45 | if me.NoStore { | |
46 | return []string{"no-store"} | |
47 | } | |
48 | return nil | |
49 | } | |
50 | ||
51 | func (me *CacheControlHeader) concat(sss ...[]string) (ret []string) { | |
52 | for _, ss := range sss { | |
53 | ret = append(ret, ss...) | |
54 | } | |
55 | return | |
56 | } | |
57 | ||
58 | func (me CacheControlHeader) String() string { | |
59 | return strings.Join(me.concat(me.caching(), me.maxAge()), ", ") | |
60 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func TestCacheControlHeaderString(t *testing.T) { | |
10 | assert.Equal(t, "public, max-age=43200", CacheControlHeader{ | |
11 | MaxAge: 12 * time.Hour, | |
12 | Caching: Public, | |
13 | }.String()) | |
14 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | "strconv" | |
5 | "strings" | |
6 | ||
7 | "github.com/bradfitz/iter" | |
8 | ||
9 | "github.com/anacrolix/missinggo" | |
10 | ) | |
11 | ||
12 | func OriginatingProtocol(r *http.Request) string { | |
13 | if fp := r.Header.Get("X-Forwarded-Proto"); fp != "" { | |
14 | return fp | |
15 | } else if r.TLS != nil { | |
16 | return "https" | |
17 | } else { | |
18 | return "http" | |
19 | } | |
20 | } | |
21 | ||
22 | // Clears the named cookie for every domain that leads to the current one. | |
23 | func NukeCookie(w http.ResponseWriter, r *http.Request, name, path string) { | |
24 | parts := strings.Split(missinggo.SplitHostMaybePort(r.Host).Host, ".") | |
25 | for i := range iter.N(len(parts) + 1) { // Include the empty domain. | |
26 | http.SetCookie(w, &http.Cookie{ | |
27 | Name: name, | |
28 | MaxAge: -1, | |
29 | Path: path, | |
30 | Domain: strings.Join(parts[i:], "."), | |
31 | }) | |
32 | } | |
33 | } | |
34 | ||
35 | // Performs quoted-string from http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html | |
36 | func EncodeQuotedString(s string) string { | |
37 | return strconv.Quote(s) | |
38 | } | |
39 | ||
40 | // https://httpstatuses.com/499 | |
41 | const StatusClientCancelledRequest = 499 |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "io" | |
5 | "net/http" | |
6 | "sync" | |
7 | ||
8 | "github.com/anacrolix/missinggo" | |
9 | ) | |
10 | ||
11 | type responseWriter struct { | |
12 | mu sync.Mutex | |
13 | r http.Response | |
14 | headerWritten missinggo.Event | |
15 | bodyWriter io.WriteCloser | |
16 | bodyClosed missinggo.SynchronizedEvent | |
17 | } | |
18 | ||
19 | var _ interface { | |
20 | http.ResponseWriter | |
21 | // We're able to emulate this easily enough. | |
22 | http.CloseNotifier | |
23 | } = &responseWriter{} | |
24 | ||
25 | // Use Request.Context.Done instead. | |
26 | func (me *responseWriter) CloseNotify() <-chan bool { | |
27 | ret := make(chan bool, 1) | |
28 | go func() { | |
29 | <-me.bodyClosed.C() | |
30 | ret <- true | |
31 | }() | |
32 | return ret | |
33 | } | |
34 | ||
35 | func (me *responseWriter) Header() http.Header { | |
36 | if me.r.Header == nil { | |
37 | me.r.Header = make(http.Header) | |
38 | } | |
39 | return me.r.Header | |
40 | } | |
41 | ||
42 | func (me *responseWriter) Write(b []byte) (int, error) { | |
43 | me.mu.Lock() | |
44 | if !me.headerWritten.IsSet() { | |
45 | me.writeHeader(200) | |
46 | } | |
47 | me.mu.Unlock() | |
48 | return me.bodyWriter.Write(b) | |
49 | } | |
50 | ||
51 | func (me *responseWriter) WriteHeader(status int) { | |
52 | me.mu.Lock() | |
53 | me.writeHeader(status) | |
54 | me.mu.Unlock() | |
55 | } | |
56 | ||
57 | func (me *responseWriter) writeHeader(status int) { | |
58 | if me.headerWritten.IsSet() { | |
59 | return | |
60 | } | |
61 | me.r.StatusCode = status | |
62 | me.headerWritten.Set() | |
63 | } | |
64 | ||
65 | func (me *responseWriter) runHandler(h http.Handler, req *http.Request) { | |
66 | var pr *io.PipeReader | |
67 | pr, me.bodyWriter = io.Pipe() | |
68 | me.r.Body = struct { | |
69 | io.Reader | |
70 | io.Closer | |
71 | }{pr, eventCloser{pr, &me.bodyClosed}} | |
72 | // Shouldn't be writing to the response after the handler returns. | |
73 | defer me.bodyWriter.Close() | |
74 | // Send a 200 if nothing was written yet. | |
75 | defer me.WriteHeader(200) | |
76 | // Wrap the context in the given Request with one that closes when either | |
77 | // the handler returns, or the response body is closed. | |
78 | ctx, cancel := context.WithCancel(req.Context()) | |
79 | defer cancel() | |
80 | go func() { | |
81 | <-me.bodyClosed.C() | |
82 | cancel() | |
83 | }() | |
84 | h.ServeHTTP(me, req.WithContext(ctx)) | |
85 | } | |
86 | ||
87 | type eventCloser struct { | |
88 | c io.Closer | |
89 | closed *missinggo.SynchronizedEvent | |
90 | } | |
91 | ||
92 | func (me eventCloser) Close() (err error) { | |
93 | err = me.c.Close() | |
94 | me.closed.Set() | |
95 | return | |
96 | } | |
97 | ||
98 | func RoundTripHandler(req *http.Request, h http.Handler) (*http.Response, error) { | |
99 | rw := responseWriter{} | |
100 | go rw.runHandler(h, req) | |
101 | <-rw.headerWritten.LockedChan(&rw.mu) | |
102 | return &rw.r, nil | |
103 | } | |
104 | ||
105 | type InProcRoundTripper struct { | |
106 | Handler http.Handler | |
107 | } | |
108 | ||
109 | func (me *InProcRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | |
110 | return RoundTripHandler(req, me.Handler) | |
111 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "net" | |
4 | "net/http" | |
5 | ||
6 | "github.com/anacrolix/missinggo" | |
7 | ) | |
8 | ||
9 | // Request is intended for localhost, either with a localhost name, or | |
10 | // loopback IP. | |
11 | func RequestIsForLocalhost(r *http.Request) bool { | |
12 | hostHost := missinggo.SplitHostMaybePort(r.Host).Host | |
13 | if ip := net.ParseIP(hostHost); ip != nil { | |
14 | return ip.IsLoopback() | |
15 | } | |
16 | return hostHost == "localhost" | |
17 | } | |
18 | ||
19 | // Request originated from a loopback IP. | |
20 | func RequestIsFromLocalhost(r *http.Request) bool { | |
21 | return net.ParseIP(missinggo.SplitHostMaybePort(r.RemoteAddr).Host).IsLoopback() | |
22 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "bufio" | |
4 | "encoding/gob" | |
5 | "io" | |
6 | "net" | |
7 | "net/http" | |
8 | "net/url" | |
9 | "sync" | |
10 | ) | |
11 | ||
12 | func deepCopy(dst, src interface{}) error { | |
13 | r, w := io.Pipe() | |
14 | e := gob.NewEncoder(w) | |
15 | d := gob.NewDecoder(r) | |
16 | var decErr, encErr error | |
17 | var wg sync.WaitGroup | |
18 | wg.Add(1) | |
19 | go func() { | |
20 | defer wg.Done() | |
21 | decErr = d.Decode(dst) | |
22 | r.Close() | |
23 | }() | |
24 | encErr = e.Encode(src) | |
25 | // Always returns nil. | |
26 | w.CloseWithError(encErr) | |
27 | wg.Wait() | |
28 | if encErr != nil { | |
29 | return encErr | |
30 | } | |
31 | return decErr | |
32 | } | |
33 | ||
34 | // Takes a request, and alters its destination fields, for proxying. | |
35 | func RedirectedRequest(r *http.Request, newUrl string) (ret *http.Request, err error) { | |
36 | u, err := url.Parse(newUrl) | |
37 | if err != nil { | |
38 | return | |
39 | } | |
40 | ret = new(http.Request) | |
41 | *ret = *r | |
42 | ret.Header = nil | |
43 | err = deepCopy(&ret.Header, r.Header) | |
44 | if err != nil { | |
45 | return | |
46 | } | |
47 | ret.URL = u | |
48 | ret.RequestURI = "" | |
49 | return | |
50 | } | |
51 | ||
52 | func CopyHeaders(w http.ResponseWriter, r *http.Response) { | |
53 | for h, vs := range r.Header { | |
54 | for _, v := range vs { | |
55 | w.Header().Add(h, v) | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | func ForwardResponse(w http.ResponseWriter, r *http.Response) { | |
61 | CopyHeaders(w, r) | |
62 | w.WriteHeader(r.StatusCode) | |
63 | // Errors frequently occur writing the body when the client hangs up. | |
64 | io.Copy(w, r.Body) | |
65 | r.Body.Close() | |
66 | } | |
67 | ||
68 | func SetOriginRequestForwardingHeaders(o, f *http.Request) { | |
69 | xff := o.Header.Get("X-Forwarded-For") | |
70 | hop, _, _ := net.SplitHostPort(f.RemoteAddr) | |
71 | if xff == "" { | |
72 | xff = hop | |
73 | } else { | |
74 | xff += "," + hop | |
75 | } | |
76 | o.Header.Set("X-Forwarded-For", xff) | |
77 | o.Header.Set("X-Forwarded-Proto", OriginatingProtocol(f)) | |
78 | } | |
79 | ||
80 | // w is for the client response. r is the request to send to the origin | |
81 | // (already "forwarded"). originUrl is where to send the request. | |
82 | func ReverseProxyUpgrade(w http.ResponseWriter, r *http.Request, originUrl string) (err error) { | |
83 | u, err := url.Parse(originUrl) | |
84 | if err != nil { | |
85 | return | |
86 | } | |
87 | oc, err := net.Dial("tcp", u.Host) | |
88 | if err != nil { | |
89 | return | |
90 | } | |
91 | defer oc.Close() | |
92 | err = r.Write(oc) | |
93 | if err != nil { | |
94 | return | |
95 | } | |
96 | originConnReadBuffer := bufio.NewReader(oc) | |
97 | originResp, err := http.ReadResponse(originConnReadBuffer, r) | |
98 | if err != nil { | |
99 | return | |
100 | } | |
101 | if originResp.StatusCode != 101 { | |
102 | ForwardResponse(w, originResp) | |
103 | return | |
104 | } | |
105 | cc, _, err := w.(http.Hijacker).Hijack() | |
106 | if err != nil { | |
107 | return | |
108 | } | |
109 | defer cc.Close() | |
110 | originResp.Write(cc) | |
111 | go io.Copy(oc, cc) | |
112 | // Let the origin connection control when this routine returns, as we | |
113 | // should trust it more. | |
114 | io.Copy(cc, originConnReadBuffer) | |
115 | return | |
116 | } | |
117 | ||
118 | func ReverseProxy(w http.ResponseWriter, r *http.Request, originUrl string, client *http.Client) (err error) { | |
119 | originRequest, err := RedirectedRequest(r, originUrl) | |
120 | if err != nil { | |
121 | return | |
122 | } | |
123 | SetOriginRequestForwardingHeaders(originRequest, r) | |
124 | if r.Header.Get("Connection") == "Upgrade" { | |
125 | return ReverseProxyUpgrade(w, originRequest, originUrl) | |
126 | } | |
127 | rt := client.Transport | |
128 | if rt == nil { | |
129 | rt = http.DefaultTransport | |
130 | } | |
131 | originResp, err := rt.RoundTrip(originRequest) | |
132 | if err != nil { | |
133 | return | |
134 | } | |
135 | ForwardResponse(w, originResp) | |
136 | return | |
137 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | "net/url" | |
5 | ) | |
6 | ||
7 | // Deep copies a URL. I could call it DeepCopyURL, but what else would you be | |
8 | // copying when you have a *url.URL? Of note is that the Userinfo is deep | |
9 | // copied. The returned URL shares no references with the original. | |
10 | func CopyURL(u *url.URL) (ret *url.URL) { | |
11 | ret = new(url.URL) | |
12 | *ret = *u | |
13 | if u.User != nil { | |
14 | ret.User = new(url.Userinfo) | |
15 | *ret.User = *u.User | |
16 | } | |
17 | return | |
18 | } | |
19 | ||
20 | // Reconstructs the URL that would have produced the given Request. | |
21 | // Request.URLs are not fully populated in http.Server handlers. | |
22 | func RequestedURL(r *http.Request) (ret *url.URL) { | |
23 | ret = CopyURL(r.URL) | |
24 | ret.Host = r.Host | |
25 | ret.Scheme = OriginatingProtocol(r) | |
26 | return | |
27 | } | |
28 | ||
29 | // The official URL struct parameters, for tracking changes and reference | |
30 | // here. | |
31 | // | |
32 | // Scheme string | |
33 | // Opaque string // encoded opaque data | |
34 | // User *Userinfo // username and password information | |
35 | // Host string // host or host:port | |
36 | // Path string | |
37 | // RawPath string // encoded path hint (Go 1.5 and later only; see EscapedPath method) | |
38 | // ForceQuery bool // append a query ('?') even if RawQuery is empty | |
39 | // RawQuery string // encoded query values, without '?' | |
40 | // Fragment string // fragment for references, without '#' | |
41 | ||
42 | // Return the first URL extended with elements of the second, in the manner | |
43 | // that occurs throughout my projects. Noteworthy difference from | |
44 | // url.URL.ResolveReference is that if the reference has a scheme, the base is | |
45 | // not completely ignored. | |
46 | func AppendURL(u, v *url.URL) *url.URL { | |
47 | u = CopyURL(u) | |
48 | clobberString(&u.Scheme, v.Scheme) | |
49 | clobberString(&u.Host, v.Host) | |
50 | u.Path += v.Path | |
51 | q := u.Query() | |
52 | for k, v := range v.Query() { | |
53 | q[k] = append(q[k], v...) | |
54 | } | |
55 | u.RawQuery = q.Encode() | |
56 | return u | |
57 | } | |
58 | ||
59 | func clobberString(s *string, value string) { | |
60 | if value != "" { | |
61 | *s = value | |
62 | } | |
63 | } |
0 | package httptoo | |
1 | ||
2 | import ( | |
3 | "net/url" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func TestAppendURL(t *testing.T) { | |
10 | assert.EqualValues(t, "http://localhost:8080/trailing/slash/", AppendURL( | |
11 | &url.URL{Scheme: "http", Host: "localhost:8080"}, | |
12 | &url.URL{Path: "/trailing/slash/"}, | |
13 | ).String()) | |
14 | assert.EqualValues(t, "ws://localhost:8080/events?ih=harpdarp", AppendURL( | |
15 | &url.URL{Scheme: "http", Host: "localhost:8080"}, | |
16 | &url.URL{Scheme: "ws", Path: "/events", RawQuery: "ih=harpdarp"}, | |
17 | ).String()) | |
18 | } |
0 | package inproc | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "io" | |
5 | "math" | |
6 | "net" | |
7 | "strconv" | |
8 | "sync" | |
9 | "time" | |
10 | ||
11 | "github.com/anacrolix/missinggo" | |
12 | ) | |
13 | ||
14 | var ( | |
15 | mu sync.Mutex | |
16 | cond = sync.Cond{L: &mu} | |
17 | nextPort int = 1 | |
18 | conns = map[int]*packetConn{} | |
19 | ) | |
20 | ||
21 | type Addr struct { | |
22 | Port int | |
23 | } | |
24 | ||
25 | func (Addr) Network() string { | |
26 | return "inproc" | |
27 | } | |
28 | ||
29 | func (me Addr) String() string { | |
30 | return ":" + strconv.FormatInt(int64(me.Port), 10) | |
31 | } | |
32 | ||
33 | func getPort() (port int) { | |
34 | mu.Lock() | |
35 | defer mu.Unlock() | |
36 | port = nextPort | |
37 | nextPort++ | |
38 | return | |
39 | } | |
40 | ||
41 | func ResolveAddr(network, str string) (net.Addr, error) { | |
42 | return ResolveInprocAddr(network, str) | |
43 | } | |
44 | ||
45 | func ResolveInprocAddr(network, str string) (addr Addr, err error) { | |
46 | if str == "" { | |
47 | addr.Port = getPort() | |
48 | return | |
49 | } | |
50 | _, p, err := net.SplitHostPort(str) | |
51 | if err != nil { | |
52 | return | |
53 | } | |
54 | i64, err := strconv.ParseInt(p, 10, 0) | |
55 | if err != nil { | |
56 | return | |
57 | } | |
58 | addr.Port = int(i64) | |
59 | if addr.Port == 0 { | |
60 | addr.Port = getPort() | |
61 | } | |
62 | return | |
63 | } | |
64 | ||
65 | func ListenPacket(network, addrStr string) (nc net.PacketConn, err error) { | |
66 | addr, err := ResolveInprocAddr(network, addrStr) | |
67 | if err != nil { | |
68 | return | |
69 | } | |
70 | mu.Lock() | |
71 | defer mu.Unlock() | |
72 | if _, ok := conns[addr.Port]; ok { | |
73 | err = errors.New("address in use") | |
74 | return | |
75 | } | |
76 | pc := &packetConn{ | |
77 | addr: addr, | |
78 | readDeadline: newCondDeadline(&cond), | |
79 | writeDeadline: newCondDeadline(&cond), | |
80 | } | |
81 | conns[addr.Port] = pc | |
82 | nc = pc | |
83 | return | |
84 | } | |
85 | ||
86 | type packet struct { | |
87 | data []byte | |
88 | addr Addr | |
89 | } | |
90 | ||
91 | type packetConn struct { | |
92 | closed bool | |
93 | addr Addr | |
94 | reads []packet | |
95 | readDeadline *condDeadline | |
96 | writeDeadline *condDeadline | |
97 | } | |
98 | ||
99 | func (me *packetConn) Close() error { | |
100 | mu.Lock() | |
101 | defer mu.Unlock() | |
102 | me.closed = true | |
103 | delete(conns, me.addr.Port) | |
104 | cond.Broadcast() | |
105 | return nil | |
106 | } | |
107 | ||
108 | func (me *packetConn) LocalAddr() net.Addr { | |
109 | return me.addr | |
110 | } | |
111 | ||
112 | type errTimeout struct{} | |
113 | ||
114 | func (errTimeout) Error() string { | |
115 | return "i/o timeout" | |
116 | } | |
117 | ||
118 | func (errTimeout) Temporary() bool { | |
119 | return false | |
120 | } | |
121 | ||
122 | func (errTimeout) Timeout() bool { | |
123 | return true | |
124 | } | |
125 | ||
126 | var _ net.Error = errTimeout{} | |
127 | ||
128 | func (me *packetConn) WriteTo(b []byte, na net.Addr) (n int, err error) { | |
129 | mu.Lock() | |
130 | defer mu.Unlock() | |
131 | if me.closed { | |
132 | err = errors.New("closed") | |
133 | return | |
134 | } | |
135 | if me.writeDeadline.exceeded() { | |
136 | err = errTimeout{} | |
137 | return | |
138 | } | |
139 | n = len(b) | |
140 | port := missinggo.AddrPort(na) | |
141 | c, ok := conns[port] | |
142 | if !ok { | |
143 | // log.Printf("no conn for port %d", port) | |
144 | return | |
145 | } | |
146 | c.reads = append(c.reads, packet{append([]byte(nil), b...), me.addr}) | |
147 | cond.Broadcast() | |
148 | return | |
149 | } | |
150 | ||
151 | func (me *packetConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { | |
152 | mu.Lock() | |
153 | defer mu.Unlock() | |
154 | for { | |
155 | if len(me.reads) != 0 { | |
156 | r := me.reads[0] | |
157 | me.reads = me.reads[1:] | |
158 | n = copy(b, r.data) | |
159 | addr = r.addr | |
160 | // log.Println(addr) | |
161 | return | |
162 | } | |
163 | if me.closed { | |
164 | err = io.EOF | |
165 | return | |
166 | } | |
167 | if me.readDeadline.exceeded() { | |
168 | err = errTimeout{} | |
169 | return | |
170 | } | |
171 | cond.Wait() | |
172 | } | |
173 | } | |
174 | ||
175 | func (me *packetConn) SetDeadline(t time.Time) error { | |
176 | me.writeDeadline.setDeadline(t) | |
177 | me.readDeadline.setDeadline(t) | |
178 | return nil | |
179 | } | |
180 | ||
181 | func (me *packetConn) SetReadDeadline(t time.Time) error { | |
182 | me.readDeadline.setDeadline(t) | |
183 | return nil | |
184 | } | |
185 | ||
186 | func (me *packetConn) SetWriteDeadline(t time.Time) error { | |
187 | me.writeDeadline.setDeadline(t) | |
188 | return nil | |
189 | } | |
190 | ||
191 | func newCondDeadline(cond *sync.Cond) (ret *condDeadline) { | |
192 | ret = &condDeadline{ | |
193 | timer: time.AfterFunc(math.MaxInt64, func() { | |
194 | mu.Lock() | |
195 | ret._exceeded = true | |
196 | mu.Unlock() | |
197 | cond.Broadcast() | |
198 | }), | |
199 | } | |
200 | ret.setDeadline(time.Time{}) | |
201 | return | |
202 | } | |
203 | ||
204 | type condDeadline struct { | |
205 | mu sync.Mutex | |
206 | _exceeded bool | |
207 | timer *time.Timer | |
208 | } | |
209 | ||
210 | func (me *condDeadline) setDeadline(t time.Time) { | |
211 | me.mu.Lock() | |
212 | defer me.mu.Unlock() | |
213 | me._exceeded = false | |
214 | if t.IsZero() { | |
215 | me.timer.Stop() | |
216 | return | |
217 | } | |
218 | me.timer.Reset(t.Sub(time.Now())) | |
219 | } | |
220 | ||
221 | func (me *condDeadline) exceeded() bool { | |
222 | me.mu.Lock() | |
223 | defer me.mu.Unlock() | |
224 | return me._exceeded | |
225 | } |
0 | package missinggo | |
1 | ||
2 | import "io" | |
3 | ||
4 | type StatWriter struct { | |
5 | Written int64 | |
6 | w io.Writer | |
7 | } | |
8 | ||
9 | func (me *StatWriter) Write(b []byte) (n int, err error) { | |
10 | n, err = me.w.Write(b) | |
11 | me.Written += int64(n) | |
12 | return | |
13 | } | |
14 | ||
15 | func NewStatWriter(w io.Writer) *StatWriter { | |
16 | return &StatWriter{w: w} | |
17 | } | |
18 | ||
19 | var ZeroReader zeroReader | |
20 | ||
21 | type zeroReader struct{} | |
22 | ||
23 | func (me zeroReader) Read(b []byte) (n int, err error) { | |
24 | for i := range b { | |
25 | b[i] = 0 | |
26 | } | |
27 | n = len(b) | |
28 | return | |
29 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net" | |
4 | "strconv" | |
5 | ) | |
6 | ||
7 | type IpPort struct { | |
8 | IP net.IP | |
9 | Port uint16 | |
10 | } | |
11 | ||
12 | func (me IpPort) String() string { | |
13 | return net.JoinHostPort(me.IP.String(), strconv.FormatUint(uint64(me.Port), 10)) | |
14 | } | |
15 | ||
16 | func IpPortFromNetAddr(na net.Addr) IpPort { | |
17 | return IpPort{AddrIP(na), uint16(AddrPort(na))} | |
18 | } |
0 | package iter | |
1 | ||
2 | func Chain(fs ...Func) Func { | |
3 | return func(cb Callback) { | |
4 | for _, f := range fs { | |
5 | if !All(cb, f) { | |
6 | break | |
7 | } | |
8 | } | |
9 | } | |
10 | } |
0 | package iter | |
1 | ||
2 | // Callback receives a value and returns true if another value should be | |
3 | // received or false to stop iteration. | |
4 | type Callback func(value interface{}) (more bool) | |
5 | ||
6 | // Func iterates by calling Callback for each of its values. | |
7 | type Func func(Callback) | |
8 | ||
9 | func All(cb Callback, fs ...Func) bool { | |
10 | for _, f := range fs { | |
11 | all := true | |
12 | f(func(v interface{}) bool { | |
13 | all = all && cb(v) | |
14 | return all | |
15 | }) | |
16 | if !all { | |
17 | return false | |
18 | } | |
19 | } | |
20 | return true | |
21 | } |
0 | package iter | |
1 | ||
2 | type groupBy struct { | |
3 | curKey interface{} | |
4 | curKeyOk bool | |
5 | curValue interface{} | |
6 | keyFunc func(interface{}) interface{} | |
7 | input Iterator | |
8 | groupKey interface{} | |
9 | groupKeyOk bool | |
10 | } | |
11 | ||
12 | type Group interface { | |
13 | Iterator | |
14 | Key() interface{} | |
15 | } | |
16 | ||
17 | type group struct { | |
18 | gb *groupBy | |
19 | key interface{} | |
20 | first bool | |
21 | stopped bool | |
22 | } | |
23 | ||
24 | func (me *group) Stop() { | |
25 | me.stopped = true | |
26 | } | |
27 | ||
28 | func (me *group) Next() (ok bool) { | |
29 | if me.stopped { | |
30 | return false | |
31 | } | |
32 | if me.first { | |
33 | me.first = false | |
34 | return true | |
35 | } | |
36 | me.gb.advance() | |
37 | if !me.gb.curKeyOk || me.gb.curKey != me.key { | |
38 | me.Stop() | |
39 | return | |
40 | } | |
41 | ok = true | |
42 | return | |
43 | } | |
44 | ||
45 | func (me group) Value() (ret interface{}) { | |
46 | if me.stopped { | |
47 | panic("iterator stopped") | |
48 | } | |
49 | ret = me.gb.curValue | |
50 | return | |
51 | } | |
52 | ||
53 | func (me group) Key() interface{} { | |
54 | return me.key | |
55 | } | |
56 | ||
57 | func (me *groupBy) advance() { | |
58 | me.curKeyOk = me.input.Next() | |
59 | if me.curKeyOk { | |
60 | me.curValue = me.input.Value() | |
61 | me.curKey = me.keyFunc(me.curValue) | |
62 | } | |
63 | } | |
64 | ||
65 | func (me *groupBy) Next() (ok bool) { | |
66 | for me.curKey == me.groupKey { | |
67 | ok = me.input.Next() | |
68 | if !ok { | |
69 | return | |
70 | } | |
71 | me.curValue = me.input.Value() | |
72 | me.curKey = me.keyFunc(me.curValue) | |
73 | me.curKeyOk = true | |
74 | } | |
75 | me.groupKey = me.curKey | |
76 | me.groupKeyOk = true | |
77 | return true | |
78 | } | |
79 | ||
80 | func (me *groupBy) Value() (ret interface{}) { | |
81 | return &group{me, me.groupKey, true, false} | |
82 | } | |
83 | ||
84 | func (me *groupBy) Stop() { | |
85 | } | |
86 | ||
87 | // Allows use of nil as a return from the key func. | |
88 | var uniqueKey = new(int) | |
89 | ||
90 | // Group by returns an iterator of iterators over the values of the input | |
91 | // iterator that consecutively return the same value when input to the key | |
92 | // function. Note that repeated calls to each value of the GroupBy Iterator | |
93 | // does not return a new iterator over the values for that key. | |
94 | func GroupBy(input Iterator, keyFunc func(interface{}) interface{}) Iterator { | |
95 | if keyFunc == nil { | |
96 | keyFunc = func(a interface{}) interface{} { return a } | |
97 | } | |
98 | return &groupBy{ | |
99 | input: input, | |
100 | keyFunc: keyFunc, | |
101 | groupKey: uniqueKey, | |
102 | curKey: uniqueKey, | |
103 | } | |
104 | } |
0 | package iter | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | "github.com/stretchr/testify/require" | |
7 | ||
8 | "github.com/anacrolix/missinggo/slices" | |
9 | ) | |
10 | ||
11 | func TestGroupByKey(t *testing.T) { | |
12 | var ks []byte | |
13 | gb := GroupBy(StringIterator("AAAABBBCCDAABBB"), nil) | |
14 | for gb.Next() { | |
15 | ks = append(ks, gb.Value().(Group).Key().(byte)) | |
16 | } | |
17 | t.Log(ks) | |
18 | require.EqualValues(t, "ABCDAB", ks) | |
19 | } | |
20 | ||
21 | func TestGroupByList(t *testing.T) { | |
22 | var gs []string | |
23 | gb := GroupBy(StringIterator("AAAABBBCCD"), nil) | |
24 | for gb.Next() { | |
25 | i := gb.Value().(Iterator) | |
26 | var g string | |
27 | for i.Next() { | |
28 | g += string(i.Value().(byte)) | |
29 | } | |
30 | gs = append(gs, g) | |
31 | } | |
32 | t.Log(gs) | |
33 | } | |
34 | ||
35 | func TestGroupByNiladicKey(t *testing.T) { | |
36 | const s = "AAAABBBCCD" | |
37 | gb := GroupBy(StringIterator(s), func(interface{}) interface{} { return nil }) | |
38 | gb.Next() | |
39 | var ss []byte | |
40 | g := ToSlice(ToFunc(gb.Value().(Iterator))) | |
41 | slices.MakeInto(&ss, g) | |
42 | assert.Equal(t, s, string(ss)) | |
43 | } | |
44 | ||
45 | func TestNilEqualsNil(t *testing.T) { | |
46 | assert.False(t, nil == uniqueKey) | |
47 | } |
0 | package iter | |
1 | ||
2 | func Head(n int, f Func) Func { | |
3 | return func(cb Callback) { | |
4 | if n <= 0 { | |
5 | return | |
6 | } | |
7 | f(func(v interface{}) bool { | |
8 | n-- | |
9 | if !cb(v) { | |
10 | return false | |
11 | } | |
12 | return n > 0 | |
13 | }) | |
14 | } | |
15 | } |
0 | package iter | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | ||
5 | "github.com/anacrolix/missinggo" | |
6 | ) | |
7 | ||
8 | type Iterable interface { | |
9 | Iter(Callback) | |
10 | } | |
11 | ||
12 | type iterator struct { | |
13 | it Iterable | |
14 | ch chan interface{} | |
15 | value interface{} | |
16 | ok bool | |
17 | mu sync.Mutex | |
18 | stopped missinggo.Event | |
19 | } | |
20 | ||
21 | func NewIterator(it Iterable) (ret *iterator) { | |
22 | ret = &iterator{ | |
23 | it: it, | |
24 | ch: make(chan interface{}), | |
25 | } | |
26 | go func() { | |
27 | // Have to do this in a goroutine, because the interface is synchronous. | |
28 | it.Iter(func(value interface{}) bool { | |
29 | select { | |
30 | case ret.ch <- value: | |
31 | return true | |
32 | case <-ret.stopped.LockedChan(&ret.mu): | |
33 | return false | |
34 | } | |
35 | }) | |
36 | close(ret.ch) | |
37 | ret.mu.Lock() | |
38 | ret.stopped.Set() | |
39 | ret.mu.Unlock() | |
40 | }() | |
41 | return | |
42 | } | |
43 | ||
44 | func (me *iterator) Value() interface{} { | |
45 | if !me.ok { | |
46 | panic("no value") | |
47 | } | |
48 | return me.value | |
49 | } | |
50 | ||
51 | func (me *iterator) Next() bool { | |
52 | me.value, me.ok = <-me.ch | |
53 | return me.ok | |
54 | } | |
55 | ||
56 | func (me *iterator) Stop() { | |
57 | me.mu.Lock() | |
58 | me.stopped.Set() | |
59 | me.mu.Unlock() | |
60 | } | |
61 | ||
62 | func IterableAsSlice(it Iterable) (ret []interface{}) { | |
63 | it.Iter(func(value interface{}) bool { | |
64 | ret = append(ret, value) | |
65 | return true | |
66 | }) | |
67 | return | |
68 | } |
0 | package iter | |
1 | ||
2 | import "github.com/anacrolix/missinggo/slices" | |
3 | ||
4 | type Iterator interface { | |
5 | // Advances to the next value. Returns false if there are no more values. | |
6 | // Must be called before the first value. | |
7 | Next() bool | |
8 | // Returns the current value. Should panic when the iterator is in an | |
9 | // invalid state. | |
10 | Value() interface{} | |
11 | // Ceases iteration prematurely. This should occur implicitly if Next | |
12 | // returns false. | |
13 | Stop() | |
14 | } | |
15 | ||
16 | func ToFunc(it Iterator) Func { | |
17 | return func(cb Callback) { | |
18 | defer it.Stop() | |
19 | for it.Next() { | |
20 | if !cb(it.Value()) { | |
21 | break | |
22 | } | |
23 | } | |
24 | } | |
25 | } | |
26 | ||
27 | type sliceIterator struct { | |
28 | slice []interface{} | |
29 | value interface{} | |
30 | ok bool | |
31 | } | |
32 | ||
33 | func (me *sliceIterator) Next() bool { | |
34 | if len(me.slice) == 0 { | |
35 | return false | |
36 | } | |
37 | me.value = me.slice[0] | |
38 | me.slice = me.slice[1:] | |
39 | me.ok = true | |
40 | return true | |
41 | } | |
42 | ||
43 | func (me *sliceIterator) Value() interface{} { | |
44 | if !me.ok { | |
45 | panic("no value; call Next") | |
46 | } | |
47 | return me.value | |
48 | } | |
49 | ||
50 | func (me *sliceIterator) Stop() {} | |
51 | ||
52 | func Slice(a []interface{}) Iterator { | |
53 | return &sliceIterator{ | |
54 | slice: a, | |
55 | } | |
56 | } | |
57 | ||
58 | func StringIterator(a string) Iterator { | |
59 | return Slice(slices.ToEmptyInterface(a)) | |
60 | } | |
61 | ||
62 | func ToSlice(f Func) (ret []interface{}) { | |
63 | f(func(v interface{}) bool { | |
64 | ret = append(ret, v) | |
65 | return true | |
66 | }) | |
67 | return | |
68 | } |
0 | package iter | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/require" | |
6 | ) | |
7 | ||
8 | func TestIterator(t *testing.T) { | |
9 | const s = "AAAABBBCCDAABBB" | |
10 | si := StringIterator(s) | |
11 | for i := range s { | |
12 | require.True(t, si.Next()) | |
13 | require.Equal(t, s[i], si.Value().(byte)) | |
14 | } | |
15 | require.False(t, si.Next()) | |
16 | } |
0 | package iter | |
1 | ||
2 | import "math/rand" | |
3 | ||
4 | type seq struct { | |
5 | i []int | |
6 | } | |
7 | ||
8 | // Creates sequence of values from [0, n) | |
9 | func newSeq(n int) seq { | |
10 | return seq{make([]int, n, n)} | |
11 | } | |
12 | ||
13 | func (me seq) Index(i int) (ret int) { | |
14 | ret = me.i[i] | |
15 | if ret == 0 { | |
16 | ret = i | |
17 | } | |
18 | return | |
19 | } | |
20 | ||
21 | func (me seq) Len() int { | |
22 | return len(me.i) | |
23 | } | |
24 | ||
25 | // Remove the nth value from the sequence. | |
26 | func (me *seq) DeleteIndex(index int) { | |
27 | me.i[index] = me.Index(me.Len() - 1) | |
28 | me.i = me.i[:me.Len()-1] | |
29 | } | |
30 | ||
31 | func ForPerm(n int, callback func(i int) (more bool)) bool { | |
32 | s := newSeq(n) | |
33 | for s.Len() > 0 { | |
34 | r := rand.Intn(s.Len()) | |
35 | if !callback(s.Index(r)) { | |
36 | return false | |
37 | } | |
38 | s.DeleteIndex(r) | |
39 | } | |
40 | return true | |
41 | } |
0 | package iter | |
1 | ||
2 | import "github.com/bradfitz/iter" | |
3 | ||
4 | func N(n int) []struct{} { | |
5 | return iter.N(n) | |
6 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "math/rand" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | // Returns random duration in the range [average-plusMinus, | |
8 | // average+plusMinus]. Negative plusMinus will likely panic. Be aware that if | |
9 | // plusMinus >= average, you may get a zero or negative Duration. The | |
10 | // distribution function is unspecified, in case I find a more appropriate one | |
11 | // in the future. | |
12 | func JitterDuration(average, plusMinus time.Duration) (ret time.Duration) { | |
13 | ret = average - plusMinus | |
14 | ret += time.Duration(rand.Int63n(2*int64(plusMinus) + 1)) | |
15 | return | |
16 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestJitterDuration(t *testing.T) { | |
9 | assert.Zero(t, JitterDuration(0, 0)) | |
10 | assert.Panics(t, func() { JitterDuration(1, -1) }) | |
11 | } |
0 | package leaktest | |
1 | ||
2 | import ( | |
3 | "runtime" | |
4 | "testing" | |
5 | "time" | |
6 | ||
7 | "github.com/bradfitz/iter" | |
8 | ) | |
9 | ||
10 | // Put defer GoroutineLeakCheck(t)() at the top of your test. Make sure the | |
11 | // goroutine count is steady before your test begins. | |
12 | func GoroutineLeakCheck(t testing.TB) func() { | |
13 | if !testing.Verbose() { | |
14 | return func() {} | |
15 | } | |
16 | numStart := runtime.NumGoroutine() | |
17 | return func() { | |
18 | var numNow int | |
19 | wait := time.Millisecond | |
20 | started := time.Now() | |
21 | for range iter.N(10) { // 1 second | |
22 | numNow = runtime.NumGoroutine() | |
23 | if numNow <= numStart { | |
24 | break | |
25 | } | |
26 | t.Logf("%d excess goroutines after %s", numNow-numStart, time.Since(started)) | |
27 | time.Sleep(wait) | |
28 | wait *= 2 | |
29 | } | |
30 | // I'd print stacks, or treat this as fatal, but I think | |
31 | // runtime.NumGoroutine is including system routines for which we are | |
32 | // not provided the stacks, and are spawned unpredictably. | |
33 | t.Logf("have %d goroutines, started with %d", numNow, numStart) | |
34 | // select {} | |
35 | } | |
36 | } |
0 | package missinggo | |
1 | ||
2 | // Sets an upper bound on the len of b. max can be any type that will cast to | |
3 | // int64. | |
4 | func LimitLen(b []byte, max ...interface{}) []byte { | |
5 | return b[:MinInt(len(b), max...)] | |
6 | } |
0 | package mime | |
1 | ||
2 | import "strings" | |
3 | ||
4 | type Type struct { | |
5 | Class string | |
6 | Specific string | |
7 | } | |
8 | ||
9 | func (t Type) String() string { | |
10 | return t.Class + "/" + t.Specific | |
11 | } | |
12 | ||
13 | func (t *Type) FromString(s string) { | |
14 | ss := strings.SplitN(s, "/", 1) | |
15 | t.Class = ss[0] | |
16 | t.Specific = ss[1] | |
17 | } |
0 | package missinggo | |
1 | ||
2 | import "reflect" | |
3 | ||
4 | func Max(_less interface{}, vals ...interface{}) interface{} { | |
5 | ret := reflect.ValueOf(vals[0]) | |
6 | retType := ret.Type() | |
7 | less := reflect.ValueOf(_less) | |
8 | for _, _v := range vals[1:] { | |
9 | v := reflect.ValueOf(_v).Convert(retType) | |
10 | out := less.Call([]reflect.Value{ret, v}) | |
11 | if out[0].Bool() { | |
12 | ret = v | |
13 | } | |
14 | } | |
15 | return ret.Interface() | |
16 | } | |
17 | ||
18 | func MaxInt(first int64, rest ...interface{}) int64 { | |
19 | return Max(func(l, r interface{}) bool { | |
20 | return l.(int64) < r.(int64) | |
21 | }, append([]interface{}{first}, rest...)...).(int64) | |
22 | } | |
23 | ||
24 | func MinInt(first interface{}, rest ...interface{}) int64 { | |
25 | ret := reflect.ValueOf(first).Int() | |
26 | for _, _i := range rest { | |
27 | i := reflect.ValueOf(_i).Int() | |
28 | if i < ret { | |
29 | ret = i | |
30 | } | |
31 | } | |
32 | return ret | |
33 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | // Monotonic time represents time since an arbitrary point in the past, where | |
8 | // the concept of now is only ever moving in a positive direction. | |
9 | type MonotonicTime struct { | |
10 | skewedStdTime time.Time | |
11 | } | |
12 | ||
13 | func (me MonotonicTime) Sub(other MonotonicTime) time.Duration { | |
14 | return me.skewedStdTime.Sub(other.skewedStdTime) | |
15 | } | |
16 | ||
17 | var ( | |
18 | stdNowFunc = time.Now | |
19 | monotonicMu sync.Mutex | |
20 | lastStdNow time.Time | |
21 | monotonicSkew time.Duration | |
22 | ) | |
23 | ||
24 | func skewedStdNow() time.Time { | |
25 | monotonicMu.Lock() | |
26 | defer monotonicMu.Unlock() | |
27 | stdNow := stdNowFunc() | |
28 | if !lastStdNow.IsZero() && stdNow.Before(lastStdNow) { | |
29 | monotonicSkew += lastStdNow.Sub(stdNow) | |
30 | } | |
31 | lastStdNow = stdNow | |
32 | return stdNow.Add(monotonicSkew) | |
33 | } | |
34 | ||
35 | // Consecutive calls always produce the same or greater time than previous | |
36 | // calls. | |
37 | func MonotonicNow() MonotonicTime { | |
38 | return MonotonicTime{skewedStdNow()} | |
39 | } | |
40 | ||
41 | func MonotonicSince(since MonotonicTime) (ret time.Duration) { | |
42 | return skewedStdNow().Sub(since.skewedStdTime) | |
43 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | // Calls suite with the used time.Now function used by MonotonicNow replaced | |
10 | // with stdNow for the duration of the call. | |
11 | func withCustomStdNow(stdNow func() time.Time, suite func()) { | |
12 | oldStdNow := stdNowFunc | |
13 | oldSkew := monotonicSkew | |
14 | defer func() { | |
15 | stdNowFunc = oldStdNow | |
16 | monotonicSkew = oldSkew | |
17 | }() | |
18 | stdNowFunc = stdNow | |
19 | suite() | |
20 | } | |
21 | ||
22 | // Returns a time.Now-like function that walks seq returning time.Unix(0, | |
23 | // seq[i]) in successive calls. | |
24 | func stdNowSeqFunc(seq []int64) func() time.Time { | |
25 | var i int | |
26 | return func() time.Time { | |
27 | defer func() { i++ }() | |
28 | return time.Unix(0, seq[i]) | |
29 | } | |
30 | } | |
31 | ||
32 | func TestMonotonicTime(t *testing.T) { | |
33 | started := MonotonicNow() | |
34 | withCustomStdNow(stdNowSeqFunc([]int64{2, 1, 3, 3, 2, 3}), func() { | |
35 | i0 := MonotonicNow() // 0 | |
36 | i1 := MonotonicNow() // 1 | |
37 | assert.EqualValues(t, 0, i0.Sub(i1)) | |
38 | assert.EqualValues(t, 2, MonotonicSince(i0)) // 2 | |
39 | assert.EqualValues(t, 2, MonotonicSince(i1)) // 3 | |
40 | i4 := MonotonicNow() | |
41 | assert.EqualValues(t, 2, i4.Sub(i0)) | |
42 | assert.EqualValues(t, 2, i4.Sub(i1)) | |
43 | i5 := MonotonicNow() | |
44 | assert.EqualValues(t, 3, i5.Sub(i0)) | |
45 | assert.EqualValues(t, 3, i5.Sub(i1)) | |
46 | assert.EqualValues(t, 1, i5.Sub(i4)) | |
47 | }) | |
48 | // Ensure that skew and time function are restored correctly and within | |
49 | // reasonable bounds. | |
50 | assert.True(t, MonotonicSince(started) >= 0 && MonotonicSince(started) < time.Second) | |
51 | } |
0 | package missinggo | |
1 | ||
2 | type ( | |
3 | SameLessFunc func() (same, less bool) | |
4 | MultiLess struct { | |
5 | ok bool | |
6 | less bool | |
7 | } | |
8 | ) | |
9 | ||
10 | func (me *MultiLess) Less() bool { | |
11 | return me.ok && me.less | |
12 | } | |
13 | ||
14 | func (me *MultiLess) Final() bool { | |
15 | if !me.ok { | |
16 | panic("undetermined") | |
17 | } | |
18 | return me.less | |
19 | } | |
20 | ||
21 | func (me *MultiLess) FinalOk() (left, ok bool) { | |
22 | return me.less, me.ok | |
23 | } | |
24 | ||
25 | func (me *MultiLess) Next(f SameLessFunc) { | |
26 | if me.ok { | |
27 | return | |
28 | } | |
29 | same, less := f() | |
30 | if same { | |
31 | return | |
32 | } | |
33 | me.ok = true | |
34 | me.less = less | |
35 | } | |
36 | ||
37 | func (me *MultiLess) StrictNext(same, less bool) { | |
38 | if me.ok { | |
39 | return | |
40 | } | |
41 | me.Next(func() (bool, bool) { return same, less }) | |
42 | } | |
43 | ||
44 | func (me *MultiLess) NextBool(l, r bool) { | |
45 | me.StrictNext(l == r, l) | |
46 | } |
0 | package missinggo | |
1 | ||
2 | import "strings" | |
3 | ||
4 | func IsAddrInUse(err error) bool { | |
5 | return strings.Contains(err.Error(), "address already in use") | |
6 | } |
0 | package oauth | |
1 | ||
2 | type Endpoint struct { | |
3 | AuthURL string | |
4 | TokenURL string | |
5 | ProfileURL string | |
6 | } | |
7 | ||
8 | var ( | |
9 | FacebookEndpoint = Endpoint{ | |
10 | AuthURL: "https://www.facebook.com/dialog/oauth", | |
11 | TokenURL: "https://graph.facebook.com/v2.3/oauth/access_token", | |
12 | ProfileURL: "https://graph.facebook.com/me", | |
13 | } | |
14 | GoogleEndpoint = Endpoint{ | |
15 | AuthURL: "https://accounts.google.com/o/oauth2/auth", | |
16 | TokenURL: "https://accounts.google.com/o/oauth2/token", | |
17 | ProfileURL: "https://www.googleapis.com/oauth2/v2/userinfo", | |
18 | } | |
19 | PatreonEndpoint = Endpoint{ | |
20 | AuthURL: "https://www.patreon.com/oauth2/authorize", | |
21 | TokenURL: "https://api.patreon.com/oauth2/token", | |
22 | ProfileURL: "https://api.patreon.com/oauth2/api/current_user", | |
23 | } | |
24 | ) |
0 | package oauth | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "io" | |
7 | "net/http" | |
8 | "net/url" | |
9 | ||
10 | "github.com/anacrolix/missinggo/patreon" | |
11 | ) | |
12 | ||
13 | func SimpleParser(r *http.Response) (UserProfile, error) { | |
14 | var sup simpleUserProfile | |
15 | err := json.NewDecoder(r.Body).Decode(&sup) | |
16 | return sup, err | |
17 | } | |
18 | ||
19 | type Provider struct { | |
20 | Client *Client | |
21 | Endpoint *Endpoint | |
22 | } | |
23 | ||
24 | type Wrapper struct { | |
25 | Scope string | |
26 | Provider Provider | |
27 | ProfileParser func(*http.Response) (UserProfile, error) | |
28 | } | |
29 | ||
30 | func (me Wrapper) GetAuthURL(redirectURI, state string) string { | |
31 | return me.Provider.GetAuthURL(redirectURI, state, me.Scope) | |
32 | } | |
33 | ||
34 | func (me Wrapper) FetchUser(accessToken string) (up UserProfile, err error) { | |
35 | resp, err := me.Provider.FetchUser(accessToken) | |
36 | if err != nil { | |
37 | return | |
38 | } | |
39 | defer resp.Body.Close() | |
40 | return me.ProfileParser(resp) | |
41 | } | |
42 | ||
43 | type Client struct { | |
44 | ID string | |
45 | Secret string | |
46 | } | |
47 | ||
48 | func (me *Provider) GetAuthURL(redirectURI, state, scope string) string { | |
49 | params := []string{ | |
50 | "client_id", me.Client.ID, | |
51 | "response_type", "code", | |
52 | "redirect_uri", redirectURI, | |
53 | "state", state, | |
54 | // This will ask again for the given scopes if they're not provided. | |
55 | "auth_type", "rerequest", | |
56 | } | |
57 | if scope != "" { | |
58 | params = append(params, "scope", scope) | |
59 | } | |
60 | return renderEndpointURL(me.Endpoint.AuthURL, params...) | |
61 | } | |
62 | ||
63 | func (me *Provider) ExchangeCode(code string, redirectURI string) (accessToken string, err error) { | |
64 | v := url.Values{ | |
65 | "client_id": {me.Client.ID}, | |
66 | "redirect_uri": {redirectURI}, | |
67 | "client_secret": {me.Client.Secret}, | |
68 | "code": {code}, | |
69 | "grant_type": {"authorization_code"}, | |
70 | } | |
71 | resp, err := http.Post(me.Endpoint.TokenURL, "application/x-www-form-urlencoded", bytes.NewBufferString(v.Encode())) | |
72 | if err != nil { | |
73 | return | |
74 | } | |
75 | var buf bytes.Buffer | |
76 | io.Copy(&buf, resp.Body) | |
77 | resp.Body.Close() | |
78 | var msg map[string]interface{} | |
79 | err = json.NewDecoder(&buf).Decode(&msg) | |
80 | if err != nil { | |
81 | return | |
82 | } | |
83 | defer func() { | |
84 | r := recover() | |
85 | if r == nil { | |
86 | return | |
87 | } | |
88 | err = fmt.Errorf("bad access_token field in %q: %s", msg, r) | |
89 | }() | |
90 | accessToken = msg["access_token"].(string) | |
91 | return | |
92 | } | |
93 | ||
94 | type simpleUserProfile struct { | |
95 | Id string `json:"id"` | |
96 | EmailField string `json:"email"` | |
97 | } | |
98 | ||
99 | var _ UserProfile = simpleUserProfile{} | |
100 | ||
101 | func (me simpleUserProfile) IsEmailVerified() bool { | |
102 | return true | |
103 | } | |
104 | ||
105 | func (me simpleUserProfile) Email() string { | |
106 | return me.EmailField | |
107 | } | |
108 | ||
109 | type UserProfile interface { | |
110 | IsEmailVerified() bool | |
111 | Email() string | |
112 | } | |
113 | ||
114 | // TODO: Allow fields to be specified. | |
115 | func (me *Provider) FetchUser(accessToken string) (*http.Response, error) { | |
116 | return http.Get(renderEndpointURL( | |
117 | me.Endpoint.ProfileURL, | |
118 | "fields", "email", | |
119 | "access_token", accessToken, | |
120 | )) | |
121 | } | |
122 | ||
123 | type PatreonUserProfile struct { | |
124 | Data patreon.ApiUser `json:"data"` | |
125 | } | |
126 | ||
127 | var _ UserProfile = PatreonUserProfile{} | |
128 | ||
129 | func (me PatreonUserProfile) IsEmailVerified() bool { | |
130 | return me.Data.Attributes.IsEmailVerified | |
131 | } | |
132 | ||
133 | func (me PatreonUserProfile) Email() string { | |
134 | return me.Data.Attributes.Email | |
135 | } | |
136 | ||
137 | func renderEndpointURL(endpoint string, params ...string) string { | |
138 | u, err := url.Parse(endpoint) | |
139 | if err != nil { | |
140 | panic(err) | |
141 | } | |
142 | v := make(url.Values, len(params)/2) | |
143 | for i := 0; i < len(params); i += 2 { | |
144 | v.Set(params[i], params[i+1]) | |
145 | } | |
146 | u.RawQuery = v.Encode() | |
147 | return u.String() | |
148 | } |
0 | package oauth | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | "github.com/stretchr/testify/require" | |
8 | ) | |
9 | ||
10 | func TestDecodePatreonUserProfile(t *testing.T) { | |
11 | var pup PatreonUserProfile | |
12 | err := json.Unmarshal([]byte( | |
13 | `{ | |
14 | "data": { | |
15 | "attributes": { | |
16 | "about": null, | |
17 | "created": "2017-05-12T12:49:31+00:00", | |
18 | "discord_id": null, | |
19 | "email": "anacrolix@gmail.com", | |
20 | "facebook": null, | |
21 | "facebook_id": "10155425587018447", | |
22 | "first_name": "Matt", | |
23 | "full_name": "Matt Joiner", | |
24 | "gender": 0, | |
25 | "has_password": false, | |
26 | "image_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?t=2145916800&w=400&v=506YL5JlU7aaQH-QyEaRXyWoXFs4ia-vcSjjZuv-dXY%3D", | |
27 | "is_deleted": false, | |
28 | "is_email_verified": true, | |
29 | "is_nuked": false, | |
30 | "is_suspended": false, | |
31 | "last_name": "Joiner", | |
32 | "social_connections": { | |
33 | "deviantart": null, | |
34 | "discord": null, | |
35 | "facebook": null, | |
36 | "spotify": null, | |
37 | "twitch": null, | |
38 | "twitter": null, | |
39 | "youtube": null | |
40 | }, | |
41 | "thumb_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?h=100&t=2145916800&w=100&v=SI72bzI4XB5mX0dyfqeZ-Nn4BNTz9FYRSgZ8pLipARg%3D", | |
42 | "twitch": null, | |
43 | "twitter": null, | |
44 | "url": "https://www.patreon.com/anacrolix", | |
45 | "vanity": "anacrolix", | |
46 | "youtube": null | |
47 | }, | |
48 | "id": "6126463", | |
49 | "relationships": { | |
50 | "pledges": { | |
51 | "data": [] | |
52 | } | |
53 | }, | |
54 | "type": "user" | |
55 | }, | |
56 | "links": { | |
57 | "self": "https://api.patreon.com/user/6126463" | |
58 | } | |
59 | }`), &pup) | |
60 | require.NoError(t, err) | |
61 | assert.EqualValues(t, "anacrolix@gmail.com", pup.Data.Attributes.Email) | |
62 | assert.True(t, pup.Data.Attributes.IsEmailVerified) | |
63 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | ) | |
5 | ||
6 | const O_ACCMODE = os.O_RDONLY | os.O_WRONLY | os.O_RDWR |
0 | package orderedmap | |
1 | ||
2 | import ( | |
3 | "github.com/anacrolix/missinggo/iter" | |
4 | "github.com/google/btree" | |
5 | ) | |
6 | ||
7 | type GoogleBTree struct { | |
8 | bt *btree.BTree | |
9 | lesser func(l, r interface{}) bool | |
10 | } | |
11 | ||
12 | type googleBTreeItem struct { | |
13 | less func(l, r interface{}) bool | |
14 | key interface{} | |
15 | value interface{} | |
16 | } | |
17 | ||
18 | func (me googleBTreeItem) Less(right btree.Item) bool { | |
19 | return me.less(me.key, right.(*googleBTreeItem).key) | |
20 | } | |
21 | ||
22 | func NewGoogleBTree(lesser func(l, r interface{}) bool) *GoogleBTree { | |
23 | return &GoogleBTree{ | |
24 | bt: btree.New(32), | |
25 | lesser: lesser, | |
26 | } | |
27 | } | |
28 | ||
29 | func (me *GoogleBTree) Set(key interface{}, value interface{}) { | |
30 | me.bt.ReplaceOrInsert(&googleBTreeItem{me.lesser, key, value}) | |
31 | } | |
32 | ||
33 | func (me *GoogleBTree) Get(key interface{}) interface{} { | |
34 | ret, _ := me.GetOk(key) | |
35 | return ret | |
36 | } | |
37 | ||
38 | func (me *GoogleBTree) GetOk(key interface{}) (interface{}, bool) { | |
39 | item := me.bt.Get(&googleBTreeItem{me.lesser, key, nil}) | |
40 | if item == nil { | |
41 | return nil, false | |
42 | } | |
43 | return item.(*googleBTreeItem).value, true | |
44 | } | |
45 | ||
46 | type googleBTreeIter struct { | |
47 | i btree.Item | |
48 | bt *btree.BTree | |
49 | } | |
50 | ||
51 | func (me *googleBTreeIter) Next() bool { | |
52 | if me.bt == nil { | |
53 | return false | |
54 | } | |
55 | if me.i == nil { | |
56 | me.bt.Ascend(func(i btree.Item) bool { | |
57 | me.i = i | |
58 | return false | |
59 | }) | |
60 | } else { | |
61 | var n int | |
62 | me.bt.AscendGreaterOrEqual(me.i, func(i btree.Item) bool { | |
63 | n++ | |
64 | if n == 1 { | |
65 | return true | |
66 | } | |
67 | me.i = i | |
68 | return false | |
69 | }) | |
70 | if n != 2 { | |
71 | me.i = nil | |
72 | } | |
73 | } | |
74 | return me.i != nil | |
75 | } | |
76 | ||
77 | func (me *googleBTreeIter) Value() interface{} { | |
78 | return me.i.(*googleBTreeItem).value | |
79 | } | |
80 | ||
81 | func (me *googleBTreeIter) Stop() { | |
82 | me.bt = nil | |
83 | me.i = nil | |
84 | } | |
85 | ||
86 | func (me *GoogleBTree) Iter(f iter.Callback) { | |
87 | me.bt.Ascend(func(i btree.Item) bool { | |
88 | return f(i.(*googleBTreeItem).key) | |
89 | }) | |
90 | } | |
91 | ||
92 | func (me *GoogleBTree) Unset(key interface{}) { | |
93 | me.bt.Delete(&googleBTreeItem{me.lesser, key, nil}) | |
94 | } | |
95 | ||
96 | func (me *GoogleBTree) Len() int { | |
97 | return me.bt.Len() | |
98 | } |
0 | package orderedmap | |
1 | ||
2 | import "github.com/anacrolix/missinggo/iter" | |
3 | ||
4 | func New(lesser func(l, r interface{}) bool) OrderedMap { | |
5 | return NewGoogleBTree(lesser) | |
6 | } | |
7 | ||
8 | type OrderedMap interface { | |
9 | Get(key interface{}) interface{} | |
10 | GetOk(key interface{}) (interface{}, bool) | |
11 | iter.Iterable | |
12 | Set(key, value interface{}) | |
13 | Unset(key interface{}) | |
14 | Len() int | |
15 | } |
0 | package orderedmap | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/anacrolix/missinggo/iter" | |
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func slice(om OrderedMap) (ret []interface{}) { | |
10 | om.Iter(func(i interface{}) bool { | |
11 | ret = append(ret, om.Get(i)) | |
12 | return true | |
13 | }) | |
14 | return | |
15 | } | |
16 | ||
17 | func TestSimple(t *testing.T) { | |
18 | om := New(func(l, r interface{}) bool { | |
19 | return l.(int) < r.(int) | |
20 | }) | |
21 | om.Set(3, 1) | |
22 | om.Set(2, 2) | |
23 | om.Set(1, 3) | |
24 | assert.EqualValues(t, []interface{}{3, 2, 1}, slice(om)) | |
25 | om.Set(3, 2) | |
26 | om.Unset(2) | |
27 | assert.EqualValues(t, []interface{}{3, 2}, slice(om)) | |
28 | om.Set(-1, 4) | |
29 | assert.EqualValues(t, []interface{}{4, 3, 2}, slice(om)) | |
30 | } | |
31 | ||
32 | func TestIterEmpty(t *testing.T) { | |
33 | om := New(nil) | |
34 | it := iter.NewIterator(om) | |
35 | assert.Panics(t, func() { it.Value() }) | |
36 | assert.False(t, it.Next()) | |
37 | it.Stop() | |
38 | } |
0 | package orderedmap | |
1 | ||
2 | import "github.com/ryszard/goskiplist/skiplist" | |
3 | ||
4 | type skiplistOrderedMap struct { | |
5 | sl *skiplist.SkipList | |
6 | } | |
7 | ||
8 | func NewSkipList(lesser func(l, r interface{}) bool) *skiplistOrderedMap { | |
9 | return &skiplistOrderedMap{skiplist.NewCustomMap(lesser)} | |
10 | } | |
11 | ||
12 | func (me *skiplistOrderedMap) Set(key interface{}, value interface{}) { | |
13 | me.sl.Set(key, value) | |
14 | } | |
15 | ||
16 | func (me *skiplistOrderedMap) Get(key interface{}) interface{} { | |
17 | if me == nil { | |
18 | return nil | |
19 | } | |
20 | ret, _ := me.sl.Get(key) | |
21 | return ret | |
22 | } | |
23 | ||
24 | func (me *skiplistOrderedMap) GetOk(key interface{}) (interface{}, bool) { | |
25 | if me == nil { | |
26 | return nil, false | |
27 | } | |
28 | return me.sl.Get(key) | |
29 | } | |
30 | ||
31 | type Iter struct { | |
32 | it skiplist.Iterator | |
33 | } | |
34 | ||
35 | func (me *Iter) Next() bool { | |
36 | if me == nil { | |
37 | return false | |
38 | } | |
39 | return me.it.Next() | |
40 | } | |
41 | ||
42 | func (me *Iter) Value() interface{} { | |
43 | return me.it.Value() | |
44 | } | |
45 | ||
46 | func (me *skiplistOrderedMap) Iter() *Iter { | |
47 | if me == nil { | |
48 | return nil | |
49 | } | |
50 | return &Iter{me.sl.Iterator()} | |
51 | } | |
52 | ||
53 | func (me *skiplistOrderedMap) Unset(key interface{}) { | |
54 | if me == nil { | |
55 | return | |
56 | } | |
57 | me.sl.Delete(key) | |
58 | } | |
59 | ||
60 | func (me *skiplistOrderedMap) Len() int { | |
61 | if me.sl == nil { | |
62 | return 0 | |
63 | } | |
64 | return me.sl.Len() | |
65 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "path" | |
5 | ) | |
6 | ||
7 | // Splits the pathname p into Root and Ext, such that Root+Ext==p. | |
8 | func PathSplitExt(p string) (ret struct { | |
9 | Root, Ext string | |
10 | }) { | |
11 | ret.Ext = path.Ext(p) | |
12 | ret.Root = p[:len(p)-len(ret.Ext)] | |
13 | return | |
14 | } | |
15 | ||
16 | func FilePathExists(p string) bool { | |
17 | _, err := os.Stat(p) | |
18 | return err == nil | |
19 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ) | |
5 | ||
6 | func ExamplePathSplitExt() { | |
7 | fmt.Printf("%q\n", PathSplitExt(".cshrc")) | |
8 | fmt.Printf("%q\n", PathSplitExt("dir/a.ext")) | |
9 | fmt.Printf("%q\n", PathSplitExt("dir/.rc")) | |
10 | fmt.Printf("%q\n", PathSplitExt("home/.secret/file")) | |
11 | // Output: | |
12 | // {"" ".cshrc"} | |
13 | // {"dir/a" ".ext"} | |
14 | // {"dir/" ".rc"} | |
15 | // {"home/.secret/file" ""} | |
16 | } |
0 | package patreon | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "fmt" | |
5 | "io" | |
6 | "net/http" | |
7 | ) | |
8 | ||
9 | type PledgesApiResponse struct { | |
10 | Pledges []struct { | |
11 | Attributes struct { | |
12 | AmountCents int `json:"amount_cents"` | |
13 | } `json:"attributes"` | |
14 | Relationships struct { | |
15 | Patron struct { | |
16 | Data struct { | |
17 | Id Id `json:"id"` | |
18 | } `json:"data"` | |
19 | } `json:"patron"` | |
20 | } `json:"relationships"` | |
21 | } `json:"data"` | |
22 | Included []ApiUser `json:"included"` | |
23 | } | |
24 | ||
25 | type ApiUser struct { | |
26 | Attributes struct { | |
27 | Email string `json:"email"` | |
28 | IsEmailVerified bool `json:"is_email_verified"` | |
29 | } `json:"attributes"` | |
30 | Id Id `json:"id"` | |
31 | } | |
32 | ||
33 | type Pledge struct { | |
34 | Email string | |
35 | EmailVerified bool | |
36 | AmountCents int | |
37 | } | |
38 | ||
39 | type Id string | |
40 | ||
41 | func makeUserMap(par *PledgesApiResponse) (ret map[Id]*ApiUser) { | |
42 | ret = make(map[Id]*ApiUser, len(par.Included)) | |
43 | for i := range par.Included { | |
44 | au := &par.Included[i] | |
45 | ret[au.Id] = au | |
46 | } | |
47 | return | |
48 | } | |
49 | ||
50 | func ParsePledgesApiResponse(r io.Reader) (ps []Pledge, err error) { | |
51 | var ar PledgesApiResponse | |
52 | err = json.NewDecoder(r).Decode(&ar) | |
53 | if err != nil { | |
54 | return | |
55 | } | |
56 | userMap := makeUserMap(&ar) | |
57 | for _, p := range ar.Pledges { | |
58 | u := userMap[p.Relationships.Patron.Data.Id] | |
59 | ps = append(ps, Pledge{ | |
60 | Email: u.Attributes.Email, | |
61 | EmailVerified: u.Attributes.IsEmailVerified, | |
62 | AmountCents: p.Attributes.AmountCents, | |
63 | }) | |
64 | } | |
65 | return | |
66 | } | |
67 | ||
68 | func GetCampaignPledges(campaign Id, userAccessToken string) (ret []Pledge, err error) { | |
69 | req, err := http.NewRequest("GET", fmt.Sprintf("https://api.patreon.com/oauth2/api/campaigns/%s/pledges", campaign), nil) | |
70 | if err != nil { | |
71 | return | |
72 | } | |
73 | req.Header.Set("Authorization", "Bearer "+userAccessToken) | |
74 | resp, err := http.DefaultClient.Do(req) | |
75 | if err != nil { | |
76 | return | |
77 | } | |
78 | defer resp.Body.Close() | |
79 | if resp.StatusCode != 200 { | |
80 | err = fmt.Errorf("got http response code %d", resp.StatusCode) | |
81 | return | |
82 | } | |
83 | return ParsePledgesApiResponse(resp.Body) | |
84 | } |
0 | package patreon | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | "github.com/stretchr/testify/require" | |
8 | ) | |
9 | ||
10 | func TestParsePledges(t *testing.T) { | |
11 | f, err := os.Open("testdata/pledges") | |
12 | require.NoError(t, err) | |
13 | defer f.Close() | |
14 | ps, err := ParsePledgesApiResponse(f) | |
15 | require.NoError(t, err) | |
16 | assert.EqualValues(t, []Pledge{{ | |
17 | Email: "yonhyaro@gmail.com", | |
18 | EmailVerified: true, | |
19 | AmountCents: 200, | |
20 | }}, ps) | |
21 | } |
0 | { | |
1 | "data": [ | |
2 | { | |
3 | "attributes": { | |
4 | "amount_cents": 200, | |
5 | "created_at": "2017-06-25T06:31:08.327895+00:00", | |
6 | "declined_since": null, | |
7 | "patron_pays_fees": false, | |
8 | "pledge_cap_cents": 200 | |
9 | }, | |
10 | "id": "6481585", | |
11 | "relationships": { | |
12 | "address": { | |
13 | "data": null | |
14 | }, | |
15 | "creator": { | |
16 | "data": { | |
17 | "id": "6126463", | |
18 | "type": "user" | |
19 | }, | |
20 | "links": { | |
21 | "related": "https://api.patreon.com/user/6126463" | |
22 | } | |
23 | }, | |
24 | "patron": { | |
25 | "data": { | |
26 | "id": "6649264", | |
27 | "type": "user" | |
28 | }, | |
29 | "links": { | |
30 | "related": "https://api.patreon.com/user/6649264" | |
31 | } | |
32 | }, | |
33 | "reward": { | |
34 | "data": { | |
35 | "id": "1683378", | |
36 | "type": "reward" | |
37 | }, | |
38 | "links": { | |
39 | "related": "https://api.patreon.com/rewards/1683378" | |
40 | } | |
41 | } | |
42 | }, | |
43 | "type": "pledge" | |
44 | } | |
45 | ], | |
46 | "included": [ | |
47 | { | |
48 | "attributes": { | |
49 | "about": null, | |
50 | "created": "2017-06-25T06:27:33+00:00", | |
51 | "email": "yonhyaro@gmail.com", | |
52 | "facebook": null, | |
53 | "first_name": "Ben", | |
54 | "full_name": "Ben Idris", | |
55 | "gender": 0, | |
56 | "image_url": "https://c3.patreon.com/2/patreon-user/oTXlvlk326g1M0aDepVz1WMmE4Tq6eGxlQHmDIeuYA5MAuPUz3oDar8XiAYsJTsF.jpeg?t=2145916800&w=400&v=oERJD4kyEAN7aSeOUX8Ki0p1iyVIbKcJ3pudh7QnZK0%3D", | |
57 | "is_email_verified": true, | |
58 | "last_name": "Idris", | |
59 | "social_connections": { | |
60 | "deviantart": null, | |
61 | "discord": null, | |
62 | "facebook": null, | |
63 | "spotify": null, | |
64 | "twitch": null, | |
65 | "twitter": null, | |
66 | "youtube": null | |
67 | }, | |
68 | "thumb_url": "https://c3.patreon.com/2/patreon-user/oTXlvlk326g1M0aDepVz1WMmE4Tq6eGxlQHmDIeuYA5MAuPUz3oDar8XiAYsJTsF.jpeg?h=100&t=2145916800&w=100&v=jXL0mDWCvzTCWk544GNyJ7IgoTIJR2gGAuLnJKcTnAI%3D", | |
69 | "twitch": null, | |
70 | "twitter": null, | |
71 | "url": "https://www.patreon.com/user?u=6649264", | |
72 | "vanity": null, | |
73 | "youtube": null | |
74 | }, | |
75 | "id": "6649264", | |
76 | "relationships": { | |
77 | "campaign": { | |
78 | "data": null | |
79 | } | |
80 | }, | |
81 | "type": "user" | |
82 | }, | |
83 | { | |
84 | "attributes": { | |
85 | "amount": 200, | |
86 | "amount_cents": 200, | |
87 | "created_at": "2017-05-12T13:31:36.623424+00:00", | |
88 | "deleted_at": null, | |
89 | "description": "<ul><li>Elevated quota on test servers</li></ul>", | |
90 | "discord_role_ids": null, | |
91 | "edited_at": "2017-06-13T13:09:39.303442+00:00", | |
92 | "image_url": null, | |
93 | "patron_count": 1, | |
94 | "post_count": null, | |
95 | "published": true, | |
96 | "published_at": "2017-05-12T13:31:36.623424+00:00", | |
97 | "remaining": 19, | |
98 | "requires_shipping": false, | |
99 | "title": "Fan", | |
100 | "unpublished_at": null, | |
101 | "url": "/bePatron?c=925561&rid=1683378", | |
102 | "user_limit": 20 | |
103 | }, | |
104 | "id": "1683378", | |
105 | "relationships": { | |
106 | "campaign": { | |
107 | "data": { | |
108 | "id": "925561", | |
109 | "type": "campaign" | |
110 | }, | |
111 | "links": { | |
112 | "related": "https://api.patreon.com/campaigns/925561" | |
113 | } | |
114 | }, | |
115 | "creator": { | |
116 | "data": { | |
117 | "id": "6126463", | |
118 | "type": "user" | |
119 | }, | |
120 | "links": { | |
121 | "related": "https://api.patreon.com/user/6126463" | |
122 | } | |
123 | } | |
124 | }, | |
125 | "type": "reward" | |
126 | }, | |
127 | { | |
128 | "attributes": { | |
129 | "about": "", | |
130 | "created": "2017-05-12T12:49:31+00:00", | |
131 | "discord_id": null, | |
132 | "email": "anacrolix@gmail.com", | |
133 | "facebook": null, | |
134 | "facebook_id": "10155425587018447", | |
135 | "first_name": "Matt", | |
136 | "full_name": "Matt Joiner", | |
137 | "gender": 0, | |
138 | "has_password": false, | |
139 | "image_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?t=2145916800&w=400&v=506YL5JlU7aaQH-QyEaRXyWoXFs4ia-vcSjjZuv-dXY%3D", | |
140 | "is_deleted": false, | |
141 | "is_email_verified": true, | |
142 | "is_nuked": false, | |
143 | "is_suspended": false, | |
144 | "last_name": "Joiner", | |
145 | "social_connections": { | |
146 | "deviantart": null, | |
147 | "discord": null, | |
148 | "facebook": null, | |
149 | "spotify": null, | |
150 | "twitch": null, | |
151 | "twitter": null, | |
152 | "youtube": null | |
153 | }, | |
154 | "thumb_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?h=100&t=2145916800&w=100&v=SI72bzI4XB5mX0dyfqeZ-Nn4BNTz9FYRSgZ8pLipARg%3D", | |
155 | "twitch": null, | |
156 | "twitter": null, | |
157 | "url": "https://www.patreon.com/anacrolix", | |
158 | "vanity": "anacrolix", | |
159 | "youtube": null | |
160 | }, | |
161 | "id": "6126463", | |
162 | "relationships": { | |
163 | "campaign": { | |
164 | "data": { | |
165 | "id": "925561", | |
166 | "type": "campaign" | |
167 | }, | |
168 | "links": { | |
169 | "related": "https://api.patreon.com/campaigns/925561" | |
170 | } | |
171 | } | |
172 | }, | |
173 | "type": "user" | |
174 | }, | |
175 | { | |
176 | "attributes": { | |
177 | "created_at": "2017-05-12T12:49:39+00:00", | |
178 | "creation_count": 1, | |
179 | "creation_name": "useful websites and technologies", | |
180 | "discord_server_id": null, | |
181 | "display_patron_goals": false, | |
182 | "earnings_visibility": "private", | |
183 | "image_small_url": null, | |
184 | "image_url": null, | |
185 | "is_charged_immediately": false, | |
186 | "is_monthly": true, | |
187 | "is_nsfw": true, | |
188 | "is_plural": false, | |
189 | "main_video_embed": null, | |
190 | "main_video_url": null, | |
191 | "one_liner": null, | |
192 | "outstanding_payment_amount_cents": 0, | |
193 | "patron_count": 1, | |
194 | "pay_per_name": "month", | |
195 | "pledge_sum": 180, | |
196 | "pledge_url": "/bePatron?c=925561", | |
197 | "published_at": "2017-06-13T05:35:54+00:00", | |
198 | "summary": null, | |
199 | "thanks_embed": null, | |
200 | "thanks_msg": "Thank you for helping me pursue my interests. I hope you enjoy the results of my research.", | |
201 | "thanks_video_url": null | |
202 | }, | |
203 | "id": "925561", | |
204 | "relationships": { | |
205 | "creator": { | |
206 | "data": { | |
207 | "id": "6126463", | |
208 | "type": "user" | |
209 | }, | |
210 | "links": { | |
211 | "related": "https://api.patreon.com/user/6126463" | |
212 | } | |
213 | }, | |
214 | "goals": { | |
215 | "data": [ | |
216 | { | |
217 | "id": "866143", | |
218 | "type": "goal" | |
219 | }, | |
220 | { | |
221 | "id": "866144", | |
222 | "type": "goal" | |
223 | } | |
224 | ] | |
225 | }, | |
226 | "rewards": { | |
227 | "data": [ | |
228 | { | |
229 | "id": "-1", | |
230 | "type": "reward" | |
231 | }, | |
232 | { | |
233 | "id": "0", | |
234 | "type": "reward" | |
235 | }, | |
236 | { | |
237 | "id": "1683378", | |
238 | "type": "reward" | |
239 | } | |
240 | ] | |
241 | } | |
242 | }, | |
243 | "type": "campaign" | |
244 | }, | |
245 | { | |
246 | "attributes": { | |
247 | "amount": 0, | |
248 | "amount_cents": 0, | |
249 | "created_at": null, | |
250 | "description": "Everyone", | |
251 | "remaining": 0, | |
252 | "requires_shipping": false, | |
253 | "type": "reward", | |
254 | "url": null, | |
255 | "user_limit": null | |
256 | }, | |
257 | "id": "-1", | |
258 | "relationships": { | |
259 | "creator": { | |
260 | "data": { | |
261 | "id": "6126463", | |
262 | "type": "user" | |
263 | }, | |
264 | "links": { | |
265 | "related": "https://api.patreon.com/user/6126463" | |
266 | } | |
267 | } | |
268 | }, | |
269 | "type": "reward" | |
270 | }, | |
271 | { | |
272 | "attributes": { | |
273 | "amount": 1, | |
274 | "amount_cents": 1, | |
275 | "created_at": null, | |
276 | "description": "Patrons Only", | |
277 | "remaining": 0, | |
278 | "requires_shipping": false, | |
279 | "type": "reward", | |
280 | "url": null, | |
281 | "user_limit": null | |
282 | }, | |
283 | "id": "0", | |
284 | "relationships": { | |
285 | "creator": { | |
286 | "data": { | |
287 | "id": "6126463", | |
288 | "type": "user" | |
289 | }, | |
290 | "links": { | |
291 | "related": "https://api.patreon.com/user/6126463" | |
292 | } | |
293 | } | |
294 | }, | |
295 | "type": "reward" | |
296 | }, | |
297 | { | |
298 | "attributes": { | |
299 | "amount": 2000, | |
300 | "amount_cents": 2000, | |
301 | "completed_percentage": 9, | |
302 | "created_at": "2017-06-13T05:41:54+00:00", | |
303 | "description": "Cover minimal server costs", | |
304 | "reached_at": null, | |
305 | "title": "" | |
306 | }, | |
307 | "id": "866143", | |
308 | "type": "goal" | |
309 | }, | |
310 | { | |
311 | "attributes": { | |
312 | "amount": 5000, | |
313 | "amount_cents": 5000, | |
314 | "completed_percentage": 3, | |
315 | "created_at": "2017-06-13T05:41:54+00:00", | |
316 | "description": "Improve webserver latency", | |
317 | "reached_at": null, | |
318 | "title": "" | |
319 | }, | |
320 | "id": "866144", | |
321 | "type": "goal" | |
322 | } | |
323 | ], | |
324 | "links": { | |
325 | "first": "https://api.patreon.com/oauth2/api/campaigns/925561/pledges?page%5Bcount%5D=10&sort=created" | |
326 | }, | |
327 | "meta": { | |
328 | "count": 1 | |
329 | } | |
330 | }⏎ |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | "sync" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | type Event struct { | |
9 | Mu sync.RWMutex | |
10 | Count int64 | |
11 | Total time.Duration | |
12 | Min time.Duration | |
13 | Max time.Duration | |
14 | } | |
15 | ||
16 | func (e *Event) Add(t time.Duration) { | |
17 | e.Mu.Lock() | |
18 | defer e.Mu.Unlock() | |
19 | if t > e.Max { | |
20 | e.Max = t | |
21 | } | |
22 | if t < e.Min { | |
23 | e.Min = t | |
24 | } | |
25 | e.Count++ | |
26 | e.Total += t | |
27 | } | |
28 | ||
29 | func (e *Event) MeanTime() time.Duration { | |
30 | e.Mu.RLock() | |
31 | defer e.Mu.RUnlock() | |
32 | return e.Total / time.Duration(e.Count) | |
33 | } | |
34 | ||
35 | func (e *Event) Init() { | |
36 | e.Min = math.MaxInt64 | |
37 | } |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "io" | |
5 | "net/http" | |
6 | "sort" | |
7 | "sync" | |
8 | "text/tabwriter" | |
9 | ) | |
10 | ||
11 | var ( | |
12 | mu sync.RWMutex | |
13 | events = map[string]*Event{} | |
14 | ) | |
15 | ||
16 | func init() { | |
17 | http.HandleFunc("/debug/perf", func(w http.ResponseWriter, r *http.Request) { | |
18 | w.Header().Set("Content-Type", "text/plain; charset=UTF-8") | |
19 | WriteEventsTable(w) | |
20 | }) | |
21 | } | |
22 | ||
23 | func WriteEventsTable(w io.Writer) { | |
24 | tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) | |
25 | fmt.Fprint(tw, "description\ttotal\tcount\tmin\tmean\tmax\n") | |
26 | type t struct { | |
27 | d string | |
28 | e Event | |
29 | } | |
30 | mu.RLock() | |
31 | es := make([]t, 0, len(events)) | |
32 | for d, e := range events { | |
33 | e.Mu.RLock() | |
34 | es = append(es, t{d, *e}) | |
35 | e.Mu.RUnlock() | |
36 | } | |
37 | mu.RUnlock() | |
38 | sort.Slice(es, func(i, j int) bool { | |
39 | return es[i].e.Total > es[j].e.Total | |
40 | }) | |
41 | for _, el := range es { | |
42 | e := el.e | |
43 | fmt.Fprintf(tw, "%s\t%v\t%v\t%v\t%v\t%v\n", el.d, e.Total, e.Count, e.Min, e.MeanTime(), e.Max) | |
44 | } | |
45 | tw.Flush() | |
46 | } |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | ||
5 | "github.com/anacrolix/missinggo" | |
6 | ) | |
7 | ||
8 | type TimedLocker struct { | |
9 | L sync.Locker | |
10 | Desc string | |
11 | } | |
12 | ||
13 | func (me *TimedLocker) Lock() { | |
14 | tr := NewTimer() | |
15 | me.L.Lock() | |
16 | tr.Mark(me.Desc) | |
17 | } | |
18 | ||
19 | func (me *TimedLocker) Unlock() { | |
20 | me.L.Unlock() | |
21 | } | |
22 | ||
23 | type TimedRWLocker struct { | |
24 | RWL missinggo.RWLocker | |
25 | WriteDesc string | |
26 | ReadDesc string | |
27 | } | |
28 | ||
29 | func (me *TimedRWLocker) Lock() { | |
30 | tr := NewTimer() | |
31 | me.RWL.Lock() | |
32 | tr.Mark(me.WriteDesc) | |
33 | } | |
34 | ||
35 | func (me *TimedRWLocker) Unlock() { | |
36 | me.RWL.Unlock() | |
37 | } | |
38 | ||
39 | func (me *TimedRWLocker) RLock() { | |
40 | tr := NewTimer() | |
41 | me.RWL.RLock() | |
42 | tr.Mark(me.ReadDesc) | |
43 | } | |
44 | ||
45 | func (me *TimedRWLocker) RUnlock() { | |
46 | me.RWL.RUnlock() | |
47 | } |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "io/ioutil" | |
4 | "strconv" | |
5 | "testing" | |
6 | ||
7 | _ "github.com/anacrolix/envpprof" | |
8 | ||
9 | "github.com/bradfitz/iter" | |
10 | ) | |
11 | ||
12 | func TestTimer(t *testing.T) { | |
13 | tr := NewTimer() | |
14 | tr.Mark("hiyo") | |
15 | tr.Mark("hiyo") | |
16 | WriteEventsTable(ioutil.Discard) | |
17 | } | |
18 | ||
19 | func BenchmarkStopWarm(b *testing.B) { | |
20 | tr := NewTimer() | |
21 | for range iter.N(b.N) { | |
22 | tr.Mark("a") | |
23 | } | |
24 | } | |
25 | ||
26 | func BenchmarkStopCold(b *testing.B) { | |
27 | tr := NewTimer() | |
28 | for i := range iter.N(b.N) { | |
29 | tr.Mark(strconv.FormatInt(int64(i), 10)) | |
30 | } | |
31 | } |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "runtime" | |
4 | ) | |
5 | ||
6 | func ScopeTimer(opts ...timerOpt) func() { | |
7 | t := NewTimer(CallerName(1)) | |
8 | return func() { t.Mark("returned") } | |
9 | } | |
10 | ||
11 | func ScopeTimerOk(ok *bool) func() { | |
12 | t := NewTimer(CallerName(1)) | |
13 | return func() { t.MarkOk(*ok) } | |
14 | } | |
15 | ||
16 | func ScopeTimerErr(err *error) func() { | |
17 | t := NewTimer(CallerName(1)) | |
18 | return func() { | |
19 | r := recover() | |
20 | if r != nil { | |
21 | t.Mark("panic") | |
22 | panic(r) | |
23 | } | |
24 | t.MarkErr(*err) | |
25 | } | |
26 | } | |
27 | ||
28 | func CallerName(skip int) timerOpt { | |
29 | return Name(getCallerName(skip)) | |
30 | } | |
31 | ||
32 | func getCallerName(skip int) string { | |
33 | var pc [1]uintptr | |
34 | runtime.Callers(3+skip, pc[:]) | |
35 | fs := runtime.CallersFrames(pc[:]) | |
36 | f, _ := fs.Next() | |
37 | return f.Func.Name() | |
38 | } |
0 | package perf | |
1 | ||
2 | import ( | |
3 | "log" | |
4 | "runtime" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | type Timer struct { | |
9 | started time.Time | |
10 | log bool | |
11 | name string | |
12 | marked bool | |
13 | } | |
14 | ||
15 | func NewTimer(opts ...timerOpt) (t *Timer) { | |
16 | t = &Timer{ | |
17 | started: time.Now(), | |
18 | } | |
19 | for _, o := range opts { | |
20 | o(t) | |
21 | } | |
22 | if t.log && t.name != "" { | |
23 | log.Printf("starting timer %q", t.name) | |
24 | } | |
25 | runtime.SetFinalizer(t, func(t *Timer) { | |
26 | if t.marked { | |
27 | return | |
28 | } | |
29 | log.Printf("timer %#v was never marked", t) | |
30 | }) | |
31 | return | |
32 | } | |
33 | ||
34 | type timerOpt func(*Timer) | |
35 | ||
36 | func Log(t *Timer) { | |
37 | t.log = true | |
38 | } | |
39 | ||
40 | func Name(name string) func(*Timer) { | |
41 | return func(t *Timer) { | |
42 | t.name = name | |
43 | } | |
44 | } | |
45 | ||
46 | func (t *Timer) Mark(events ...string) time.Duration { | |
47 | d := time.Since(t.started) | |
48 | if len(events) == 0 { | |
49 | if t.name == "" { | |
50 | panic("no name or events specified") | |
51 | } | |
52 | t.addDuration(t.name, d) | |
53 | } else { | |
54 | for _, e := range events { | |
55 | if t.name != "" { | |
56 | e = t.name + "/" + e | |
57 | } | |
58 | t.addDuration(e, d) | |
59 | } | |
60 | } | |
61 | return d | |
62 | } | |
63 | ||
64 | func (t *Timer) MarkOk(ok bool) { | |
65 | if ok { | |
66 | t.Mark("ok") | |
67 | } else { | |
68 | t.Mark("not ok") | |
69 | } | |
70 | } | |
71 | ||
72 | func (t *Timer) MarkErr(err error) { | |
73 | if err == nil { | |
74 | t.Mark("success") | |
75 | } else { | |
76 | t.Mark("error") | |
77 | } | |
78 | } | |
79 | ||
80 | func (t *Timer) addDuration(desc string, d time.Duration) { | |
81 | t.marked = true | |
82 | mu.RLock() | |
83 | e := events[desc] | |
84 | mu.RUnlock() | |
85 | if e == nil { | |
86 | mu.Lock() | |
87 | e = events[desc] | |
88 | if e == nil { | |
89 | e = new(Event) | |
90 | e.Init() | |
91 | events[desc] = e | |
92 | } | |
93 | mu.Unlock() | |
94 | } | |
95 | e.Add(d) | |
96 | if t.log { | |
97 | if t.name != "" { | |
98 | log.Printf("timer %q got event %q after %s", t.name, desc, d) | |
99 | } else { | |
100 | log.Printf("marking event %q after %s", desc, d) | |
101 | } | |
102 | } | |
103 | } |
0 | // Package pproffd is for detecting resource leaks due to unclosed handles. | |
1 | package pproffd | |
2 | ||
3 | import ( | |
4 | "io" | |
5 | "net" | |
6 | "os" | |
7 | "runtime/pprof" | |
8 | ) | |
9 | ||
10 | const enabled = false | |
11 | ||
12 | var p *pprof.Profile | |
13 | ||
14 | func init() { | |
15 | if enabled { | |
16 | p = pprof.NewProfile("fds") | |
17 | } | |
18 | } | |
19 | ||
20 | type fd int | |
21 | ||
22 | func (me *fd) Closed() { | |
23 | p.Remove(me) | |
24 | } | |
25 | ||
26 | func add(skip int) (ret *fd) { | |
27 | ret = new(fd) | |
28 | p.Add(ret, skip+2) | |
29 | return | |
30 | } | |
31 | ||
32 | type closeWrapper struct { | |
33 | fd *fd | |
34 | c io.Closer | |
35 | } | |
36 | ||
37 | func (me closeWrapper) Close() error { | |
38 | me.fd.Closed() | |
39 | return me.c.Close() | |
40 | } | |
41 | ||
42 | func newCloseWrapper(c io.Closer) closeWrapper { | |
43 | return closeWrapper{ | |
44 | fd: add(2), | |
45 | c: c, | |
46 | } | |
47 | } | |
48 | ||
49 | type wrappedNetConn struct { | |
50 | net.Conn | |
51 | closeWrapper | |
52 | } | |
53 | ||
54 | func (me wrappedNetConn) Close() error { | |
55 | return me.closeWrapper.Close() | |
56 | } | |
57 | ||
58 | // Tracks a net.Conn until Close() is explicitly called. | |
59 | func WrapNetConn(nc net.Conn) net.Conn { | |
60 | if !enabled { | |
61 | return nc | |
62 | } | |
63 | if nc == nil { | |
64 | return nil | |
65 | } | |
66 | return wrappedNetConn{ | |
67 | nc, | |
68 | newCloseWrapper(nc), | |
69 | } | |
70 | } | |
71 | ||
72 | type OSFile interface { | |
73 | io.Reader | |
74 | io.Seeker | |
75 | io.Closer | |
76 | io.Writer | |
77 | Stat() (os.FileInfo, error) | |
78 | io.ReaderAt | |
79 | io.WriterAt | |
80 | } | |
81 | ||
82 | type wrappedOSFile struct { | |
83 | *os.File | |
84 | closeWrapper | |
85 | } | |
86 | ||
87 | func (me wrappedOSFile) Close() error { | |
88 | return me.closeWrapper.Close() | |
89 | } | |
90 | ||
91 | func WrapOSFile(f *os.File) OSFile { | |
92 | if !enabled { | |
93 | return f | |
94 | } | |
95 | return &wrappedOSFile{f, newCloseWrapper(f)} | |
96 | } |
0 | // Package prioritybitmap implements a set of integers ordered by attached | |
1 | // priorities. | |
2 | package prioritybitmap | |
3 | ||
4 | import ( | |
5 | "sync" | |
6 | ||
7 | "github.com/anacrolix/missinggo/bitmap" | |
8 | "github.com/anacrolix/missinggo/iter" | |
9 | "github.com/anacrolix/missinggo/orderedmap" | |
10 | ) | |
11 | ||
12 | var ( | |
13 | bitSets = sync.Pool{ | |
14 | New: func() interface{} { | |
15 | return make(map[int]struct{}, 1) | |
16 | }, | |
17 | } | |
18 | ) | |
19 | ||
20 | // Maintains set of ints ordered by priority. | |
21 | type PriorityBitmap struct { | |
22 | mu sync.Mutex | |
23 | // From priority to singleton or set of bit indices. | |
24 | om orderedmap.OrderedMap | |
25 | // From bit index to priority | |
26 | priorities map[int]int | |
27 | } | |
28 | ||
29 | var _ bitmap.Interface = (*PriorityBitmap)(nil) | |
30 | ||
31 | func (me *PriorityBitmap) Contains(bit int) bool { | |
32 | _, ok := me.priorities[bit] | |
33 | return ok | |
34 | } | |
35 | ||
36 | func (me *PriorityBitmap) Len() int { | |
37 | return len(me.priorities) | |
38 | } | |
39 | ||
40 | func (me *PriorityBitmap) Clear() { | |
41 | me.om = nil | |
42 | me.priorities = nil | |
43 | } | |
44 | ||
45 | func (me *PriorityBitmap) deleteBit(bit int) (priority int, ok bool) { | |
46 | priority, ok = me.priorities[bit] | |
47 | if !ok { | |
48 | return | |
49 | } | |
50 | switch v := me.om.Get(priority).(type) { | |
51 | case int: | |
52 | if v != bit { | |
53 | panic("invariant broken") | |
54 | } | |
55 | case map[int]struct{}: | |
56 | if _, ok := v[bit]; !ok { | |
57 | panic("invariant broken") | |
58 | } | |
59 | delete(v, bit) | |
60 | if len(v) != 0 { | |
61 | return | |
62 | } | |
63 | bitSets.Put(v) | |
64 | } | |
65 | me.om.Unset(priority) | |
66 | if me.om.Len() == 0 { | |
67 | me.om = nil | |
68 | } | |
69 | return | |
70 | } | |
71 | ||
72 | func bitLess(l, r interface{}) bool { | |
73 | return l.(int) < r.(int) | |
74 | } | |
75 | ||
76 | func (me *PriorityBitmap) lazyInit() { | |
77 | me.om = orderedmap.New(func(l, r interface{}) bool { | |
78 | return l.(int) < r.(int) | |
79 | }) | |
80 | me.priorities = make(map[int]int) | |
81 | } | |
82 | ||
83 | // Returns true if the priority is changed, or the bit wasn't present. | |
84 | func (me *PriorityBitmap) Set(bit int, priority int) bool { | |
85 | if p, ok := me.priorities[bit]; ok && p == priority { | |
86 | return false | |
87 | } | |
88 | if oldPriority, deleted := me.deleteBit(bit); deleted && oldPriority == priority { | |
89 | panic("should have already returned") | |
90 | } | |
91 | if me.priorities == nil { | |
92 | me.priorities = make(map[int]int) | |
93 | } | |
94 | me.priorities[bit] = priority | |
95 | if me.om == nil { | |
96 | me.om = orderedmap.New(bitLess) | |
97 | } | |
98 | _v, ok := me.om.GetOk(priority) | |
99 | if !ok { | |
100 | // No other bits with this priority, set it to a lone int. | |
101 | me.om.Set(priority, bit) | |
102 | return true | |
103 | } | |
104 | switch v := _v.(type) { | |
105 | case int: | |
106 | newV := bitSets.Get().(map[int]struct{}) | |
107 | newV[v] = struct{}{} | |
108 | newV[bit] = struct{}{} | |
109 | me.om.Set(priority, newV) | |
110 | case map[int]struct{}: | |
111 | v[bit] = struct{}{} | |
112 | default: | |
113 | panic(v) | |
114 | } | |
115 | return true | |
116 | } | |
117 | ||
118 | func (me *PriorityBitmap) Remove(bit int) bool { | |
119 | me.mu.Lock() | |
120 | defer me.mu.Unlock() | |
121 | if _, ok := me.deleteBit(bit); !ok { | |
122 | return false | |
123 | } | |
124 | delete(me.priorities, bit) | |
125 | if len(me.priorities) == 0 { | |
126 | me.priorities = nil | |
127 | } | |
128 | if me.om != nil && me.om.Len() == 0 { | |
129 | me.om = nil | |
130 | } | |
131 | return true | |
132 | } | |
133 | ||
134 | func (me *PriorityBitmap) Iter(f iter.Callback) { | |
135 | me.IterTyped(func(i int) bool { | |
136 | return f(i) | |
137 | }) | |
138 | } | |
139 | ||
140 | func (me *PriorityBitmap) IterTyped(_f func(i bitmap.BitIndex) bool) bool { | |
141 | me.mu.Lock() | |
142 | defer me.mu.Unlock() | |
143 | if me == nil || me.om == nil { | |
144 | return true | |
145 | } | |
146 | f := func(i int) bool { | |
147 | me.mu.Unlock() | |
148 | defer me.mu.Lock() | |
149 | return _f(i) | |
150 | } | |
151 | return iter.All(func(key interface{}) bool { | |
152 | value := me.om.Get(key) | |
153 | switch v := value.(type) { | |
154 | case int: | |
155 | return f(v) | |
156 | case map[int]struct{}: | |
157 | for i := range v { | |
158 | if !f(i) { | |
159 | return false | |
160 | } | |
161 | } | |
162 | } | |
163 | return true | |
164 | }, me.om.Iter) | |
165 | } | |
166 | ||
167 | func (me *PriorityBitmap) IsEmpty() bool { | |
168 | if me.om == nil { | |
169 | return true | |
170 | } | |
171 | return me.om.Len() == 0 | |
172 | } | |
173 | ||
174 | // ok is false if the bit is not set. | |
175 | func (me *PriorityBitmap) GetPriority(bit int) (prio int, ok bool) { | |
176 | prio, ok = me.priorities[bit] | |
177 | return | |
178 | } |
0 | package prioritybitmap | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ||
8 | "github.com/anacrolix/missinggo/iter" | |
9 | ) | |
10 | ||
11 | func TestEmpty(t *testing.T) { | |
12 | var pb PriorityBitmap | |
13 | it := iter.NewIterator(&pb) | |
14 | assert.Panics(t, func() { it.Value() }) | |
15 | assert.False(t, it.Next()) | |
16 | } | |
17 | ||
18 | func TestIntBounds(t *testing.T) { | |
19 | var pb PriorityBitmap | |
20 | assert.True(t, pb.Set(math.MaxInt32, math.MinInt32)) | |
21 | assert.True(t, pb.Set(math.MinInt32, math.MaxInt32)) | |
22 | assert.EqualValues(t, []interface{}{math.MaxInt32, math.MinInt32}, iter.IterableAsSlice(&pb)) | |
23 | } | |
24 | ||
25 | func TestDistinct(t *testing.T) { | |
26 | var pb PriorityBitmap | |
27 | assert.True(t, pb.Set(0, 0)) | |
28 | pb.Set(1, 1) | |
29 | assert.EqualValues(t, []interface{}{0, 1}, iter.IterableAsSlice(&pb)) | |
30 | pb.Set(0, -1) | |
31 | assert.EqualValues(t, []interface{}{0, 1}, iter.IterableAsSlice(&pb)) | |
32 | pb.Set(1, -2) | |
33 | assert.EqualValues(t, []interface{}{1, 0}, iter.IterableAsSlice(&pb)) | |
34 | } | |
35 | ||
36 | func TestNextAfterIterFinished(t *testing.T) { | |
37 | var pb PriorityBitmap | |
38 | pb.Set(0, 0) | |
39 | it := iter.NewIterator(&pb) | |
40 | assert.True(t, it.Next()) | |
41 | assert.False(t, it.Next()) | |
42 | assert.False(t, it.Next()) | |
43 | } | |
44 | ||
45 | func TestMutationResults(t *testing.T) { | |
46 | var pb PriorityBitmap | |
47 | assert.False(t, pb.Remove(1)) | |
48 | assert.True(t, pb.Set(1, -1)) | |
49 | assert.True(t, pb.Set(1, 2)) | |
50 | assert.True(t, pb.Set(2, 2)) | |
51 | assert.True(t, pb.Set(2, -1)) | |
52 | assert.False(t, pb.Set(1, 2)) | |
53 | assert.EqualValues(t, []interface{}{2, 1}, iter.IterableAsSlice(&pb)) | |
54 | assert.True(t, pb.Set(1, -1)) | |
55 | assert.False(t, pb.Remove(0)) | |
56 | assert.True(t, pb.Remove(1)) | |
57 | assert.False(t, pb.Remove(0)) | |
58 | assert.False(t, pb.Remove(1)) | |
59 | assert.True(t, pb.Remove(2)) | |
60 | assert.False(t, pb.Remove(2)) | |
61 | assert.False(t, pb.Remove(0)) | |
62 | assert.True(t, pb.IsEmpty()) | |
63 | assert.Len(t, iter.IterableAsSlice(&pb), 0) | |
64 | } | |
65 | ||
66 | func TestDoubleRemove(t *testing.T) { | |
67 | var pb PriorityBitmap | |
68 | assert.True(t, pb.Set(0, 0)) | |
69 | assert.True(t, pb.Remove(0)) | |
70 | assert.False(t, pb.Remove(0)) | |
71 | } |
0 | package xprometheus | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "expvar" | |
5 | "fmt" | |
6 | "strconv" | |
7 | ||
8 | "github.com/bradfitz/iter" | |
9 | "github.com/prometheus/client_golang/prometheus" | |
10 | ) | |
11 | ||
12 | // A Prometheus collector that exposes all vars. | |
13 | type expvarCollector struct { | |
14 | descs map[int]*prometheus.Desc | |
15 | } | |
16 | ||
17 | func NewExpvarCollector() expvarCollector { | |
18 | return expvarCollector{ | |
19 | descs: make(map[int]*prometheus.Desc), | |
20 | } | |
21 | } | |
22 | ||
23 | const ( | |
24 | fqName = "go_expvar" | |
25 | help = "All expvars" | |
26 | ) | |
27 | ||
28 | var desc = prometheus.NewDesc(fqName, help, nil, nil) | |
29 | ||
30 | // Describe implements Collector. | |
31 | func (e expvarCollector) Describe(ch chan<- *prometheus.Desc) { | |
32 | ch <- desc | |
33 | } | |
34 | ||
35 | // Collect implements Collector. | |
36 | func (e expvarCollector) Collect(ch chan<- prometheus.Metric) { | |
37 | expvar.Do(func(kv expvar.KeyValue) { | |
38 | collector{ | |
39 | f: func(m prometheus.Metric) { | |
40 | ch <- m | |
41 | }, | |
42 | labelValues: []string{kv.Key}, | |
43 | descs: e.descs, | |
44 | }.collectVar(kv.Value) | |
45 | }) | |
46 | } | |
47 | ||
48 | func labels(n int) (ls []string) { | |
49 | for i := range iter.N(n) { | |
50 | ls = append(ls, "key"+strconv.FormatInt(int64(i), 10)) | |
51 | } | |
52 | return | |
53 | } | |
54 | ||
55 | type collector struct { | |
56 | f func(prometheus.Metric) | |
57 | labelValues []string | |
58 | descs map[int]*prometheus.Desc | |
59 | } | |
60 | ||
61 | func (c *collector) newMetric(f float64) { | |
62 | c.f(prometheus.MustNewConstMetric( | |
63 | c.desc(), | |
64 | prometheus.UntypedValue, | |
65 | float64(f), | |
66 | c.labelValues...)) | |
67 | } | |
68 | ||
69 | func (c collector) desc() *prometheus.Desc { | |
70 | d, ok := c.descs[len(c.labelValues)] | |
71 | if !ok { | |
72 | d = prometheus.NewDesc(fqName, "", labels(len(c.labelValues)), nil) | |
73 | c.descs[len(c.labelValues)] = d | |
74 | } | |
75 | return d | |
76 | } | |
77 | ||
78 | func (c collector) metricError(err error) { | |
79 | c.f(prometheus.NewInvalidMetric(c.desc(), err)) | |
80 | } | |
81 | ||
82 | func (c collector) withLabelValue(lv string) collector { | |
83 | c.labelValues = append(c.labelValues, lv) | |
84 | return c | |
85 | } | |
86 | ||
87 | func (c collector) collectJsonValue(v interface{}) { | |
88 | switch v := v.(type) { | |
89 | case float64: | |
90 | c.newMetric(v) | |
91 | case map[string]interface{}: | |
92 | for k, v := range v { | |
93 | c.withLabelValue(k).collectJsonValue(v) | |
94 | } | |
95 | case bool: | |
96 | if v { | |
97 | c.newMetric(1) | |
98 | } else { | |
99 | c.newMetric(0) | |
100 | } | |
101 | case string: | |
102 | c.f(prometheus.MustNewConstMetric( | |
103 | prometheus.NewDesc("go_expvar", "", | |
104 | append(labels(len(c.labelValues)), "value"), | |
105 | nil), | |
106 | prometheus.UntypedValue, | |
107 | 1, | |
108 | append(c.labelValues, v)..., | |
109 | )) | |
110 | case []interface{}: | |
111 | for i, v := range v { | |
112 | c.withLabelValue(strconv.FormatInt(int64(i), 10)).collectJsonValue(v) | |
113 | } | |
114 | default: | |
115 | c.metricError(fmt.Errorf("unhandled json value type %T", v)) | |
116 | } | |
117 | } | |
118 | ||
119 | func (c collector) collectVar(v expvar.Var) { | |
120 | var jv interface{} | |
121 | if err := json.Unmarshal([]byte(v.String()), &jv); err != nil { | |
122 | c.metricError(err) | |
123 | } | |
124 | c.collectJsonValue(jv) | |
125 | } |
0 | package xprometheus | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | "testing" | |
5 | ||
6 | "github.com/bradfitz/iter" | |
7 | "github.com/prometheus/client_golang/prometheus" | |
8 | ) | |
9 | ||
10 | func BenchmarkExpvarCollector_Collect(b *testing.B) { | |
11 | ec := NewExpvarCollector() | |
12 | ch := make(chan prometheus.Metric) | |
13 | n := 0 | |
14 | var wg sync.WaitGroup | |
15 | wg.Add(1) | |
16 | go func() { | |
17 | defer wg.Done() | |
18 | for range ch { | |
19 | n++ | |
20 | } | |
21 | }() | |
22 | b.ReportAllocs() | |
23 | for range iter.N(b.N) { | |
24 | ec.Collect(ch) | |
25 | } | |
26 | close(ch) | |
27 | wg.Wait() | |
28 | b.Logf("collected %d metrics (%f per collect)", n, float64(n)/float64(b.N)) | |
29 | } |
0 | package pubsub | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | ) | |
5 | ||
6 | type PubSub struct { | |
7 | mu sync.Mutex | |
8 | next chan item | |
9 | closed bool | |
10 | } | |
11 | ||
12 | type item struct { | |
13 | value interface{} | |
14 | next chan item | |
15 | } | |
16 | ||
17 | type Subscription struct { | |
18 | next chan item | |
19 | Values chan interface{} | |
20 | mu sync.Mutex | |
21 | closed chan struct{} | |
22 | } | |
23 | ||
24 | func NewPubSub() (ret *PubSub) { | |
25 | return new(PubSub) | |
26 | } | |
27 | ||
28 | func (me *PubSub) init() { | |
29 | me.next = make(chan item, 1) | |
30 | } | |
31 | ||
32 | func (me *PubSub) lazyInit() { | |
33 | me.mu.Lock() | |
34 | defer me.mu.Unlock() | |
35 | if me.closed { | |
36 | return | |
37 | } | |
38 | if me.next == nil { | |
39 | me.init() | |
40 | } | |
41 | } | |
42 | ||
43 | func (me *PubSub) Publish(v interface{}) { | |
44 | me.lazyInit() | |
45 | next := make(chan item, 1) | |
46 | i := item{v, next} | |
47 | me.mu.Lock() | |
48 | if !me.closed { | |
49 | me.next <- i | |
50 | me.next = next | |
51 | } | |
52 | me.mu.Unlock() | |
53 | } | |
54 | ||
55 | func (me *Subscription) Close() { | |
56 | me.mu.Lock() | |
57 | defer me.mu.Unlock() | |
58 | select { | |
59 | case <-me.closed: | |
60 | default: | |
61 | close(me.closed) | |
62 | } | |
63 | } | |
64 | ||
65 | func (me *Subscription) runner() { | |
66 | defer close(me.Values) | |
67 | for { | |
68 | select { | |
69 | case i, ok := <-me.next: | |
70 | if !ok { | |
71 | me.Close() | |
72 | return | |
73 | } | |
74 | // Send the value back into the channel for someone else. This | |
75 | // won't block because the channel has a capacity of 1, and this | |
76 | // is currently the only copy of this value being sent to this | |
77 | // channel. | |
78 | me.next <- i | |
79 | // The next value comes from the channel given to us by the value | |
80 | // we just got. | |
81 | me.next = i.next | |
82 | select { | |
83 | case me.Values <- i.value: | |
84 | case <-me.closed: | |
85 | return | |
86 | } | |
87 | case <-me.closed: | |
88 | return | |
89 | } | |
90 | } | |
91 | } | |
92 | ||
93 | func (me *PubSub) Subscribe() (ret *Subscription) { | |
94 | me.lazyInit() | |
95 | ret = &Subscription{ | |
96 | closed: make(chan struct{}), | |
97 | Values: make(chan interface{}), | |
98 | } | |
99 | me.mu.Lock() | |
100 | ret.next = me.next | |
101 | me.mu.Unlock() | |
102 | go ret.runner() | |
103 | return | |
104 | } | |
105 | ||
106 | func (me *PubSub) Close() { | |
107 | me.mu.Lock() | |
108 | defer me.mu.Unlock() | |
109 | if me.closed { | |
110 | return | |
111 | } | |
112 | if me.next != nil { | |
113 | close(me.next) | |
114 | } | |
115 | me.closed = true | |
116 | } |
0 | package pubsub | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | "testing" | |
5 | ||
6 | "github.com/bradfitz/iter" | |
7 | "github.com/stretchr/testify/assert" | |
8 | "github.com/stretchr/testify/require" | |
9 | ) | |
10 | ||
11 | func TestDoubleClose(t *testing.T) { | |
12 | ps := NewPubSub() | |
13 | ps.Close() | |
14 | ps.Close() | |
15 | } | |
16 | ||
17 | func testBroadcast(t testing.TB, subs, vals int) { | |
18 | ps := NewPubSub() | |
19 | var wg sync.WaitGroup | |
20 | for range iter.N(subs) { | |
21 | wg.Add(1) | |
22 | s := ps.Subscribe() | |
23 | go func() { | |
24 | defer wg.Done() | |
25 | var e int | |
26 | for i := range s.Values { | |
27 | assert.Equal(t, e, i.(int)) | |
28 | e++ | |
29 | } | |
30 | assert.Equal(t, vals, e) | |
31 | }() | |
32 | } | |
33 | for i := range iter.N(vals) { | |
34 | ps.Publish(i) | |
35 | } | |
36 | ps.Close() | |
37 | wg.Wait() | |
38 | } | |
39 | ||
40 | func TestBroadcast(t *testing.T) { | |
41 | testBroadcast(t, 100, 10) | |
42 | } | |
43 | ||
44 | func BenchmarkBroadcast(b *testing.B) { | |
45 | for range iter.N(b.N) { | |
46 | testBroadcast(b, 10, 1000) | |
47 | } | |
48 | } | |
49 | ||
50 | func TestCloseSubscription(t *testing.T) { | |
51 | ps := NewPubSub() | |
52 | ps.Publish(1) | |
53 | s := ps.Subscribe() | |
54 | select { | |
55 | case <-s.Values: | |
56 | t.FailNow() | |
57 | default: | |
58 | } | |
59 | ps.Publish(2) | |
60 | s2 := ps.Subscribe() | |
61 | ps.Publish(3) | |
62 | require.Equal(t, 2, <-s.Values) | |
63 | require.EqualValues(t, 3, <-s.Values) | |
64 | s.Close() | |
65 | _, ok := <-s.Values | |
66 | require.False(t, ok) | |
67 | ps.Publish(4) | |
68 | ps.Close() | |
69 | require.Equal(t, 3, <-s2.Values) | |
70 | require.Equal(t, 4, <-s2.Values) | |
71 | require.Nil(t, <-s2.Values) | |
72 | s2.Close() | |
73 | } |
0 | package missinggo | |
1 | ||
2 | import "context" | |
3 | ||
4 | type ContextedReader struct { | |
5 | R ReadContexter | |
6 | Ctx context.Context | |
7 | } | |
8 | ||
9 | func (me ContextedReader) Read(b []byte) (int, error) { | |
10 | return me.R.ReadContext(me.Ctx, b) | |
11 | } | |
12 | ||
13 | type ReadContexter interface { | |
14 | ReadContext(context.Context, []byte) (int, error) | |
15 | } |
0 | package refclose | |
1 | ||
2 | import ( | |
3 | "runtime/pprof" | |
4 | "sync" | |
5 | ) | |
6 | ||
7 | var profile = pprof.NewProfile("refs") | |
8 | ||
9 | type RefPool struct { | |
10 | mu sync.Mutex | |
11 | rs map[interface{}]*resource | |
12 | } | |
13 | ||
14 | type Closer func() | |
15 | ||
16 | func (me *RefPool) inc(key interface{}) { | |
17 | me.mu.Lock() | |
18 | defer me.mu.Unlock() | |
19 | r := me.rs[key] | |
20 | if r == nil { | |
21 | r = new(resource) | |
22 | if me.rs == nil { | |
23 | me.rs = make(map[interface{}]*resource) | |
24 | } | |
25 | me.rs[key] = r | |
26 | } | |
27 | r.numRefs++ | |
28 | } | |
29 | ||
30 | func (me *RefPool) dec(key interface{}) { | |
31 | me.mu.Lock() | |
32 | defer me.mu.Unlock() | |
33 | r := me.rs[key] | |
34 | r.numRefs-- | |
35 | if r.numRefs > 0 { | |
36 | return | |
37 | } | |
38 | if r.numRefs < 0 { | |
39 | panic(r.numRefs) | |
40 | } | |
41 | r.closer() | |
42 | delete(me.rs, key) | |
43 | } | |
44 | ||
45 | type resource struct { | |
46 | closer Closer | |
47 | numRefs int | |
48 | } | |
49 | ||
50 | func (me *RefPool) NewRef(key interface{}) (ret *Ref) { | |
51 | me.inc(key) | |
52 | ret = &Ref{ | |
53 | pool: me, | |
54 | key: key, | |
55 | } | |
56 | profile.Add(ret, 0) | |
57 | return | |
58 | } | |
59 | ||
60 | type Ref struct { | |
61 | mu sync.Mutex | |
62 | pool *RefPool | |
63 | key interface{} | |
64 | closed bool | |
65 | } | |
66 | ||
67 | func (me *Ref) SetCloser(closer Closer) { | |
68 | me.pool.mu.Lock() | |
69 | defer me.pool.mu.Unlock() | |
70 | me.pool.rs[me.key].closer = closer | |
71 | } | |
72 | ||
73 | func (me *Ref) panicIfClosed() { | |
74 | if me.closed { | |
75 | panic("ref is closed") | |
76 | } | |
77 | } | |
78 | ||
79 | func (me *Ref) Release() { | |
80 | me.mu.Lock() | |
81 | defer me.mu.Unlock() | |
82 | me.panicIfClosed() | |
83 | profile.Remove(me) | |
84 | me.pool.dec(me.key) | |
85 | } | |
86 | ||
87 | func (me *Ref) Key() interface{} { | |
88 | me.mu.Lock() | |
89 | defer me.mu.Unlock() | |
90 | me.panicIfClosed() | |
91 | return me.key | |
92 | } |
0 | package refclose | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | "testing" | |
5 | ||
6 | "github.com/bradfitz/iter" | |
7 | "github.com/stretchr/testify/assert" | |
8 | ) | |
9 | ||
10 | type refTest struct { | |
11 | pool RefPool | |
12 | key interface{} | |
13 | objs map[*object]struct{} | |
14 | t *testing.T | |
15 | } | |
16 | ||
17 | func (me *refTest) run() { | |
18 | me.objs = make(map[*object]struct{}) | |
19 | var ( | |
20 | mu sync.Mutex | |
21 | curObj *object | |
22 | wg sync.WaitGroup | |
23 | ) | |
24 | for range iter.N(1000) { | |
25 | wg.Add(1) | |
26 | go func() { | |
27 | defer wg.Done() | |
28 | ref := me.pool.NewRef(me.key) | |
29 | mu.Lock() | |
30 | if curObj == nil { | |
31 | curObj = new(object) | |
32 | me.objs[curObj] = struct{}{} | |
33 | } | |
34 | // obj := curObj | |
35 | mu.Unlock() | |
36 | ref.SetCloser(func() { | |
37 | mu.Lock() | |
38 | if curObj.closed { | |
39 | panic("object already closed") | |
40 | } | |
41 | curObj.closed = true | |
42 | curObj = nil | |
43 | mu.Unlock() | |
44 | }) | |
45 | ref.Release() | |
46 | }() | |
47 | } | |
48 | wg.Wait() | |
49 | me.t.Logf("created %d objects", len(me.objs)) | |
50 | assert.True(me.t, len(me.objs) >= 1) | |
51 | for obj := range me.objs { | |
52 | assert.True(me.t, obj.closed) | |
53 | } | |
54 | } | |
55 | ||
56 | type object struct { | |
57 | closed bool | |
58 | } | |
59 | ||
60 | func Test(t *testing.T) { | |
61 | (&refTest{ | |
62 | key: 3, | |
63 | t: t, | |
64 | }).run() | |
65 | } |
0 | package reqctx | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "net/http" | |
5 | ||
6 | "github.com/anacrolix/missinggo/futures" | |
7 | ) | |
8 | ||
9 | var lazyValuesContextKey = new(byte) | |
10 | ||
11 | func WithLazyMiddleware() func(http.Handler) http.Handler { | |
12 | return func(h http.Handler) http.Handler { | |
13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
14 | r = WithLazy(r) | |
15 | h.ServeHTTP(w, r) | |
16 | }) | |
17 | } | |
18 | } | |
19 | ||
20 | func WithLazy(r *http.Request) *http.Request { | |
21 | if r.Context().Value(lazyValuesContextKey) == nil { | |
22 | r = r.WithContext(context.WithValue(r.Context(), lazyValuesContextKey, &LazyValues{r: r})) | |
23 | } | |
24 | return r | |
25 | } | |
26 | ||
27 | func GetLazyValues(ctx context.Context) *LazyValues { | |
28 | return ctx.Value(lazyValuesContextKey).(*LazyValues) | |
29 | } | |
30 | ||
31 | type LazyValues struct { | |
32 | values map[interface{}]*futures.F | |
33 | r *http.Request | |
34 | } | |
35 | ||
36 | func (me *LazyValues) Get(val *lazyValue) *futures.F { | |
37 | f := me.values[val.key] | |
38 | if f != nil { | |
39 | return f | |
40 | } | |
41 | f = futures.Start(func() (interface{}, error) { | |
42 | return val.get(me.r) | |
43 | }) | |
44 | if me.values == nil { | |
45 | me.values = make(map[interface{}]*futures.F) | |
46 | } | |
47 | me.values[val.key] = f | |
48 | return f | |
49 | } | |
50 | ||
51 | func NewLazyValue(get func(r *http.Request) (interface{}, error)) *lazyValue { | |
52 | val := &lazyValue{ | |
53 | get: get, | |
54 | } | |
55 | val.key = val | |
56 | return val | |
57 | } | |
58 | ||
59 | type lazyValue struct { | |
60 | key interface{} | |
61 | get func(r *http.Request) (interface{}, error) | |
62 | } | |
63 | ||
64 | func (me *lazyValue) Get(r *http.Request) *futures.F { | |
65 | return me.GetContext(r.Context()) | |
66 | } | |
67 | ||
68 | func (me *lazyValue) GetContext(ctx context.Context) *futures.F { | |
69 | return GetLazyValues(ctx).Get(me) | |
70 | } | |
71 | ||
72 | func (me *lazyValue) Prefetch(r *http.Request) { | |
73 | me.Get(r) | |
74 | } | |
75 | ||
76 | func (me *lazyValue) PrefetchMiddleware(h http.Handler) http.Handler { | |
77 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
78 | me.Prefetch(r) | |
79 | h.ServeHTTP(w, r) | |
80 | }) | |
81 | } |
0 | package reqctx | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "net/http" | |
5 | ||
6 | "github.com/anacrolix/missinggo/expect" | |
7 | ) | |
8 | ||
9 | func SetNewValue(r *http.Request, key, value interface{}) *http.Request { | |
10 | expect.Nil(r.Context().Value(key)) | |
11 | expect.NotNil(value) | |
12 | return r.WithContext(context.WithValue(r.Context(), key, value)) | |
13 | } |
0 | package reqctx | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "net/http" | |
5 | ||
6 | "github.com/anacrolix/missinggo/expect" | |
7 | ) | |
8 | ||
9 | func NewValue() *contextValue { | |
10 | return &contextValue{new(byte)} | |
11 | } | |
12 | ||
13 | type contextValue struct { | |
14 | key interface{} | |
15 | } | |
16 | ||
17 | func (me contextValue) Get(ctx context.Context) interface{} { | |
18 | return ctx.Value(me.key) | |
19 | } | |
20 | ||
21 | // Sets the value on the Request. It must not have been already set. | |
22 | func (me contextValue) SetRequestOnce(r *http.Request, val interface{}) *http.Request { | |
23 | expect.Nil(me.Get(r.Context())) | |
24 | return r.WithContext(context.WithValue(r.Context(), me.key, val)) | |
25 | } | |
26 | ||
27 | // Returns a middleware that sets the value in the Request's Context. | |
28 | func (me contextValue) SetMiddleware(val interface{}) func(http.Handler) http.Handler { | |
29 | return func(h http.Handler) http.Handler { | |
30 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
31 | r = me.SetRequestOnce(r, val) | |
32 | h.ServeHTTP(w, r) | |
33 | }) | |
34 | } | |
35 | } |
0 | package resource | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "errors" | |
5 | "fmt" | |
6 | "io" | |
7 | "net/http" | |
8 | "net/url" | |
9 | "os" | |
10 | "strconv" | |
11 | "time" | |
12 | ) | |
13 | ||
14 | // Provides access to resources through a http.Client. | |
15 | type HTTPProvider struct { | |
16 | Client *http.Client | |
17 | } | |
18 | ||
19 | var _ Provider = &HTTPProvider{} | |
20 | ||
21 | func (me *HTTPProvider) NewInstance(urlStr string) (r Instance, err error) { | |
22 | _r := new(httpInstance) | |
23 | _r.URL, err = url.Parse(urlStr) | |
24 | if err != nil { | |
25 | return | |
26 | } | |
27 | _r.Client = me.Client | |
28 | if _r.Client == nil { | |
29 | _r.Client = http.DefaultClient | |
30 | } | |
31 | r = _r | |
32 | return | |
33 | } | |
34 | ||
35 | type httpInstance struct { | |
36 | Client *http.Client | |
37 | URL *url.URL | |
38 | } | |
39 | ||
40 | var _ Instance = &httpInstance{} | |
41 | ||
42 | func mustNewRequest(method, urlStr string, body io.Reader) *http.Request { | |
43 | req, err := http.NewRequest(method, urlStr, body) | |
44 | if err != nil { | |
45 | panic(err) | |
46 | } | |
47 | return req | |
48 | } | |
49 | ||
50 | func responseError(r *http.Response) error { | |
51 | if r.StatusCode == http.StatusNotFound { | |
52 | return os.ErrNotExist | |
53 | } | |
54 | return errors.New(r.Status) | |
55 | } | |
56 | ||
57 | func (me *httpInstance) Get() (ret io.ReadCloser, err error) { | |
58 | resp, err := me.Client.Get(me.URL.String()) | |
59 | if err != nil { | |
60 | return | |
61 | } | |
62 | if resp.StatusCode == http.StatusOK { | |
63 | ret = resp.Body | |
64 | return | |
65 | } | |
66 | resp.Body.Close() | |
67 | err = responseError(resp) | |
68 | return | |
69 | } | |
70 | ||
71 | func (me *httpInstance) Put(r io.Reader) (err error) { | |
72 | resp, err := me.Client.Do(mustNewRequest("PUT", me.URL.String(), r)) | |
73 | if err != nil { | |
74 | return | |
75 | } | |
76 | resp.Body.Close() | |
77 | if resp.StatusCode == http.StatusOK { | |
78 | return | |
79 | } | |
80 | err = responseError(resp) | |
81 | return | |
82 | } | |
83 | ||
84 | func (me *httpInstance) ReadAt(b []byte, off int64) (n int, err error) { | |
85 | req := mustNewRequest("GET", me.URL.String(), nil) | |
86 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1)) | |
87 | resp, err := me.Client.Do(req) | |
88 | if err != nil { | |
89 | return | |
90 | } | |
91 | defer resp.Body.Close() | |
92 | switch resp.StatusCode { | |
93 | case http.StatusPartialContent: | |
94 | case http.StatusRequestedRangeNotSatisfiable: | |
95 | err = io.EOF | |
96 | return | |
97 | default: | |
98 | err = responseError(resp) | |
99 | return | |
100 | } | |
101 | // TODO: This will crash if ContentLength was not provided (-1). Do | |
102 | // something about that. | |
103 | b = b[:resp.ContentLength] | |
104 | return io.ReadFull(resp.Body, b) | |
105 | } | |
106 | ||
107 | func (me *httpInstance) WriteAt(b []byte, off int64) (n int, err error) { | |
108 | req := mustNewRequest("PATCH", me.URL.String(), bytes.NewReader(b)) | |
109 | req.ContentLength = int64(len(b)) | |
110 | req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1)) | |
111 | resp, err := me.Client.Do(req) | |
112 | if err != nil { | |
113 | return | |
114 | } | |
115 | resp.Body.Close() | |
116 | if resp.StatusCode != http.StatusOK { | |
117 | err = responseError(resp) | |
118 | } | |
119 | n = len(b) | |
120 | return | |
121 | } | |
122 | ||
123 | func (me *httpInstance) Stat() (fi os.FileInfo, err error) { | |
124 | resp, err := me.Client.Head(me.URL.String()) | |
125 | if err != nil { | |
126 | return | |
127 | } | |
128 | resp.Body.Close() | |
129 | if resp.StatusCode == http.StatusNotFound { | |
130 | err = os.ErrNotExist | |
131 | return | |
132 | } | |
133 | if resp.StatusCode != http.StatusOK { | |
134 | err = errors.New(resp.Status) | |
135 | return | |
136 | } | |
137 | var _fi httpFileInfo | |
138 | if h := resp.Header.Get("Last-Modified"); h != "" { | |
139 | _fi.lastModified, err = time.Parse(http.TimeFormat, h) | |
140 | if err != nil { | |
141 | err = fmt.Errorf("error parsing Last-Modified header: %s", err) | |
142 | return | |
143 | } | |
144 | } | |
145 | if h := resp.Header.Get("Content-Length"); h != "" { | |
146 | _fi.contentLength, err = strconv.ParseInt(h, 10, 64) | |
147 | if err != nil { | |
148 | err = fmt.Errorf("error parsing Content-Length header: %s", err) | |
149 | return | |
150 | } | |
151 | } | |
152 | fi = _fi | |
153 | return | |
154 | } | |
155 | ||
156 | func (me *httpInstance) Delete() (err error) { | |
157 | resp, err := me.Client.Do(mustNewRequest("DELETE", me.URL.String(), nil)) | |
158 | if err != nil { | |
159 | return | |
160 | } | |
161 | err = responseError(resp) | |
162 | resp.Body.Close() | |
163 | return | |
164 | } | |
165 | ||
166 | type httpFileInfo struct { | |
167 | lastModified time.Time | |
168 | contentLength int64 | |
169 | } | |
170 | ||
171 | var _ os.FileInfo = httpFileInfo{} | |
172 | ||
173 | func (fi httpFileInfo) IsDir() bool { | |
174 | return false | |
175 | } | |
176 | ||
177 | func (fi httpFileInfo) Mode() os.FileMode { | |
178 | return 0 | |
179 | } | |
180 | ||
181 | func (fi httpFileInfo) Name() string { | |
182 | return "" | |
183 | } | |
184 | ||
185 | func (fi httpFileInfo) Size() int64 { | |
186 | return fi.contentLength | |
187 | } | |
188 | ||
189 | func (fi httpFileInfo) ModTime() time.Time { | |
190 | return fi.lastModified | |
191 | } | |
192 | ||
193 | func (fi httpFileInfo) Sys() interface{} { | |
194 | return nil | |
195 | } |
0 | package resource | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "os" | |
5 | ) | |
6 | ||
7 | // Provides access to resources through the native OS filesystem. | |
8 | type OSFileProvider struct{} | |
9 | ||
10 | var _ Provider = OSFileProvider{} | |
11 | ||
12 | func (me OSFileProvider) NewInstance(filePath string) (r Instance, err error) { | |
13 | return &osFileInstance{filePath}, nil | |
14 | } | |
15 | ||
16 | type osFileInstance struct { | |
17 | path string | |
18 | } | |
19 | ||
20 | var _ Instance = &osFileInstance{} | |
21 | ||
22 | func (me *osFileInstance) Get() (ret io.ReadCloser, err error) { | |
23 | return os.Open(me.path) | |
24 | } | |
25 | ||
26 | func (me *osFileInstance) Put(r io.Reader) (err error) { | |
27 | f, err := os.OpenFile(me.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) | |
28 | if err != nil { | |
29 | return | |
30 | } | |
31 | defer f.Close() | |
32 | _, err = io.Copy(f, r) | |
33 | return | |
34 | } | |
35 | ||
36 | func (me *osFileInstance) ReadAt(b []byte, off int64) (n int, err error) { | |
37 | f, err := os.Open(me.path) | |
38 | if err != nil { | |
39 | return | |
40 | } | |
41 | defer f.Close() | |
42 | return f.ReadAt(b, off) | |
43 | } | |
44 | ||
45 | func (me *osFileInstance) WriteAt(b []byte, off int64) (n int, err error) { | |
46 | f, err := os.OpenFile(me.path, os.O_CREATE|os.O_WRONLY, 0640) | |
47 | if err != nil { | |
48 | return | |
49 | } | |
50 | defer f.Close() | |
51 | return f.WriteAt(b, off) | |
52 | } | |
53 | ||
54 | func (me *osFileInstance) Stat() (fi os.FileInfo, err error) { | |
55 | return os.Stat(me.path) | |
56 | } | |
57 | ||
58 | func (me *osFileInstance) Delete() error { | |
59 | return os.Remove(me.path) | |
60 | } |
0 | package resource | |
1 | ||
2 | type Provider interface { | |
3 | NewInstance(string) (Instance, error) | |
4 | } | |
5 | ||
6 | // TranslatedProvider manipulates resource locations, so as to allow | |
7 | // sandboxing, or relative paths for example. | |
8 | type TranslatedProvider struct { | |
9 | // The underlying Provider. | |
10 | BaseProvider Provider | |
11 | // Some location used in calculating final locations. | |
12 | BaseLocation string | |
13 | // Function that takes BaseLocation, and the caller location and returns | |
14 | // the location to be used with the BaseProvider. | |
15 | JoinLocations func(base, rel string) string | |
16 | } | |
17 | ||
18 | func (me TranslatedProvider) NewInstance(rel string) (Instance, error) { | |
19 | return me.BaseProvider.NewInstance(me.JoinLocations(me.BaseLocation, rel)) | |
20 | } |
0 | package resource | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "os" | |
5 | ) | |
6 | ||
7 | // An Instance represents the content at some location accessed through some | |
8 | // Provider. It's the data at some URL. | |
9 | type Instance interface { | |
10 | Get() (io.ReadCloser, error) | |
11 | Put(io.Reader) error | |
12 | Stat() (os.FileInfo, error) | |
13 | ReadAt([]byte, int64) (int, error) | |
14 | WriteAt([]byte, int64) (int, error) | |
15 | Delete() error | |
16 | } | |
17 | ||
18 | // Creates a io.ReadSeeker to an Instance. | |
19 | func ReadSeeker(r Instance) io.ReadSeeker { | |
20 | fi, err := r.Stat() | |
21 | if err != nil { | |
22 | return nil | |
23 | } | |
24 | return io.NewSectionReader(r, 0, fi.Size()) | |
25 | } | |
26 | ||
27 | // Move instance content, deleting the source if it succeeds. | |
28 | func Move(from, to Instance) (err error) { | |
29 | rc, err := from.Get() | |
30 | if err != nil { | |
31 | return | |
32 | } | |
33 | defer rc.Close() | |
34 | err = to.Put(rc) | |
35 | if err != nil { | |
36 | return | |
37 | } | |
38 | from.Delete() | |
39 | return | |
40 | } | |
41 | ||
42 | func Exists(i Instance) bool { | |
43 | _, err := i.Stat() | |
44 | return err == nil | |
45 | } |
0 | package missinggo | |
1 | ||
2 | // A RunLengthEncoder counts successive duplicate elements and emits the | |
3 | // element and the run length when the element changes or the encoder is | |
4 | // flushed. | |
5 | type RunLengthEncoder interface { | |
6 | // Add a series of identical elements to the stream. | |
7 | Append(element interface{}, count uint64) | |
8 | // Emit the current element and its count if non-zero without waiting for | |
9 | // the element to change. | |
10 | Flush() | |
11 | } | |
12 | ||
13 | type runLengthEncoder struct { | |
14 | eachRun func(element interface{}, count uint64) | |
15 | element interface{} | |
16 | count uint64 | |
17 | } | |
18 | ||
19 | // Creates a new RunLengthEncoder. eachRun is called when an element and its | |
20 | // count is emitted, per the RunLengthEncoder interface. | |
21 | func NewRunLengthEncoder(eachRun func(element interface{}, count uint64)) RunLengthEncoder { | |
22 | return &runLengthEncoder{ | |
23 | eachRun: eachRun, | |
24 | } | |
25 | } | |
26 | ||
27 | func (me *runLengthEncoder) Append(element interface{}, count uint64) { | |
28 | if element == me.element { | |
29 | me.count += count | |
30 | return | |
31 | } | |
32 | if me.count != 0 { | |
33 | me.eachRun(me.element, me.count) | |
34 | } | |
35 | me.count = count | |
36 | me.element = element | |
37 | } | |
38 | ||
39 | func (me *runLengthEncoder) Flush() { | |
40 | if me.count == 0 { | |
41 | return | |
42 | } | |
43 | me.eachRun(me.element, me.count) | |
44 | me.count = 0 | |
45 | } |
0 | package missinggo_test | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | "github.com/anacrolix/missinggo" | |
6 | ) | |
7 | ||
8 | func ExampleNewRunLengthEncoder() { | |
9 | var s string | |
10 | rle := missinggo.NewRunLengthEncoder(func(e interface{}, count uint64) { | |
11 | s += fmt.Sprintf("%d%c", count, e) | |
12 | }) | |
13 | for _, e := range "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW" { | |
14 | rle.Append(e, 1) | |
15 | } | |
16 | rle.Flush() | |
17 | fmt.Println(s) | |
18 | // Output: 12W1B12W3B24W1B14W | |
19 | } |
0 | package runid | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "database/sql" | |
5 | ||
6 | "github.com/anacrolix/missinggo/expect" | |
7 | ) | |
8 | ||
9 | type T int64 | |
10 | ||
11 | func New(db *sql.DB) (ret *T) { | |
12 | ctx := context.Background() | |
13 | conn, err := db.Conn(ctx) | |
14 | expect.Nil(err) | |
15 | defer func() { | |
16 | expect.Nil(conn.Close()) | |
17 | }() | |
18 | _, err = conn.ExecContext(ctx, `CREATE TABLE if not exists runs (started datetime default (datetime('now')))`) | |
19 | expect.Nil(err) | |
20 | res, err := conn.ExecContext(ctx, "insert into runs default values") | |
21 | expect.Nil(err) | |
22 | expect.OneRowAffected(res) | |
23 | expect.Nil(conn.QueryRowContext(ctx, "select last_insert_rowid()").Scan(&ret)) | |
24 | return | |
25 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "fmt" | |
5 | "io" | |
6 | ) | |
7 | ||
8 | type sectionReadSeeker struct { | |
9 | base io.ReadSeeker | |
10 | off, size int64 | |
11 | } | |
12 | ||
13 | type ReadSeekContexter interface { | |
14 | io.ReadSeeker | |
15 | ReadContexter | |
16 | } | |
17 | ||
18 | // Returns a ReadSeeker on a section of another ReadSeeker. | |
19 | func NewSectionReadSeeker(base io.ReadSeeker, off, size int64) (ret ReadSeekContexter) { | |
20 | ret = §ionReadSeeker{ | |
21 | base: base, | |
22 | off: off, | |
23 | size: size, | |
24 | } | |
25 | seekOff, err := ret.Seek(0, io.SeekStart) | |
26 | if err != nil { | |
27 | panic(err) | |
28 | } | |
29 | if seekOff != 0 { | |
30 | panic(seekOff) | |
31 | } | |
32 | return | |
33 | } | |
34 | ||
35 | func (me *sectionReadSeeker) Seek(off int64, whence int) (ret int64, err error) { | |
36 | switch whence { | |
37 | case io.SeekStart: | |
38 | off += me.off | |
39 | case io.SeekCurrent: | |
40 | case io.SeekEnd: | |
41 | off += me.off + me.size | |
42 | whence = io.SeekStart | |
43 | default: | |
44 | err = fmt.Errorf("unhandled whence: %d", whence) | |
45 | return | |
46 | } | |
47 | ret, err = me.base.Seek(off, whence) | |
48 | ret -= me.off | |
49 | return | |
50 | } | |
51 | ||
52 | func (me *sectionReadSeeker) ReadContext(ctx context.Context, b []byte) (int, error) { | |
53 | off, err := me.Seek(0, io.SeekCurrent) | |
54 | if err != nil { | |
55 | return 0, err | |
56 | } | |
57 | left := me.size - off | |
58 | if left <= 0 { | |
59 | return 0, io.EOF | |
60 | } | |
61 | b = LimitLen(b, left) | |
62 | if rc, ok := me.base.(ReadContexter); ok { | |
63 | return rc.ReadContext(ctx, b) | |
64 | } | |
65 | if ctx != context.Background() { | |
66 | // Can't handle cancellation. | |
67 | panic(ctx) | |
68 | } | |
69 | return me.base.Read(b) | |
70 | } | |
71 | ||
72 | func (me *sectionReadSeeker) Read(b []byte) (int, error) { | |
73 | return me.ReadContext(context.Background(), b) | |
74 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "io" | |
5 | "os" | |
6 | "testing" | |
7 | ||
8 | "github.com/stretchr/testify/assert" | |
9 | ) | |
10 | ||
11 | func TestSectionReadSeekerReadBeyondEnd(t *testing.T) { | |
12 | base := bytes.NewReader([]byte{1, 2, 3}) | |
13 | srs := NewSectionReadSeeker(base, 1, 1) | |
14 | dest := new(bytes.Buffer) | |
15 | n, err := io.Copy(dest, srs) | |
16 | assert.EqualValues(t, 1, n) | |
17 | assert.NoError(t, err) | |
18 | } | |
19 | ||
20 | func TestSectionReadSeekerSeekEnd(t *testing.T) { | |
21 | base := bytes.NewReader([]byte{1, 2, 3}) | |
22 | srs := NewSectionReadSeeker(base, 1, 1) | |
23 | off, err := srs.Seek(0, os.SEEK_END) | |
24 | assert.NoError(t, err) | |
25 | assert.EqualValues(t, 1, off) | |
26 | } |
0 | package missinggo | |
1 | ||
2 | import "io" | |
3 | ||
4 | type SectionWriter struct { | |
5 | w io.WriterAt | |
6 | off, len int64 | |
7 | } | |
8 | ||
9 | func NewSectionWriter(w io.WriterAt, off, len int64) *SectionWriter { | |
10 | return &SectionWriter{w, off, len} | |
11 | } | |
12 | ||
13 | func (me *SectionWriter) WriteAt(b []byte, off int64) (n int, err error) { | |
14 | if off >= me.len { | |
15 | err = io.EOF | |
16 | return | |
17 | } | |
18 | if off+int64(len(b)) > me.len { | |
19 | b = b[:me.len-off] | |
20 | } | |
21 | return me.w.WriteAt(b, me.off+off) | |
22 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "crypto/ecdsa" | |
4 | "crypto/rand" | |
5 | "crypto/rsa" | |
6 | "crypto/tls" | |
7 | "crypto/x509" | |
8 | "crypto/x509/pkix" | |
9 | "log" | |
10 | "math/big" | |
11 | "time" | |
12 | ) | |
13 | ||
14 | func publicKey(priv interface{}) interface{} { | |
15 | switch k := priv.(type) { | |
16 | case *rsa.PrivateKey: | |
17 | return &k.PublicKey | |
18 | case *ecdsa.PrivateKey: | |
19 | return &k.PublicKey | |
20 | default: | |
21 | return nil | |
22 | } | |
23 | } | |
24 | ||
25 | // Creates a self-signed certificate in memory for use with tls.Config. | |
26 | func NewSelfSignedCertificate() (cert tls.Certificate, err error) { | |
27 | cert.PrivateKey, err = rsa.GenerateKey(rand.Reader, 2048) | |
28 | if err != nil { | |
29 | return | |
30 | } | |
31 | notBefore := time.Now() | |
32 | notAfter := notBefore.Add(365 * 24 * time.Hour) | |
33 | ||
34 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | |
35 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | |
36 | if err != nil { | |
37 | log.Fatalf("failed to generate serial number: %s", err) | |
38 | } | |
39 | ||
40 | template := x509.Certificate{ | |
41 | SerialNumber: serialNumber, | |
42 | Subject: pkix.Name{ | |
43 | Organization: []string{"Acme Co"}, | |
44 | }, | |
45 | NotBefore: notBefore, | |
46 | NotAfter: notAfter, | |
47 | ||
48 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | |
49 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | |
50 | BasicConstraintsValid: true, | |
51 | } | |
52 | ||
53 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(cert.PrivateKey), cert.PrivateKey) | |
54 | if err != nil { | |
55 | log.Fatalf("Failed to create certificate: %s", err) | |
56 | } | |
57 | cert.Certificate = [][]byte{derBytes} | |
58 | return | |
59 | } |
0 | package missinggo | |
1 | ||
2 | import "sync" | |
3 | ||
4 | type ongoing struct { | |
5 | do sync.Mutex | |
6 | users int | |
7 | } | |
8 | ||
9 | type SingleFlight struct { | |
10 | mu sync.Mutex | |
11 | ongoing map[string]*ongoing | |
12 | } | |
13 | ||
14 | type Operation struct { | |
15 | sf *SingleFlight | |
16 | id string | |
17 | } | |
18 | ||
19 | func (op Operation) Unlock() { | |
20 | op.sf.Unlock(op.id) | |
21 | } | |
22 | ||
23 | func (me *SingleFlight) Lock(id string) Operation { | |
24 | me.mu.Lock() | |
25 | on, ok := me.ongoing[id] | |
26 | if !ok { | |
27 | on = new(ongoing) | |
28 | if me.ongoing == nil { | |
29 | me.ongoing = make(map[string]*ongoing) | |
30 | } | |
31 | me.ongoing[id] = on | |
32 | } | |
33 | on.users++ | |
34 | me.mu.Unlock() | |
35 | on.do.Lock() | |
36 | return Operation{me, id} | |
37 | } | |
38 | ||
39 | func (me *SingleFlight) Unlock(id string) { | |
40 | me.mu.Lock() | |
41 | on := me.ongoing[id] | |
42 | on.do.Unlock() | |
43 | on.users-- | |
44 | if on.users == 0 { | |
45 | delete(me.ongoing, id) | |
46 | } | |
47 | me.mu.Unlock() | |
48 | } |
0 | package slices | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | ||
5 | "github.com/bradfitz/iter" | |
6 | ) | |
7 | ||
8 | // Returns a copy of all the elements of slice []T as a slice of interface{}. | |
9 | func ToEmptyInterface(slice interface{}) (ret []interface{}) { | |
10 | v := reflect.ValueOf(slice) | |
11 | l := v.Len() | |
12 | ret = make([]interface{}, 0, l) | |
13 | for i := range iter.N(v.Len()) { | |
14 | ret = append(ret, v.Index(i).Interface()) | |
15 | } | |
16 | return | |
17 | } | |
18 | ||
19 | // Makes and sets a slice at *ptrTo, and type asserts all the elements from | |
20 | // from to it. | |
21 | func MakeInto(ptrTo interface{}, from interface{}) { | |
22 | fromSliceValue := reflect.ValueOf(from) | |
23 | fromLen := fromSliceValue.Len() | |
24 | if fromLen == 0 { | |
25 | return | |
26 | } | |
27 | // Deref the pointer to slice. | |
28 | slicePtrValue := reflect.ValueOf(ptrTo) | |
29 | if slicePtrValue.Kind() != reflect.Ptr { | |
30 | panic("destination is not a pointer") | |
31 | } | |
32 | destSliceValue := slicePtrValue.Elem() | |
33 | // The type of the elements of the destination slice. | |
34 | destSliceElemType := destSliceValue.Type().Elem() | |
35 | destSliceValue.Set(reflect.MakeSlice(destSliceValue.Type(), fromLen, fromLen)) | |
36 | for i := range iter.N(fromSliceValue.Len()) { | |
37 | // The value inside the interface in the slice element. | |
38 | itemValue := fromSliceValue.Index(i) | |
39 | if itemValue.Kind() == reflect.Interface { | |
40 | itemValue = itemValue.Elem() | |
41 | } | |
42 | convertedItem := itemValue.Convert(destSliceElemType) | |
43 | destSliceValue.Index(i).Set(convertedItem) | |
44 | } | |
45 | } |
0 | package slices | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | type herp int | |
9 | ||
10 | func TestCastSliceInterface(t *testing.T) { | |
11 | var dest []herp | |
12 | MakeInto(&dest, []interface{}{herp(1), herp(2)}) | |
13 | assert.Len(t, dest, 2) | |
14 | assert.EqualValues(t, 1, dest[0]) | |
15 | assert.EqualValues(t, 2, dest[1]) | |
16 | } | |
17 | ||
18 | func TestCastSliceInts(t *testing.T) { | |
19 | var dest []int | |
20 | MakeInto(&dest, []uint32{1, 2}) | |
21 | assert.Len(t, dest, 2) | |
22 | assert.EqualValues(t, 1, dest[0]) | |
23 | assert.EqualValues(t, 2, dest[1]) | |
24 | } |
0 | // Package slices has several utilities for operating on slices given Go's | |
1 | // lack of generic types. Many functions take an argument of type func(l, r T) | |
2 | // bool, that's expected to compute l < r where T is T in []T, the type of the | |
3 | // given slice. | |
4 | package slices |
0 | package slices | |
1 | ||
2 | import "reflect" | |
3 | ||
4 | // sl []T, f is func(*T) bool. | |
5 | func FilterInPlace(sl interface{}, f interface{}) { | |
6 | v := reflect.ValueOf(sl).Elem() | |
7 | j := 0 | |
8 | for i := 0; i < v.Len(); i++ { | |
9 | e := v.Index(i) | |
10 | if reflect.ValueOf(f).Call([]reflect.Value{e.Addr()})[0].Bool() { | |
11 | v.Index(j).Set(e) | |
12 | j++ | |
13 | } | |
14 | } | |
15 | v.SetLen(j) | |
16 | } |
0 | package slices | |
1 | ||
2 | import "reflect" | |
3 | ||
4 | type MapItem struct { | |
5 | Key, Elem interface{} | |
6 | } | |
7 | ||
8 | // Creates a []struct{Key K; Value V} for map[K]V. | |
9 | func FromMap(m interface{}) (slice []MapItem) { | |
10 | mapValue := reflect.ValueOf(m) | |
11 | for _, key := range mapValue.MapKeys() { | |
12 | slice = append(slice, MapItem{key.Interface(), mapValue.MapIndex(key).Interface()}) | |
13 | } | |
14 | return | |
15 | } | |
16 | ||
17 | // Returns all the elements []T, from m where m is map[K]T. | |
18 | func FromMapElems(m interface{}) interface{} { | |
19 | inValue := reflect.ValueOf(m) | |
20 | outValue := reflect.MakeSlice(reflect.SliceOf(inValue.Type().Elem()), inValue.Len(), inValue.Len()) | |
21 | for i, key := range inValue.MapKeys() { | |
22 | outValue.Index(i).Set(inValue.MapIndex(key)) | |
23 | } | |
24 | return outValue.Interface() | |
25 | } | |
26 | ||
27 | // Returns all the elements []K, from m where m is map[K]T. | |
28 | func FromMapKeys(m interface{}) interface{} { | |
29 | inValue := reflect.ValueOf(m) | |
30 | outValue := reflect.MakeSlice(reflect.SliceOf(inValue.Type().Key()), inValue.Len(), inValue.Len()) | |
31 | for i, key := range inValue.MapKeys() { | |
32 | outValue.Index(i).Set(key) | |
33 | } | |
34 | return outValue.Interface() | |
35 | } | |
36 | ||
37 | // f: (T)T, input: []T, outout: []T | |
38 | func Map(f, input interface{}) interface{} { | |
39 | inputValue := reflect.ValueOf(input) | |
40 | funcValue := reflect.ValueOf(f) | |
41 | _len := inputValue.Len() | |
42 | retValue := reflect.MakeSlice(reflect.TypeOf(input), _len, _len) | |
43 | for i := 0; i < _len; i++ { | |
44 | out := funcValue.Call([]reflect.Value{inputValue.Index(i)}) | |
45 | retValue.Index(i).Set(out[0]) | |
46 | } | |
47 | return retValue.Interface() | |
48 | } |
0 | package slices | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestFromMap(t *testing.T) { | |
9 | sl := FromMap(map[string]int{"two": 2, "one": 1}) | |
10 | assert.Len(t, sl, 2) | |
11 | Sort(sl, func(left, right MapItem) bool { | |
12 | return left.Key.(string) < right.Key.(string) | |
13 | }) | |
14 | assert.EqualValues(t, []MapItem{{"one", 1}, {"two", 2}}, sl) | |
15 | } |
0 | package slices | |
1 | ||
2 | import ( | |
3 | "container/heap" | |
4 | "reflect" | |
5 | "sort" | |
6 | ) | |
7 | ||
8 | // Sorts the slice in place. Returns sl for convenience. | |
9 | func Sort(sl interface{}, less interface{}) interface{} { | |
10 | sorter := sorter{ | |
11 | sl: reflect.ValueOf(sl), | |
12 | less: reflect.ValueOf(less), | |
13 | } | |
14 | sort.Sort(&sorter) | |
15 | return sorter.sl.Interface() | |
16 | } | |
17 | ||
18 | // Creates a modifiable copy of a slice reference. Because you can't modify | |
19 | // non-pointer types inside an interface{}. | |
20 | func addressableSlice(slice interface{}) reflect.Value { | |
21 | v := reflect.ValueOf(slice) | |
22 | p := reflect.New(v.Type()) | |
23 | p.Elem().Set(v) | |
24 | return p.Elem() | |
25 | } | |
26 | ||
27 | // Returns a "container/heap".Interface for the provided slice. | |
28 | func HeapInterface(sl interface{}, less interface{}) heap.Interface { | |
29 | ret := &sorter{ | |
30 | sl: addressableSlice(sl), | |
31 | less: reflect.ValueOf(less), | |
32 | } | |
33 | heap.Init(ret) | |
34 | return ret | |
35 | } |
0 | package slices | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestSort(t *testing.T) { | |
9 | a := []int{3, 2, 1} | |
10 | Sort(a, func(left, right int) bool { | |
11 | return left < right | |
12 | }) | |
13 | assert.EqualValues(t, []int{1, 2, 3}, a) | |
14 | } |
0 | package slices | |
1 | ||
2 | import "reflect" | |
3 | ||
4 | type sorter struct { | |
5 | sl reflect.Value | |
6 | less reflect.Value | |
7 | } | |
8 | ||
9 | func (s *sorter) Len() int { | |
10 | return s.sl.Len() | |
11 | } | |
12 | ||
13 | func (s *sorter) Less(i, j int) bool { | |
14 | return s.less.Call([]reflect.Value{ | |
15 | s.sl.Index(i), | |
16 | s.sl.Index(j), | |
17 | })[0].Bool() | |
18 | } | |
19 | ||
20 | func (s *sorter) Swap(i, j int) { | |
21 | t := reflect.New(s.sl.Type().Elem()).Elem() | |
22 | t.Set(s.sl.Index(i)) | |
23 | s.sl.Index(i).Set(s.sl.Index(j)) | |
24 | s.sl.Index(j).Set(t) | |
25 | } | |
26 | ||
27 | func (s *sorter) Pop() interface{} { | |
28 | ret := s.sl.Index(s.sl.Len() - 1).Interface() | |
29 | s.sl.SetLen(s.sl.Len() - 1) | |
30 | return ret | |
31 | } | |
32 | ||
33 | func (s *sorter) Push(val interface{}) { | |
34 | s.sl = reflect.Append(s.sl, reflect.ValueOf(val)) | |
35 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | type SqliteTime time.Time | |
8 | ||
9 | var _ sql.Scanner = (*SqliteTime)(nil) | |
10 | ||
11 | func (me *SqliteTime) Scan(src interface{}) error { | |
12 | var tt time.Time | |
13 | tt, err := time.Parse("2006-01-02 15:04:05", string(src.([]byte))) | |
14 | *me = SqliteTime(tt) | |
15 | return err | |
16 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "io" | |
5 | "runtime" | |
6 | ) | |
7 | ||
8 | func WriteStack(w io.Writer, stack []uintptr) { | |
9 | for _, pc := range stack { | |
10 | if pc == 0 { | |
11 | break | |
12 | } | |
13 | pc-- | |
14 | f := runtime.FuncForPC(pc) | |
15 | if f.Name() == "runtime.goexit" { | |
16 | continue | |
17 | } | |
18 | file, line := f.FileLine(pc) | |
19 | fmt.Fprintf(w, "# %s:\t%s:%d\n", f.Name(), file, line) | |
20 | } | |
21 | fmt.Fprintf(w, "\n") | |
22 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "strconv" | |
4 | "strings" | |
5 | "unicode" | |
6 | ) | |
7 | ||
8 | func StringTruth(s string) (ret bool) { | |
9 | s = strings.TrimFunc(s, func(r rune) bool { | |
10 | return r == 0 || unicode.IsSpace(r) | |
11 | }) | |
12 | if s == "" { | |
13 | return false | |
14 | } | |
15 | ret, err := strconv.ParseBool(s) | |
16 | if err == nil { | |
17 | return | |
18 | } | |
19 | i, err := strconv.ParseInt(s, 0, 0) | |
20 | if err == nil { | |
21 | return i != 0 | |
22 | } | |
23 | return true | |
24 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | func TestStringTruth(t *testing.T) { | |
9 | for _, s := range []string{ | |
10 | "", | |
11 | " ", | |
12 | "\n", | |
13 | "\x00", | |
14 | "0", | |
15 | } { | |
16 | t.Run(s, func(t *testing.T) { | |
17 | assert.False(t, StringTruth(s)) | |
18 | }) | |
19 | } | |
20 | for _, s := range []string{ | |
21 | " 1", | |
22 | "t", | |
23 | } { | |
24 | t.Run(s, func(t *testing.T) { | |
25 | assert.True(t, StringTruth(s)) | |
26 | }) | |
27 | } | |
28 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "strings" | |
4 | ||
5 | "github.com/huandu/xstrings" | |
6 | ) | |
7 | ||
8 | func KebabCase(s string) string { | |
9 | return strings.Replace(xstrings.ToSnakeCase(s), "_", "-", -1) | |
10 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "sync" | |
4 | ) | |
5 | ||
6 | type RWLocker interface { | |
7 | sync.Locker | |
8 | RLock() | |
9 | RUnlock() | |
10 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "regexp" | |
4 | "runtime" | |
5 | ) | |
6 | ||
7 | // It will be the one and only identifier after a package specifier. | |
8 | var testNameRegexp = regexp.MustCompile(`\.(Test[\p{L}_\p{N}]*)`) | |
9 | ||
10 | // Returns the name of the test function from the call stack. See | |
11 | // http://stackoverflow.com/q/35535635/149482 for another method. | |
12 | func GetTestName() string { | |
13 | pc := make([]uintptr, 32) | |
14 | n := runtime.Callers(0, pc) | |
15 | for i := 0; i < n; i++ { | |
16 | name := runtime.FuncForPC(pc[i]).Name() | |
17 | ms := testNameRegexp.FindStringSubmatch(name) | |
18 | if ms == nil { | |
19 | continue | |
20 | } | |
21 | return ms[1] | |
22 | } | |
23 | panic("test name could not be recovered") | |
24 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/stretchr/testify/assert" | |
6 | ) | |
7 | ||
8 | // Since GetTestName panics if the test name isn't found, it'll be easy to | |
9 | // expand the tests if we find weird cases. | |
10 | func TestGetTestName(t *testing.T) { | |
11 | assert.EqualValues(t, "TestGetTestName", GetTestName()) | |
12 | } | |
13 | ||
14 | func TestGetSubtestName(t *testing.T) { | |
15 | t.Run("hello", func(t *testing.T) { | |
16 | assert.Contains(t, "TestGetSubtestName", GetTestName()) | |
17 | }) | |
18 | t.Run("world", func(t *testing.T) { | |
19 | assert.Contains(t, "TestGetSubtestName", GetTestName()) | |
20 | }) | |
21 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | // Returns a time.Timer that calls f. The timer is initially stopped. | |
8 | func StoppedFuncTimer(f func()) (t *time.Timer) { | |
9 | t = time.AfterFunc(math.MaxInt64, f) | |
10 | if !t.Stop() { | |
11 | panic("timer already fired") | |
12 | } | |
13 | return | |
14 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | "github.com/bradfitz/iter" | |
7 | "github.com/stretchr/testify/assert" | |
8 | ) | |
9 | ||
10 | func TestTimerDrain(t *testing.T) { | |
11 | tr := time.NewTimer(0) | |
12 | <-tr.C | |
13 | select { | |
14 | case <-tr.C: | |
15 | assert.Fail(t, "shouldn't have received again on the the expired timer") | |
16 | default: | |
17 | } | |
18 | tr.Reset(1) | |
19 | select { | |
20 | case <-tr.C: | |
21 | assert.Fail(t, "received too soon") | |
22 | default: | |
23 | } | |
24 | time.Sleep(1) | |
25 | <-tr.C | |
26 | // Stop() should return false, as it just fired. | |
27 | assert.False(t, tr.Stop()) | |
28 | tr.Reset(0) | |
29 | // Check we receive again after a Reset(). | |
30 | <-tr.C | |
31 | } | |
32 | ||
33 | func TestTimerDoesNotFireAfterStop(t *testing.T) { | |
34 | t.Skip("the standard library implementation is broken") | |
35 | fail := make(chan struct{}) | |
36 | done := make(chan struct{}) | |
37 | defer close(done) | |
38 | for range iter.N(1000) { | |
39 | tr := time.NewTimer(0) | |
40 | tr.Stop() | |
41 | // There may or may not be a value in the channel now. But definitely | |
42 | // one should not be added after we receive it. | |
43 | select { | |
44 | case <-tr.C: | |
45 | default: | |
46 | } | |
47 | // Now set the timer to trigger in hour. It definitely shouldn't be | |
48 | // receivable now for an hour. | |
49 | tr.Reset(time.Hour) | |
50 | go func() { | |
51 | select { | |
52 | case <-tr.C: | |
53 | // As soon as the channel receives, notify failure. | |
54 | fail <- struct{}{} | |
55 | case <-done: | |
56 | } | |
57 | }() | |
58 | } | |
59 | select { | |
60 | case <-fail: | |
61 | t.FailNow() | |
62 | case <-time.After(100 * time.Millisecond): | |
63 | } | |
64 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "crypto/tls" | |
4 | "strings" | |
5 | ) | |
6 | ||
7 | // Select the best named certificate per the usual behaviour if | |
8 | // c.GetCertificate is nil, and c.NameToCertificate is not. | |
9 | func BestNamedCertificate(c *tls.Config, clientHello *tls.ClientHelloInfo) (*tls.Certificate, bool) { | |
10 | name := strings.ToLower(clientHello.ServerName) | |
11 | for len(name) > 0 && name[len(name)-1] == '.' { | |
12 | name = name[:len(name)-1] | |
13 | } | |
14 | ||
15 | if cert, ok := c.NameToCertificate[name]; ok { | |
16 | return cert, true | |
17 | } | |
18 | ||
19 | // try replacing labels in the name with wildcards until we get a | |
20 | // match. | |
21 | labels := strings.Split(name, ".") | |
22 | for i := range labels { | |
23 | labels[i] = "*" | |
24 | candidate := strings.Join(labels, ".") | |
25 | if cert, ok := c.NameToCertificate[candidate]; ok { | |
26 | return cert, true | |
27 | } | |
28 | } | |
29 | ||
30 | return nil, false | |
31 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net/url" | |
4 | "path" | |
5 | ) | |
6 | ||
7 | // Returns URL opaque as an unrooted path. | |
8 | func URLOpaquePath(u *url.URL) string { | |
9 | if u.Opaque != "" { | |
10 | return u.Opaque | |
11 | } | |
12 | return u.Path | |
13 | } | |
14 | ||
15 | // Cleans the (absolute) URL path, removing unnecessary . and .. elements. See | |
16 | // "net/http".cleanPath. | |
17 | func CleanURLPath(p string) string { | |
18 | if p == "" { | |
19 | return "/" | |
20 | } | |
21 | if p[0] != '/' { | |
22 | p = "/" + p | |
23 | } | |
24 | cp := path.Clean(p) | |
25 | // Add the trailing slash back, as it's relevant to a URL. | |
26 | if p[len(p)-1] == '/' && cp != "/" { | |
27 | cp += "/" | |
28 | } | |
29 | return cp | |
30 | } | |
31 | ||
32 | func URLJoinSubPath(base, rel string) string { | |
33 | baseURL, err := url.Parse(base) | |
34 | if err != nil { | |
35 | // Honey badger doesn't give a fuck. | |
36 | panic(err) | |
37 | } | |
38 | rel = CleanURLPath(rel) | |
39 | baseURL.Path = path.Join(baseURL.Path, rel) | |
40 | return baseURL.String() | |
41 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "net/url" | |
4 | "testing" | |
5 | ||
6 | "github.com/stretchr/testify/assert" | |
7 | ) | |
8 | ||
9 | func TestURLOpaquePath(t *testing.T) { | |
10 | assert.Equal(t, "sqlite3://sqlite3.db", (&url.URL{Scheme: "sqlite3", Path: "sqlite3.db"}).String()) | |
11 | u, err := url.Parse("sqlite3:sqlite3.db") | |
12 | assert.NoError(t, err) | |
13 | assert.Equal(t, "sqlite3.db", URLOpaquePath(u)) | |
14 | assert.Equal(t, "sqlite3:sqlite3.db", (&url.URL{Scheme: "sqlite3", Opaque: "sqlite3.db"}).String()) | |
15 | assert.Equal(t, "sqlite3:/sqlite3.db", (&url.URL{Scheme: "sqlite3", Opaque: "/sqlite3.db"}).String()) | |
16 | u, err = url.Parse("sqlite3:/sqlite3.db") | |
17 | assert.NoError(t, err) | |
18 | assert.Equal(t, "/sqlite3.db", u.Path) | |
19 | assert.Equal(t, "/sqlite3.db", URLOpaquePath(u)) | |
20 | } |
0 | package missinggo | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "sync" | |
5 | ) | |
6 | ||
7 | func WaitEvents(l sync.Locker, evs ...*Event) { | |
8 | cases := make([]reflect.SelectCase, 0, len(evs)) | |
9 | for _, ev := range evs { | |
10 | cases = append(cases, reflect.SelectCase{ | |
11 | Dir: reflect.SelectRecv, | |
12 | Chan: reflect.ValueOf(ev.C()), | |
13 | }) | |
14 | } | |
15 | l.Unlock() | |
16 | reflect.Select(cases) | |
17 | l.Lock() | |
18 | } |