├── .circleci └── config.yml ├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── addr.go ├── atime.go ├── atime_atim.go ├── atime_atimespec.go ├── atime_js.go ├── atime_plan9.go ├── atime_test.go ├── atime_windows.go ├── bitmap ├── bitmap.go ├── bitmap_test.go ├── global.go └── iter.go ├── cache ├── cache.go ├── lru_policy.go └── policy.go ├── certdir.go ├── chancond.go ├── chans └── drain.go ├── cmd ├── filecache │ ├── main.go │ └── main_test.go ├── gd │ └── main.go ├── go-env │ └── main.go ├── http-file-server │ └── main.go ├── jenny │ └── main.go ├── nop │ └── main.go ├── query-escape │ └── main.go └── query-unescape │ └── main.go ├── conntrack ├── conntrack.go ├── conntrack_test.go ├── entryhandle.go └── instance.go ├── container └── xheap │ └── xheap.go ├── copy.go ├── copy_test.go ├── croak.go ├── ctrlflow └── ctrlflow.go ├── doc.go ├── docopt └── docopt.go ├── empty-value_go118.go ├── empty_value.go ├── empty_value_test.go ├── encoding.go ├── event.go ├── event_synchronized.go ├── event_test.go ├── expect ├── assert.go └── assert_test.go ├── expvarIndentMap.go ├── filecache ├── cache.go ├── cache_test.go ├── file.go ├── itemstate.go ├── key.go ├── lru.go ├── lru_test.go ├── lruitems_test.go ├── policy.go ├── policy_test.go └── uniform.go ├── flag.go ├── futures ├── delayed.go ├── delayed_test.go ├── funcs.go └── future.go ├── go.mod ├── go.sum ├── hostmaybeport.go ├── hostmaybeport_test.go ├── hostport.go ├── httpfile ├── defaultfs.go ├── file.go ├── fs.go └── misc.go ├── httpmux └── httpmux.go ├── httpresponsestatus.go ├── httptoo ├── accept.go ├── bytes_content_range.go ├── bytes_content_range_test.go ├── client.go ├── fs.go ├── gzip.go ├── gzip_test.go ├── headers.go ├── headers_test.go ├── httptoo.go ├── inproc_roundtrip.go ├── request.go ├── reverse_proxy.go ├── url.go └── url_test.go ├── inproc └── inproc.go ├── ioutil.go ├── ipport.go ├── iter ├── chain.go ├── func.go ├── groupby.go ├── groupby_test.go ├── head.go ├── iterable.go ├── iterator.go ├── iterator_test.go ├── iterutils.go └── n.go ├── jitter.go ├── jitter_test.go ├── leaktest └── goleaktest.go ├── limitlen.go ├── mime └── mime.go ├── minmax.go ├── monotonic.go ├── monotonic_test.go ├── multiless.go ├── net.go ├── oauth ├── endpoints.go ├── oauth.go └── oauth_test.go ├── openflags.go ├── orderedmap ├── google_btree.go ├── orderedmap.go ├── orderedmap_test.go └── skiplist.go ├── panicif ├── panicif.go └── panicif_test.go ├── path.go ├── path_test.go ├── patreon ├── patreon.go ├── patreon_test.go └── testdata │ └── pledges ├── perf ├── event.go ├── events.go ├── go.mod ├── go.sum ├── mutex.go ├── perf_test.go ├── scope.go └── timer.go ├── pproffd └── pproffd.go ├── prioritybitmap ├── mapset.go ├── prioritybitmap.go └── prioritybitmap_test.go ├── prometheus ├── expvar.go └── expvar_test.go ├── pubsub ├── pubsub.go └── pubsub_test.go ├── reader_context.go ├── refclose ├── refclose.go └── refclose_test.go ├── reqctx ├── lazy.go ├── reqctx.go └── value.go ├── resource ├── http.go ├── osfile.go ├── provider.go └── resource.go ├── rle.go ├── rle_test.go ├── runid ├── crawshaw │ └── crawshaw-runid.go ├── sqlite3.go └── zombiezen │ └── zombiezen-runid.go ├── section_read_seeker.go ├── section_read_seeker_test.go ├── section_writer.go ├── selfcert.go ├── singleflight.go ├── slicepool └── slicepool.go ├── slices ├── cast.go ├── cast_test.go ├── doc.go ├── filter.go ├── map.go ├── map_test.go ├── sort.go ├── sort_test.go └── sorter.go ├── sqlite.go ├── stack.go ├── strbool.go ├── strbool_test.go ├── strcase.go ├── sync.go ├── testing.go ├── testing_test.go ├── timer.go ├── timer_test.go ├── tls.go ├── units.go ├── url.go ├── url_test.go ├── wait_event.go └── x └── panic.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | machine: true 5 | environment: 6 | GO_BRANCH: release-branch.go1.13 7 | steps: 8 | - run: echo $CIRCLE_WORKING_DIRECTORY 9 | - run: echo $PWD 10 | - run: echo $GOPATH 11 | - run: echo 'export GOPATH=$HOME/go' >> $BASH_ENV 12 | - run: echo 'export PATH="$GOPATH/bin:$PATH"' >> $BASH_ENV 13 | - run: echo $GOPATH 14 | - run: which go 15 | - run: go version 16 | - run: | 17 | cd /usr/local 18 | sudo mkdir go.local 19 | sudo chown `whoami` go.local 20 | - restore_cache: 21 | key: go-local- 22 | - run: | 23 | cd /usr/local 24 | git clone git://github.com/golang/go go.local || true 25 | cd go.local 26 | git fetch 27 | git checkout "$GO_BRANCH" 28 | [[ -x bin/go && `git rev-parse HEAD` == `cat anacrolix.built` ]] && exit 29 | cd src 30 | ./make.bash || exit 31 | git rev-parse HEAD > ../anacrolix.built 32 | - save_cache: 33 | paths: /usr/local/go.local 34 | key: go-local-{{ checksum "/usr/local/go.local/anacrolix.built" }} 35 | - run: echo 'export PATH="/usr/local/go.local/bin:$PATH"' >> $BASH_ENV 36 | - run: go version 37 | - checkout 38 | - restore_cache: 39 | keys: 40 | - go-pkg- 41 | - restore_cache: 42 | keys: 43 | - go-cache- 44 | - run: go get -d ./... 45 | - run: go test -bench . -count 2 ./... -v -race 46 | - run: go test -bench . -count 2 ./... 47 | - run: GOARCH=386 go test -bench . -count 2 ./... -v 48 | - save_cache: 49 | key: go-pkg-{{ checksum "go.mod" }} 50 | paths: 51 | - ~/go/pkg 52 | - save_cache: 53 | key: go-cache-{{ .Revision }} 54 | paths: 55 | - ~/.cache/go-build 56 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matt Joiner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # missinggo 2 | [![GoDoc](https://godoc.org/github.com/anacrolix/missinggo?status.svg)](https://godoc.org/github.com/anacrolix/missinggo) 3 | 4 | Stuff that supplements Go's stdlib, or isn't significant enough to be in its own repo. 5 | -------------------------------------------------------------------------------- /addr.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | // Extracts the port as an integer from an address string. 9 | func AddrPort(addr net.Addr) int { 10 | switch raw := addr.(type) { 11 | case *net.UDPAddr: 12 | return raw.Port 13 | case *net.TCPAddr: 14 | return raw.Port 15 | default: 16 | _, port, err := net.SplitHostPort(addr.String()) 17 | if err != nil { 18 | panic(err) 19 | } 20 | i64, err := strconv.ParseInt(port, 0, 0) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return int(i64) 25 | } 26 | } 27 | 28 | func AddrIP(addr net.Addr) net.IP { 29 | if addr == nil { 30 | return nil 31 | } 32 | switch raw := addr.(type) { 33 | case *net.UDPAddr: 34 | return raw.IP 35 | case *net.TCPAddr: 36 | return raw.IP 37 | default: 38 | host, _, err := net.SplitHostPort(addr.String()) 39 | if err != nil { 40 | panic(err) 41 | } 42 | return net.ParseIP(host) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /atime.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | "time" 6 | ) 7 | 8 | // Extracts the access time from the FileInfo internals. 9 | func FileInfoAccessTime(fi os.FileInfo) time.Time { 10 | return fileInfoAccessTime(fi) 11 | } 12 | -------------------------------------------------------------------------------- /atime_atim.go: -------------------------------------------------------------------------------- 1 | //go:build linux || dragonfly || openbsd || solaris 2 | // +build linux dragonfly openbsd solaris 3 | 4 | package missinggo 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func fileInfoAccessTime(fi os.FileInfo) time.Time { 13 | ts := fi.Sys().(*syscall.Stat_t).Atim 14 | return time.Unix(int64(ts.Sec), int64(ts.Nsec)) 15 | } 16 | -------------------------------------------------------------------------------- /atime_atimespec.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || freebsd || netbsd 2 | // +build darwin freebsd netbsd 3 | 4 | package missinggo 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func fileInfoAccessTime(fi os.FileInfo) time.Time { 13 | ts := fi.Sys().(*syscall.Stat_t).Atimespec 14 | return time.Unix(int64(ts.Sec), int64(ts.Nsec)) 15 | } 16 | -------------------------------------------------------------------------------- /atime_js.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | "time" 7 | ) 8 | 9 | func fileInfoAccessTime(fi os.FileInfo) time.Time { 10 | sys := fi.Sys().(*syscall.Stat_t) 11 | return time.Unix(sys.Atime, sys.AtimeNsec) 12 | } 13 | -------------------------------------------------------------------------------- /atime_plan9.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | "time" 7 | ) 8 | 9 | func fileInfoAccessTime(fi os.FileInfo) time.Time { 10 | sec := fi.Sys().(*syscall.Dir).Atime 11 | return time.Unix(int64(sec), 0) 12 | } 13 | -------------------------------------------------------------------------------- /atime_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestFileInfoAccessTime(t *testing.T) { 13 | f, err := ioutil.TempFile("", "") 14 | require.NoError(t, err) 15 | assert.NoError(t, f.Close()) 16 | name := f.Name() 17 | t.Log(name) 18 | defer func() { 19 | err := os.Remove(name) 20 | if err != nil { 21 | t.Log(err) 22 | } 23 | }() 24 | fi, err := os.Stat(name) 25 | require.NoError(t, err) 26 | t.Log(FileInfoAccessTime(fi)) 27 | } 28 | -------------------------------------------------------------------------------- /atime_windows.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | "time" 7 | ) 8 | 9 | func fileInfoAccessTime(fi os.FileInfo) time.Time { 10 | ts := fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime 11 | return time.Unix(0, int64(ts.Nanoseconds())) 12 | } 13 | -------------------------------------------------------------------------------- /bitmap/bitmap.go: -------------------------------------------------------------------------------- 1 | // Package bitmap provides a []bool/bitmap implementation with standardized 2 | // iteration. Bitmaps are the equivalent of []bool, with improved compression 3 | // for runs of similar values, and faster operations on ranges and the like. 4 | package bitmap 5 | 6 | import ( 7 | "github.com/RoaringBitmap/roaring" 8 | "github.com/anacrolix/missinggo/iter" 9 | ) 10 | 11 | type ( 12 | BitIndex = uint32 13 | BitRange = uint64 14 | ) 15 | 16 | type Interface interface { 17 | Len() int 18 | } 19 | 20 | // Bitmaps store the existence of values in [0,math.MaxUint32] more 21 | // efficiently than []bool. The empty value starts with no bits set. 22 | type Bitmap struct { 23 | RB *roaring.Bitmap 24 | } 25 | 26 | const ( 27 | MaxInt BitIndex = roaring.MaxUint32 28 | ToEnd BitRange = roaring.MaxRange 29 | ) 30 | 31 | // The number of set bits in the bitmap. Also known as cardinality. 32 | func (me Bitmap) Len() BitRange { 33 | if me.RB == nil { 34 | return 0 35 | } 36 | return me.RB.GetCardinality() 37 | } 38 | 39 | func (me Bitmap) ToSortedSlice() []BitIndex { 40 | if me.RB == nil { 41 | return nil 42 | } 43 | return me.RB.ToArray() 44 | } 45 | 46 | func (me *Bitmap) lazyRB() *roaring.Bitmap { 47 | if me.RB == nil { 48 | me.RB = roaring.NewBitmap() 49 | } 50 | return me.RB 51 | } 52 | 53 | func (me Bitmap) Iter(cb iter.Callback) { 54 | me.IterTyped(func(i int) bool { 55 | return cb(i) 56 | }) 57 | } 58 | 59 | // Returns true if all values were traversed without early termination. 60 | func (me Bitmap) IterTyped(f func(int) bool) bool { 61 | if me.RB == nil { 62 | return true 63 | } 64 | it := me.RB.Iterator() 65 | for it.HasNext() { 66 | if !f(int(it.Next())) { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | func checkInt(i BitIndex) { 74 | // Nothing to do if BitIndex is uint32, as this matches what roaring can handle. 75 | } 76 | 77 | func (me *Bitmap) Add(is ...BitIndex) { 78 | rb := me.lazyRB() 79 | for _, i := range is { 80 | checkInt(i) 81 | rb.Add(i) 82 | } 83 | } 84 | 85 | func (me *Bitmap) AddRange(begin, end BitRange) { 86 | // Filter here so we don't prematurely create a bitmap before having roaring do this check 87 | // anyway. 88 | if begin >= end { 89 | return 90 | } 91 | me.lazyRB().AddRange(begin, end) 92 | } 93 | 94 | func (me *Bitmap) Remove(i BitIndex) bool { 95 | if me.RB == nil { 96 | return false 97 | } 98 | return me.RB.CheckedRemove(uint32(i)) 99 | } 100 | 101 | func (me *Bitmap) Union(other Bitmap) { 102 | me.lazyRB().Or(other.lazyRB()) 103 | } 104 | 105 | func (me Bitmap) Contains(i BitIndex) bool { 106 | if me.RB == nil { 107 | return false 108 | } 109 | return me.RB.Contains(i) 110 | } 111 | 112 | func (me *Bitmap) Sub(other Bitmap) { 113 | if other.RB == nil { 114 | return 115 | } 116 | if me.RB == nil { 117 | return 118 | } 119 | me.RB.AndNot(other.RB) 120 | } 121 | 122 | func (me *Bitmap) Clear() { 123 | if me.RB == nil { 124 | return 125 | } 126 | me.RB.Clear() 127 | } 128 | 129 | func (me Bitmap) Copy() (ret Bitmap) { 130 | ret = me 131 | if ret.RB != nil { 132 | ret.RB = ret.RB.Clone() 133 | } 134 | return 135 | } 136 | 137 | func (me *Bitmap) FlipRange(begin, end BitRange) { 138 | me.lazyRB().Flip(begin, end) 139 | } 140 | 141 | func (me Bitmap) Get(bit BitIndex) bool { 142 | return me.RB != nil && me.RB.Contains(bit) 143 | } 144 | 145 | func (me *Bitmap) Set(bit BitIndex, value bool) { 146 | if value { 147 | me.lazyRB().Add(bit) 148 | } else { 149 | if me.RB != nil { 150 | me.RB.Remove(bit) 151 | } 152 | } 153 | } 154 | 155 | func (me *Bitmap) RemoveRange(begin, end BitRange) *Bitmap { 156 | if me.RB == nil { 157 | return me 158 | } 159 | me.RB.RemoveRange(begin, end) 160 | return me 161 | } 162 | 163 | func (me Bitmap) IsEmpty() bool { 164 | return me.RB == nil || me.RB.IsEmpty() 165 | } 166 | -------------------------------------------------------------------------------- /bitmap/bitmap_test.go: -------------------------------------------------------------------------------- 1 | package bitmap 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/RoaringBitmap/roaring" 8 | "github.com/anacrolix/missinggo/iter" 9 | "github.com/anacrolix/missinggo/slices" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 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, []BitIndex{4, 5}, Sub(left, right).ToSortedSlice()) 51 | assert.Equal(t, []BitIndex{3, 6}, Sub(right, left).ToSortedSlice()) 52 | } 53 | 54 | func TestSubUninited(t *testing.T) { 55 | var left, right Bitmap 56 | assert.Empty(t, 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, []BitIndex{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, []BitIndex{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, ToEnd) 81 | assert.EqualValues(t, 9, bm.Len()) 82 | bm.RemoveRange(2, 5) 83 | assert.EqualValues(t, 7, bm.Len()) 84 | bm.RemoveRange(10, ToEnd) 85 | assert.EqualValues(t, 5, bm.Len()) 86 | } 87 | 88 | func TestLimits(t *testing.T) { 89 | var bm Bitmap 90 | 91 | // We can't reliably test out of bounds for systems where int is only 32-bit. Rather than guess 92 | // for every possible GOARCH, I'll just skip the test here. The BitIndex/int wrapper around 93 | // roaring's types are bad anyway. See https://github.com/anacrolix/missinggo/issues/16. 94 | 95 | //assert.Panics(t, func() { bm.Add(math.MaxInt64) }) 96 | 97 | bm.Add(MaxInt) 98 | assert.EqualValues(t, 1, bm.Len()) 99 | assert.EqualValues(t, []BitIndex{MaxInt}, bm.ToSortedSlice()) 100 | } 101 | 102 | func TestRoaringRangeEnd(t *testing.T) { 103 | r := roaring.New() 104 | r.Add(roaring.MaxUint32) 105 | require.EqualValues(t, 1, r.GetCardinality()) 106 | r.RemoveRange(0, roaring.MaxUint32) 107 | assert.EqualValues(t, 1, r.GetCardinality()) 108 | r.RemoveRange(0, math.MaxUint64) 109 | assert.EqualValues(t, 0, r.GetCardinality()) 110 | } 111 | -------------------------------------------------------------------------------- /bitmap/global.go: -------------------------------------------------------------------------------- 1 | package bitmap 2 | 3 | import "github.com/RoaringBitmap/roaring" 4 | 5 | func Sub(left, right Bitmap) Bitmap { 6 | return Bitmap{ 7 | RB: roaring.AndNot(left.lazyRB(), right.lazyRB()), 8 | } 9 | } 10 | 11 | func Flip(bm Bitmap, start, end BitRange) Bitmap { 12 | return Bitmap{ 13 | RB: roaring.Flip(bm.lazyRB(), start, end), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bitmap/iter.go: -------------------------------------------------------------------------------- 1 | package bitmap 2 | 3 | import "github.com/RoaringBitmap/roaring" 4 | 5 | type Iter struct { 6 | ii roaring.IntIterable 7 | } 8 | 9 | func (me *Iter) Next() bool { 10 | if me == nil { 11 | return false 12 | } 13 | return me.ii.HasNext() 14 | } 15 | 16 | func (me *Iter) Value() interface{} { 17 | return me.ValueInt() 18 | } 19 | 20 | func (me *Iter) ValueInt() int { 21 | return int(me.ii.Next()) 22 | } 23 | 24 | func (me *Iter) Stop() {} 25 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | 8 | humanize "github.com/dustin/go-humanize" 9 | ) 10 | 11 | type Key = string 12 | 13 | type Cache struct { 14 | mu sync.Mutex 15 | filled int64 16 | Policy Policy 17 | Items map[Key]ItemMeta 18 | } 19 | 20 | type ItemMeta struct { 21 | Size int64 22 | CanEvict bool 23 | Usage 24 | } 25 | 26 | type Item struct { 27 | Key 28 | ItemMeta 29 | } 30 | 31 | func (me *Cache) Remove(k Key) { 32 | me.mu.Lock() 33 | i := me.Items[k] 34 | me.filled -= i.Size 35 | delete(me.Items, k) 36 | me.Policy.Forget(k) 37 | me.mu.Unlock() 38 | } 39 | 40 | func (me *Cache) Update(i Item) { 41 | me.mu.Lock() 42 | m := me.Items[i.Key] 43 | me.filled -= m.Size 44 | me.filled += i.Size 45 | if me.Items == nil { 46 | me.Items = make(map[Key]ItemMeta) 47 | } 48 | me.Items[i.Key] = i.ItemMeta 49 | if i.CanEvict { 50 | me.Policy.Update(i.Key, i.Usage) 51 | } else { 52 | me.Policy.Forget(i.Key) 53 | } 54 | me.mu.Unlock() 55 | } 56 | 57 | func (me *Cache) logState() { 58 | log.Print(me) 59 | } 60 | 61 | func (me *Cache) String() string { 62 | me.mu.Lock() 63 | defer me.mu.Unlock() 64 | return fmt.Sprintf( 65 | "%p: %d items, %v bytes used, lru: %s", 66 | me, len(me.Items), humanize.Bytes(uint64(me.filled)), 67 | func() string { 68 | k, ok := me.Policy.Candidate() 69 | if ok { 70 | i := me.Items[k] 71 | return fmt.Sprintf( 72 | "%q (%v: %v)", 73 | k, humanize.Bytes(uint64(i.Size)), i.Usage) 74 | } 75 | return "none" 76 | }(), 77 | ) 78 | } 79 | 80 | func (me *Cache) Used() int64 { 81 | return me.filled 82 | } 83 | 84 | func (me *Cache) NumItems() int { 85 | return len(me.Items) 86 | } 87 | 88 | func (me *Cache) Clear() { 89 | for k := range me.Items { 90 | delete(me.Items, k) 91 | me.Policy.Forget(k) 92 | } 93 | me.Items = nil 94 | me.filled = 0 95 | } 96 | 97 | func (me *Cache) Filled() int64 { 98 | me.mu.Lock() 99 | defer me.mu.Unlock() 100 | return me.filled 101 | } 102 | 103 | func (me *Cache) Candidate() (Item, bool) { 104 | me.mu.Lock() 105 | defer me.mu.Unlock() 106 | k, ok := me.Policy.Candidate() 107 | return Item{ 108 | Key: k, 109 | ItemMeta: me.Items[k], 110 | }, ok 111 | } 112 | -------------------------------------------------------------------------------- /cache/lru_policy.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/anacrolix/missinggo/orderedmap" 7 | 8 | "github.com/anacrolix/missinggo/v2" 9 | ) 10 | 11 | type LruPolicy struct { 12 | mu sync.RWMutex 13 | sorted orderedmap.OrderedMap 14 | keys map[Key]Usage 15 | } 16 | 17 | type lruItem struct { 18 | Key 19 | Usage 20 | } 21 | 22 | var _ Policy = (*LruPolicy)(nil) 23 | 24 | func (me *LruPolicy) Candidate() (k Key, ok bool) { 25 | me.mu.RLock() 26 | defer me.mu.RUnlock() 27 | if me.sorted == nil { 28 | return 29 | } 30 | me.sorted.Iter(func(i interface{}) bool { 31 | k = i.(lruItem).Key 32 | ok = true 33 | return false 34 | }) 35 | return 36 | } 37 | 38 | func (me *LruPolicy) Forget(k Key) { 39 | me.mu.Lock() 40 | defer me.mu.Unlock() 41 | u, ok := me.keys[k] 42 | if !ok { 43 | return 44 | } 45 | me.sorted.Unset(lruItem{k, u}) 46 | delete(me.keys, k) 47 | } 48 | 49 | func (me *LruPolicy) NumItems() int { 50 | return len(me.keys) 51 | } 52 | 53 | func (me *LruPolicy) Update(k Key, u Usage) { 54 | me.mu.Lock() 55 | defer me.mu.Unlock() 56 | if me.sorted == nil { 57 | me.sorted = orderedmap.New(func(l, r interface{}) bool { 58 | _l := l.(lruItem) 59 | _r := r.(lruItem) 60 | var ml missinggo.MultiLess 61 | ml.NextBool(_l.Usage.Less(_r.Usage), _r.Usage.Less(_l.Usage)) 62 | ml.StrictNext(_l.Key == _r.Key, _l.Key < _r.Key) 63 | return ml.Less() 64 | }) 65 | } 66 | if u, ok := me.keys[k]; ok { 67 | me.sorted.Unset(lruItem{k, u}) 68 | } 69 | me.sorted.Set(lruItem{k, u}, struct{}{}) 70 | if me.keys == nil { 71 | me.keys = make(map[Key]Usage) 72 | } 73 | me.keys[k] = u 74 | } 75 | -------------------------------------------------------------------------------- /cache/policy.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | type Usage interface { 4 | Less(Usage) bool 5 | } 6 | 7 | type Policy interface { 8 | Candidate() (Key, bool) 9 | Update(Key, Usage) 10 | Forget(Key) 11 | NumItems() int 12 | } 13 | -------------------------------------------------------------------------------- /certdir.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func LoadCertificateDir(dir string) (certs []tls.Certificate, err error) { 12 | d, err := os.Open(dir) 13 | if err != nil { 14 | return 15 | } 16 | defer d.Close() 17 | const defaultPEMFile = "default.pem" 18 | if p := filepath.Join(dir, defaultPEMFile); FilePathExists(p) { 19 | cert, err := tls.LoadX509KeyPair(p, p) 20 | if err == nil { 21 | certs = append(certs, cert) 22 | } else { 23 | log.Printf("error loading default certicate: %s", err) 24 | } 25 | } 26 | files, err := d.Readdir(-1) 27 | if err != nil { 28 | return 29 | } 30 | for _, f := range files { 31 | if f.Name() == defaultPEMFile { 32 | continue 33 | } 34 | if !strings.HasSuffix(f.Name(), ".pem") { 35 | continue 36 | } 37 | p := filepath.Join(dir, f.Name()) 38 | cert, err := tls.LoadX509KeyPair(p, p) 39 | if err != nil { 40 | log.Printf("error loading key pair from %q: %s", p, err) 41 | continue 42 | } 43 | certs = append(certs, cert) 44 | } 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /chancond.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "sync" 4 | 5 | type ChanCond struct { 6 | mu sync.Mutex 7 | ch chan struct{} 8 | } 9 | 10 | func (me *ChanCond) Wait() <-chan struct{} { 11 | me.mu.Lock() 12 | defer me.mu.Unlock() 13 | if me.ch == nil { 14 | me.ch = make(chan struct{}) 15 | } 16 | return me.ch 17 | } 18 | 19 | func (me *ChanCond) Signal() { 20 | me.mu.Lock() 21 | defer me.mu.Unlock() 22 | select { 23 | case me.ch <- struct{}{}: 24 | default: 25 | } 26 | } 27 | 28 | func (me *ChanCond) Broadcast() { 29 | me.mu.Lock() 30 | defer me.mu.Unlock() 31 | if me.ch == nil { 32 | return 33 | } 34 | close(me.ch) 35 | me.ch = nil 36 | } 37 | -------------------------------------------------------------------------------- /chans/drain.go: -------------------------------------------------------------------------------- 1 | package chans 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Receives from any channel until it's closed. 8 | func Drain(ch interface{}) { 9 | chValue := reflect.ValueOf(ch) 10 | for { 11 | _, ok := chValue.Recv() 12 | if !ok { 13 | break 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cmd/filecache/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "regexp" 11 | "strconv" 12 | 13 | _ "github.com/anacrolix/envpprof" 14 | "github.com/anacrolix/missinggo/filecache" 15 | "github.com/anacrolix/tagflag" 16 | "github.com/dustin/go-humanize" 17 | 18 | "github.com/anacrolix/missinggo/v2" 19 | ) 20 | 21 | var c *filecache.Cache 22 | 23 | func handleNewData(w http.ResponseWriter, path string, offset int64, r io.Reader) (served bool) { 24 | f, err := c.OpenFile(path, os.O_CREATE|os.O_WRONLY) 25 | if err != nil { 26 | log.Print(err) 27 | http.Error(w, "couldn't open file", http.StatusInternalServerError) 28 | return true 29 | } 30 | defer f.Close() 31 | f.Seek(offset, os.SEEK_SET) 32 | _, err = io.Copy(f, r) 33 | if err != nil { 34 | log.Print(err) 35 | c.Remove(path) 36 | http.Error(w, "didn't complete", http.StatusInternalServerError) 37 | return true 38 | } 39 | return 40 | } 41 | 42 | // Parses out the first byte from a Content-Range header. Returns 0 if it 43 | // isn't found, which is what is implied if there is no header. 44 | func parseContentRangeFirstByte(s string) int64 { 45 | matches := regexp.MustCompile(`(\d+)-`).FindStringSubmatch(s) 46 | if matches == nil { 47 | return 0 48 | } 49 | ret, _ := strconv.ParseInt(matches[1], 0, 64) 50 | return ret 51 | } 52 | 53 | func handleDelete(w http.ResponseWriter, path string) { 54 | err := c.Remove(path) 55 | if err != nil { 56 | log.Print(err) 57 | http.Error(w, "didn't work", http.StatusInternalServerError) 58 | return 59 | } 60 | } 61 | 62 | func main() { 63 | log.SetFlags(log.Flags() | log.Lshortfile) 64 | args := struct { 65 | Capacity tagflag.Bytes `short:"c"` 66 | Addr string 67 | }{ 68 | Capacity: -1, 69 | Addr: "localhost:2076", 70 | } 71 | tagflag.Parse(&args) 72 | root, err := os.Getwd() 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | log.Printf("cache root at %q", root) 77 | c, err = filecache.NewCache(root) 78 | if err != nil { 79 | log.Fatalf("error creating cache: %s", err) 80 | } 81 | if args.Capacity < 0 { 82 | log.Printf("no capacity set, no evictions will occur") 83 | } else { 84 | c.SetCapacity(args.Capacity.Int64()) 85 | log.Printf("setting capacity to %s bytes", humanize.Comma(args.Capacity.Int64())) 86 | } 87 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 88 | p := r.URL.Path[1:] 89 | switch r.Method { 90 | case "DELETE": 91 | log.Printf("%s %s", r.Method, r.RequestURI) 92 | handleDelete(w, p) 93 | return 94 | case "PUT", "PATCH", "POST": 95 | contentRange := r.Header.Get("Content-Range") 96 | firstByte := parseContentRangeFirstByte(contentRange) 97 | log.Printf("%s (%d-) %s", r.Method, firstByte, r.RequestURI) 98 | handleNewData(w, p, firstByte, r.Body) 99 | return 100 | } 101 | log.Printf("%s %s %s", r.Method, r.Header.Get("Range"), r.RequestURI) 102 | f, err := c.OpenFile(p, os.O_RDONLY) 103 | if os.IsNotExist(err) { 104 | http.NotFound(w, r) 105 | return 106 | } 107 | if err != nil { 108 | log.Printf("couldn't open requested file: %s", err) 109 | http.Error(w, "couldn't open file", http.StatusInternalServerError) 110 | return 111 | } 112 | defer func() { 113 | go f.Close() 114 | }() 115 | info, _ := f.Stat() 116 | w.Header().Set("Content-Range", fmt.Sprintf("*/%d", info.Size())) 117 | http.ServeContent(w, r, p, info.ModTime(), f) 118 | }) 119 | http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { 120 | info := c.Info() 121 | fmt.Fprintf(w, "Capacity: %d\n", info.Capacity) 122 | fmt.Fprintf(w, "Current Size: %d\n", info.Filled) 123 | fmt.Fprintf(w, "Item Count: %d\n", info.NumItems) 124 | }) 125 | http.HandleFunc("/lru", func(w http.ResponseWriter, r *http.Request) { 126 | c.WalkItems(func(item filecache.ItemInfo) { 127 | fmt.Fprintf(w, "%s\t%d\t%s\n", item.Accessed, item.Size, item.Path) 128 | }) 129 | }) 130 | cert, err := missinggo.NewSelfSignedCertificate() 131 | if err != nil { 132 | log.Fatal(err) 133 | } 134 | srv := http.Server{ 135 | Addr: args.Addr, 136 | TLSConfig: &tls.Config{ 137 | Certificates: []tls.Certificate{cert}, 138 | }, 139 | } 140 | log.Fatal(srv.ListenAndServeTLS("", "")) 141 | } 142 | -------------------------------------------------------------------------------- /cmd/filecache/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFailedPutDeletesFile(t *testing.T) { 8 | // TODO 9 | } 10 | -------------------------------------------------------------------------------- /cmd/gd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base32" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/url" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | func escape(encoding string) { 15 | switch { 16 | case strings.HasPrefix("query", encoding): 17 | b, err := ioutil.ReadAll(os.Stdin) 18 | if err != nil { 19 | fmt.Fprintln(os.Stderr, err) 20 | os.Exit(1) 21 | } 22 | os.Stdout.Write([]byte(url.QueryEscape(string(b)))) 23 | case strings.HasPrefix("hex", encoding): 24 | b, err := ioutil.ReadAll(os.Stdin) 25 | if err != nil { 26 | fmt.Fprintln(os.Stderr, err) 27 | os.Exit(1) 28 | } 29 | os.Stdout.Write([]byte(hex.EncodeToString(b))) 30 | default: 31 | fmt.Fprintf(os.Stderr, "unknown escape encoding: %q\n", encoding) 32 | os.Exit(2) 33 | } 34 | } 35 | 36 | func unescape(encoding string) { 37 | switch { 38 | case strings.HasPrefix("query", encoding): 39 | b, err := ioutil.ReadAll(os.Stdin) 40 | if err != nil { 41 | fmt.Fprintln(os.Stderr, err) 42 | os.Exit(1) 43 | } 44 | s, err := url.QueryUnescape(string(b)) 45 | if err != nil { 46 | fmt.Fprintln(os.Stderr, err) 47 | os.Exit(1) 48 | } 49 | os.Stdout.Write([]byte(s)) 50 | case strings.HasPrefix("b32", encoding): 51 | d := base32.NewDecoder(base32.StdEncoding, os.Stdin) 52 | io.Copy(os.Stdout, d) 53 | default: 54 | fmt.Fprintf(os.Stderr, "unknown unescape encoding: %q\n", encoding) 55 | } 56 | } 57 | 58 | func main() { 59 | if len(os.Args) != 3 { 60 | fmt.Fprintf(os.Stderr, "expected two arguments: : got %d\n", len(os.Args)-1) 61 | os.Exit(2) 62 | } 63 | mode := os.Args[1] 64 | switch { 65 | case strings.HasPrefix("escape", mode): 66 | escape(os.Args[2]) 67 | case strings.HasPrefix("unescape", mode) || strings.HasPrefix("decode", mode): 68 | unescape(os.Args[2]) 69 | default: 70 | fmt.Fprintf(os.Stderr, "unknown mode: %q\n", mode) 71 | os.Exit(2) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmd/go-env/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | for _, v := range os.Environ() { 10 | fmt.Printf("%s\n", v) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cmd/http-file-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/anacrolix/tagflag" 10 | ) 11 | 12 | func setIfGetHeader(w http.ResponseWriter, r *http.Request, set, get string) { 13 | h := r.Header.Get(get) 14 | if h == "" { 15 | return 16 | } 17 | w.Header().Set(set, h) 18 | } 19 | 20 | func allowCORS(h http.Handler) http.Handler { 21 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | if origin := r.Header.Get("Origin"); origin != "" { 23 | w.Header().Set("Access-Control-Allow-Origin", origin) 24 | w.Header().Set("Access-Control-Allow-Credentials", "true") 25 | setIfGetHeader(w, r, "Access-Control-Allow-Methods", "Access-Control-Request-Method") 26 | setIfGetHeader(w, r, "Access-Control-Allow-Headers", "Access-Control-Request-Headers") 27 | w.Header().Set("Access-Control-Expose-Headers", "Content-Type") 28 | } 29 | h.ServeHTTP(w, r) 30 | }) 31 | } 32 | 33 | func main() { 34 | var flags = struct { 35 | Addr string 36 | }{ 37 | Addr: "localhost:8080", 38 | } 39 | tagflag.Parse(&flags) 40 | dir, err := os.Getwd() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | l, err := net.Listen("tcp", flags.Addr) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | defer l.Close() 49 | addr := l.Addr() 50 | log.Printf("serving %q at %s", dir, addr) 51 | log.Fatal(http.Serve(l, allowCORS(http.FileServer(http.Dir(dir))))) 52 | } 53 | -------------------------------------------------------------------------------- /cmd/jenny/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "html/template" 7 | "io" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | t, err := template.ParseFiles(os.Args[1]) 13 | if err != nil { 14 | panic(err) 15 | } 16 | var buf bytes.Buffer 17 | err = t.Execute(&buf, os.Args[3:]) 18 | if err != nil { 19 | panic(err) 20 | } 21 | b, err := format.Source(buf.Bytes()) 22 | if err != nil { 23 | io.Copy(os.Stdout, &buf) 24 | panic(err) 25 | } 26 | f, err := os.Create(os.Args[2]) 27 | if err != nil { 28 | panic(err) 29 | } 30 | f.Write(b) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/nop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /cmd/query-escape/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | fmt.Println(url.QueryEscape(os.Args[1])) 11 | } 12 | -------------------------------------------------------------------------------- /cmd/query-unescape/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | fmt.Println(url.QueryUnescape(os.Args[1])) 11 | } 12 | -------------------------------------------------------------------------------- /conntrack/conntrack.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import "expvar" 4 | 5 | type Protocol = string 6 | 7 | type Endpoint = string 8 | 9 | type Entry struct { 10 | Protocol 11 | LocalAddr Endpoint 12 | RemoteAddr Endpoint 13 | } 14 | 15 | var expvars = expvar.NewMap("conntrack") 16 | -------------------------------------------------------------------------------- /conntrack/entryhandle.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 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 | } 15 | 16 | func (eh *EntryHandle) Done() { 17 | expvars.Add("entry handles done", 1) 18 | timeout := eh.timeout() 19 | eh.expires = time.Now().Add(timeout) 20 | if timeout <= 0 { 21 | eh.remove() 22 | } else { 23 | time.AfterFunc(eh.timeout(), eh.remove) 24 | } 25 | } 26 | 27 | func (eh *EntryHandle) Forget() { 28 | expvars.Add("entry handles forgotten", 1) 29 | eh.remove() 30 | } 31 | 32 | func (eh *EntryHandle) remove() { 33 | eh.i.remove(eh) 34 | } 35 | 36 | func (eh *EntryHandle) timeout() time.Duration { 37 | return eh.i.Timeout(eh.e) 38 | } 39 | -------------------------------------------------------------------------------- /container/xheap/xheap.go: -------------------------------------------------------------------------------- 1 | package xheap 2 | 3 | import ( 4 | "container/heap" 5 | "sort" 6 | ) 7 | 8 | type pushPopper interface { 9 | Push(interface{}) 10 | Pop() interface{} 11 | } 12 | 13 | func Flipped(h heap.Interface) heap.Interface { 14 | return struct { 15 | sort.Interface 16 | pushPopper 17 | }{ 18 | sort.Reverse(h), 19 | h, 20 | } 21 | } 22 | 23 | // type top struct { 24 | // k int 25 | // heap.Interface 26 | // } 27 | 28 | // func (me top) Push(x interface{}) { 29 | // heap.Push(me.Interface, x) 30 | // if me.Len() > me.k { 31 | // heap.Pop(me) 32 | // } 33 | // } 34 | 35 | // func Bounded(k int, h heap.Interface) heap.Interface { 36 | // return top{k, Flipped(h)} 37 | // } 38 | 39 | type slice struct { 40 | Slice *[]interface{} 41 | Lesser func(l, r interface{}) bool 42 | } 43 | 44 | func (me slice) Len() int { return len(*me.Slice) } 45 | 46 | func (me slice) Less(i, j int) bool { 47 | return me.Lesser((*me.Slice)[i], (*me.Slice)[j]) 48 | } 49 | 50 | func (me slice) Pop() (ret interface{}) { 51 | i := me.Len() - 1 52 | ret = (*me.Slice)[i] 53 | *me.Slice = (*me.Slice)[:i] 54 | return 55 | } 56 | 57 | func (me slice) Push(x interface{}) { 58 | *me.Slice = append(*me.Slice, x) 59 | } 60 | 61 | func (me slice) Swap(i, j int) { 62 | sl := *me.Slice 63 | sl[i], sl[j] = sl[j], sl[i] 64 | } 65 | 66 | func Slice(sl *[]interface{}, lesser func(l, r interface{}) bool) heap.Interface { 67 | return slice{sl, lesser} 68 | } 69 | -------------------------------------------------------------------------------- /copy.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Copy elements from src to dst. Panics if the length of src and dst are 9 | // different. 10 | func CopyExact(dest interface{}, src interface{}) { 11 | dV := reflect.ValueOf(dest) 12 | sV := reflect.ValueOf(src) 13 | if dV.Kind() == reflect.Ptr { 14 | dV = dV.Elem() 15 | } 16 | if dV.Kind() == reflect.Array && !dV.CanAddr() { 17 | panic(fmt.Sprintf("dest not addressable: %T", dest)) 18 | } 19 | if sV.Kind() == reflect.Ptr { 20 | sV = sV.Elem() 21 | } 22 | if sV.Kind() == reflect.String { 23 | sV = sV.Convert(reflect.SliceOf(dV.Type().Elem())) 24 | } 25 | if !sV.IsValid() { 26 | panic("invalid source, probably nil") 27 | } 28 | if dV.Len() != sV.Len() { 29 | panic(fmt.Sprintf("dest len (%d) != src len (%d)", dV.Len(), sV.Len())) 30 | } 31 | if dV.Len() != reflect.Copy(dV, sV) { 32 | panic("dammit") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /copy_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestCopyToArray(t *testing.T) { 10 | var arr [3]byte 11 | bb := []byte{1, 2, 3} 12 | CopyExact(&arr, bb) 13 | if !bytes.Equal(arr[:], bb) { 14 | t.FailNow() 15 | } 16 | } 17 | 18 | func TestCopyToSlicedArray(t *testing.T) { 19 | var arr [5]byte 20 | CopyExact(arr[:], "hello") 21 | if !bytes.Equal(arr[:], []byte("hello")) { 22 | t.FailNow() 23 | } 24 | } 25 | 26 | func TestCopyDestNotAddr(t *testing.T) { 27 | defer func() { 28 | r := recover() 29 | if r == nil { 30 | t.FailNow() 31 | } 32 | t.Log(r) 33 | }() 34 | var arr [3]byte 35 | CopyExact(arr, "nope") 36 | } 37 | 38 | func TestCopyLenMismatch(t *testing.T) { 39 | defer func() { 40 | r := recover() 41 | if r == nil { 42 | t.FailNow() 43 | } 44 | t.Log(r) 45 | }() 46 | CopyExact(make([]byte, 2), "abc") 47 | } 48 | 49 | func TestCopySrcString(t *testing.T) { 50 | dest := make([]byte, 3) 51 | CopyExact(dest, "lol") 52 | if string(dest) != "lol" { 53 | t.FailNow() 54 | } 55 | func() { 56 | defer func() { 57 | r := recover() 58 | if r == nil { 59 | t.FailNow() 60 | } 61 | }() 62 | CopyExact(dest, "rofl") 63 | }() 64 | var arr [5]byte 65 | CopyExact(&arr, interface{}("hello")) 66 | if string(arr[:]) != "hello" { 67 | t.FailNow() 68 | } 69 | } 70 | 71 | func TestCopySrcNilInterface(t *testing.T) { 72 | var arr [3]byte 73 | defer func() { 74 | r := recover().(string) 75 | if !strings.Contains(r, "invalid source") { 76 | t.FailNow() 77 | } 78 | }() 79 | CopyExact(&arr, nil) 80 | } 81 | 82 | func TestCopySrcPtr(t *testing.T) { 83 | var bigDst [1024]byte 84 | var bigSrc [1024]byte = [1024]byte{'h', 'i'} 85 | CopyExact(&bigDst, &bigSrc) 86 | if !bytes.Equal(bigDst[:], bigSrc[:]) { 87 | t.FailNow() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /croak.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func Unchomp(s string) string { 9 | if len(s) > 0 && s[len(s)-1] == '\n' { 10 | return s 11 | } 12 | return s + "\n" 13 | } 14 | 15 | func Fatal(msg interface{}) { 16 | os.Stderr.WriteString(Unchomp(fmt.Sprint(msg))) 17 | os.Exit(1) 18 | } 19 | -------------------------------------------------------------------------------- /ctrlflow/ctrlflow.go: -------------------------------------------------------------------------------- 1 | package ctrlflow 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type valueWrapper struct { 8 | value interface{} 9 | } 10 | 11 | func (me valueWrapper) String() string { 12 | return fmt.Sprint(me.value) 13 | } 14 | 15 | func Panic(val interface{}) { 16 | panic(valueWrapper{val}) 17 | } 18 | 19 | func Recover(handler func(interface{}) bool) { 20 | r := recover() 21 | if r == nil { 22 | return 23 | } 24 | if vw, ok := r.(valueWrapper); ok { 25 | if handler(vw.value) { 26 | return 27 | } 28 | } 29 | panic(r) 30 | } 31 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package missinggo contains miscellaneous helpers used in many of anacrolix' 2 | // projects. 3 | package missinggo 4 | -------------------------------------------------------------------------------- /docopt/docopt.go: -------------------------------------------------------------------------------- 1 | package docopt 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/docopt/docopt-go" 8 | ) 9 | 10 | func Parse(doc string) (opts map[string]interface{}) { 11 | opts, err := docopt.Parse(doc, nil, true, "1.2.3", false, false) 12 | if ue, ok := err.(*docopt.UserError); ok { 13 | if ue.Error() != "" { 14 | fmt.Fprintf(os.Stderr, "\n%s\n", ue) 15 | } 16 | os.Exit(2) 17 | } 18 | if err != nil { 19 | fmt.Fprintf(os.Stderr, "error parsing docopt: %#v\n", err) 20 | os.Exit(1) 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /empty-value_go118.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | func IsZeroValue[T comparable](i T) bool { 4 | var z T 5 | return i == z 6 | } 7 | -------------------------------------------------------------------------------- /empty_value.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.18 2 | // +build !go1.18 3 | 4 | package missinggo 5 | 6 | import "reflect" 7 | 8 | func IsZeroValue(i interface{}) bool { 9 | return IsEmptyValue(reflect.ValueOf(i)) 10 | } 11 | 12 | // Returns whether the value represents the empty value for its type. Used for 13 | // example to determine if complex types satisfy the common "omitempty" tag 14 | // option for marshalling. Taken from 15 | // http://stackoverflow.com/a/23555352/149482. 16 | func IsEmptyValue(v reflect.Value) bool { 17 | switch v.Kind() { 18 | case reflect.Func, reflect.Map, reflect.Slice: 19 | return v.IsNil() 20 | case reflect.Array: 21 | z := true 22 | for i := 0; i < v.Len(); i++ { 23 | z = z && IsEmptyValue(v.Index(i)) 24 | } 25 | return z 26 | case reflect.Struct: 27 | z := true 28 | vType := v.Type() 29 | for i := 0; i < v.NumField(); i++ { 30 | // ignore unexported fields to avoid reflection panics 31 | if !vType.Field(i).IsExported() { 32 | continue 33 | } 34 | z = z && IsEmptyValue(v.Field(i)) 35 | } 36 | return z 37 | } 38 | // Compare other types directly: 39 | z := reflect.Zero(v.Type()) 40 | return v.Interface() == z.Interface() 41 | } 42 | -------------------------------------------------------------------------------- /empty_value_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEmptyValue(t *testing.T) { 10 | assert.True(t, IsZeroValue(false)) 11 | assert.False(t, IsZeroValue(true)) 12 | } 13 | 14 | func TestUnexportedField(t *testing.T) { 15 | type FooType1 struct { 16 | Bar int 17 | Dog bool 18 | fish string 19 | } 20 | fooInstance := FooType1{} 21 | 22 | assert.True(t, IsZeroValue(fooInstance)) 23 | 24 | fooInstance2 := FooType1{fish: "fishy"} 25 | assert.False(t, IsZeroValue(fooInstance2)) 26 | 27 | fooInstance3 := FooType1{Bar: 5} 28 | 29 | assert.False(t, IsZeroValue(fooInstance3)) 30 | } 31 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | // An interface for "encoding/base64".Encoder 4 | type Encoding interface { 5 | EncodeToString([]byte) string 6 | DecodeString(string) ([]byte, error) 7 | } 8 | 9 | // An encoding that does nothing. 10 | type IdentityEncoding struct{} 11 | 12 | var _ Encoding = IdentityEncoding{} 13 | 14 | func (IdentityEncoding) EncodeToString(b []byte) string { return string(b) } 15 | func (IdentityEncoding) DecodeString(s string) ([]byte, error) { return []byte(s), nil } 16 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "sync" 4 | 5 | // Events are boolean flags that provide a channel that's closed when true. 6 | // This could go in the sync package, but that's more of a debug wrapper on 7 | // the standard library sync. 8 | type Event struct { 9 | ch chan struct{} 10 | closed bool 11 | } 12 | 13 | func (me *Event) LockedChan(lock sync.Locker) <-chan struct{} { 14 | lock.Lock() 15 | ch := me.C() 16 | lock.Unlock() 17 | return ch 18 | } 19 | 20 | // Returns a chan that is closed when the event is true. 21 | func (me *Event) C() <-chan struct{} { 22 | if me.ch == nil { 23 | me.ch = make(chan struct{}) 24 | } 25 | return me.ch 26 | } 27 | 28 | // TODO: Merge into Set. 29 | func (me *Event) Clear() { 30 | if me.closed { 31 | me.ch = nil 32 | me.closed = false 33 | } 34 | } 35 | 36 | // Set the event to true/on. 37 | func (me *Event) Set() (first bool) { 38 | if me.closed { 39 | return false 40 | } 41 | if me.ch == nil { 42 | me.ch = make(chan struct{}) 43 | } 44 | close(me.ch) 45 | me.closed = true 46 | return true 47 | } 48 | 49 | // TODO: Change to Get. 50 | func (me *Event) IsSet() bool { 51 | return me.closed 52 | } 53 | 54 | func (me *Event) Wait() { 55 | <-me.C() 56 | } 57 | 58 | // TODO: Merge into Set. 59 | func (me *Event) SetBool(b bool) { 60 | if b { 61 | me.Set() 62 | } else { 63 | me.Clear() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /event_synchronized.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "sync" 4 | 5 | type SynchronizedEvent struct { 6 | mu sync.Mutex 7 | e Event 8 | } 9 | 10 | func (me *SynchronizedEvent) Set() { 11 | me.mu.Lock() 12 | me.e.Set() 13 | me.mu.Unlock() 14 | } 15 | 16 | func (me *SynchronizedEvent) Clear() { 17 | me.mu.Lock() 18 | me.e.Clear() 19 | me.mu.Unlock() 20 | } 21 | 22 | func (me *SynchronizedEvent) C() <-chan struct{} { 23 | me.mu.Lock() 24 | defer me.mu.Unlock() 25 | return me.e.C() 26 | } 27 | -------------------------------------------------------------------------------- /event_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSetEvent(t *testing.T) { 10 | var e Event 11 | e.Set() 12 | } 13 | 14 | func TestEventIsSet(t *testing.T) { 15 | var e Event 16 | assert.False(t, e.IsSet()) 17 | e.Set() 18 | assert.True(t, e.IsSet()) 19 | } 20 | -------------------------------------------------------------------------------- /expect/assert.go: -------------------------------------------------------------------------------- 1 | package expect // import "github.com/anacrolix/missinggo/v2/expect" 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | func Nil(x interface{}) { 10 | if x != nil { 11 | panic(fmt.Sprintf("expected nil; got %v", x)) 12 | } 13 | } 14 | 15 | func NotNil(x interface{}) { 16 | if x == nil { 17 | panic(x) 18 | } 19 | } 20 | 21 | func Equal(x, y interface{}) { 22 | if x == y { 23 | return 24 | } 25 | yAsXType := reflect.ValueOf(y).Convert(reflect.TypeOf(x)).Interface() 26 | if !reflect.DeepEqual(x, yAsXType) { 27 | panic(fmt.Sprintf("%v != %v", x, y)) 28 | } 29 | } 30 | 31 | func StrictlyEqual(x, y interface{}) { 32 | if x != y { 33 | panic(fmt.Sprintf("%s != %s", x, y)) 34 | } 35 | } 36 | 37 | func OneRowAffected(r sql.Result) { 38 | count, err := r.RowsAffected() 39 | Nil(err) 40 | if count != 1 { 41 | panic(count) 42 | } 43 | } 44 | 45 | func True(b bool) { 46 | if !b { 47 | panic(b) 48 | } 49 | } 50 | 51 | var Ok = True 52 | 53 | func False(b bool) { 54 | if b { 55 | panic(b) 56 | } 57 | } 58 | 59 | func Zero(x interface{}) { 60 | if x != reflect.Zero(reflect.TypeOf(x)).Interface() { 61 | panic(x) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /expect/assert_test.go: -------------------------------------------------------------------------------- 1 | package expect 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEqualDifferentIntTypes(t *testing.T) { 10 | var a int = 1 11 | var b int64 = 1 12 | assert.EqualValues(t, a, b) 13 | assert.NotEqual(t, a, b) 14 | assert.NotPanics(t, func() { Equal(a, b) }) 15 | assert.Panics(t, func() { StrictlyEqual(a, b) }) 16 | } 17 | -------------------------------------------------------------------------------- /expvarIndentMap.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "bytes" 5 | "expvar" 6 | "fmt" 7 | ) 8 | 9 | type IndentMap struct { 10 | expvar.Map 11 | } 12 | 13 | var _ expvar.Var = (*IndentMap)(nil) 14 | 15 | func NewExpvarIndentMap(name string) *IndentMap { 16 | v := new(IndentMap) 17 | v.Init() 18 | expvar.Publish(name, v) 19 | return v 20 | } 21 | 22 | func (v *IndentMap) String() string { 23 | var b bytes.Buffer 24 | fmt.Fprintf(&b, "{") 25 | first := true 26 | v.Do(func(kv expvar.KeyValue) { 27 | if !first { 28 | fmt.Fprintf(&b, ",") 29 | } 30 | fmt.Fprintf(&b, "\n\t%q: %v", kv.Key, kv.Value) 31 | first = false 32 | }) 33 | fmt.Fprintf(&b, "}") 34 | return b.String() 35 | } 36 | -------------------------------------------------------------------------------- /filecache/file.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "sync" 7 | 8 | "github.com/anacrolix/missinggo/v2/pproffd" 9 | ) 10 | 11 | type File struct { 12 | path key 13 | f pproffd.OSFile 14 | afterWrite func(endOff int64) 15 | onRead func(n int) 16 | mu sync.Mutex 17 | offset int64 18 | } 19 | 20 | func (me *File) Seek(offset int64, whence int) (ret int64, err error) { 21 | ret, err = me.f.Seek(offset, whence) 22 | if err != nil { 23 | return 24 | } 25 | me.offset = ret 26 | return 27 | } 28 | 29 | var ( 30 | ErrFileTooLarge = errors.New("file too large for cache") 31 | ErrFileDisappeared = errors.New("file disappeared") 32 | ) 33 | 34 | func (me *File) Write(b []byte) (n int, err error) { 35 | n, err = me.f.Write(b) 36 | me.offset += int64(n) 37 | me.afterWrite(me.offset) 38 | return 39 | } 40 | 41 | func (me *File) WriteAt(b []byte, off int64) (n int, err error) { 42 | n, err = me.f.WriteAt(b, off) 43 | me.afterWrite(off + int64(n)) 44 | return 45 | } 46 | 47 | func (me *File) Close() error { 48 | return me.f.Close() 49 | } 50 | 51 | func (me *File) Stat() (os.FileInfo, error) { 52 | return me.f.Stat() 53 | } 54 | 55 | func (me *File) Read(b []byte) (n int, err error) { 56 | n, err = me.f.Read(b) 57 | me.onRead(n) 58 | return 59 | } 60 | 61 | func (me *File) ReadAt(b []byte, off int64) (n int, err error) { 62 | n, err = me.f.ReadAt(b, off) 63 | me.onRead(n) 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /filecache/itemstate.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/anacrolix/missinggo/v2" 8 | ) 9 | 10 | type itemState struct { 11 | Accessed time.Time 12 | Size int64 13 | } 14 | 15 | func (i *itemState) FromOSFileInfo(fi os.FileInfo) { 16 | i.Size = fi.Size() 17 | i.Accessed = missinggo.FileInfoAccessTime(fi) 18 | if fi.ModTime().After(i.Accessed) { 19 | i.Accessed = fi.ModTime() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /filecache/key.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | type key string 4 | 5 | func (me key) Before(other policyItemKey) bool { 6 | return me < other.(key) 7 | } 8 | -------------------------------------------------------------------------------- /filecache/lru.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/anacrolix/missinggo/orderedmap" 7 | ) 8 | 9 | type lru struct { 10 | o orderedmap.OrderedMap 11 | oKeys map[policyItemKey]lruKey 12 | } 13 | 14 | type lruKey struct { 15 | item policyItemKey 16 | used time.Time 17 | } 18 | 19 | func (me lruKey) Before(other lruKey) bool { 20 | if me.used.Equal(other.used) { 21 | return me.item.Before(other.item) 22 | } 23 | return me.used.Before(other.used) 24 | } 25 | 26 | var _ Policy = (*lru)(nil) 27 | 28 | func (me *lru) Choose() (ret policyItemKey) { 29 | any := false 30 | me.o.Iter(func(i interface{}) bool { 31 | ret = i.(lruKey).item 32 | any = true 33 | return false 34 | }) 35 | if !any { 36 | panic("cache empty") 37 | } 38 | return 39 | } 40 | 41 | func (me *lru) Used(k policyItemKey, at time.Time) { 42 | if me.o == nil { 43 | me.o = orderedmap.NewGoogleBTree(func(l, r interface{}) bool { 44 | return l.(lruKey).Before(r.(lruKey)) 45 | }) 46 | } else { 47 | me.o.Unset(me.oKeys[k]) 48 | } 49 | lk := lruKey{k, at} 50 | me.o.Set(lk, lk) 51 | if me.oKeys == nil { 52 | me.oKeys = make(map[policyItemKey]lruKey) 53 | } 54 | me.oKeys[k] = lk 55 | } 56 | 57 | func (me *lru) Forget(k policyItemKey) { 58 | if me.o != nil { 59 | me.o.Unset(me.oKeys[k]) 60 | } 61 | delete(me.oKeys, k) 62 | } 63 | 64 | func (me *lru) NumItems() int { 65 | return len(me.oKeys) 66 | } 67 | -------------------------------------------------------------------------------- /filecache/lru_test.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import "testing" 4 | 5 | func TestLRU(t *testing.T) { 6 | testPolicy(t, &lru{}) 7 | } 8 | -------------------------------------------------------------------------------- /filecache/lruitems_test.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLruDuplicateAccessTimes(t *testing.T) { 11 | var li Policy = new(lru) 12 | now := time.Now() 13 | li.Used(key("a"), now) 14 | li.Used(key("b"), now) 15 | assert.EqualValues(t, 2, li.NumItems()) 16 | } 17 | -------------------------------------------------------------------------------- /filecache/policy.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import "time" 4 | 5 | type policyItemKey interface { 6 | Before(policyItemKey) bool 7 | } 8 | 9 | type Policy interface { 10 | Choose() policyItemKey 11 | Used(k policyItemKey, at time.Time) 12 | Forget(k policyItemKey) 13 | NumItems() int 14 | } 15 | -------------------------------------------------------------------------------- /filecache/policy_test.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func testChooseForgottenKey(t *testing.T, p Policy) { 11 | assert.Equal(t, 0, p.NumItems()) 12 | assert.Panics(t, func() { p.Choose() }) 13 | p.Used(key("a"), time.Now()) 14 | assert.Equal(t, 1, p.NumItems()) 15 | p.Used(key("a"), time.Now().Add(1)) 16 | assert.Equal(t, 1, p.NumItems()) 17 | p.Forget(key("a")) 18 | assert.Equal(t, 0, p.NumItems()) 19 | assert.Panics(t, func() { p.Choose() }) 20 | } 21 | 22 | func testPolicy(t *testing.T, p Policy) { 23 | testChooseForgottenKey(t, p) 24 | } 25 | -------------------------------------------------------------------------------- /filecache/uniform.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | 8 | "github.com/anacrolix/missinggo/v2/resource" 9 | ) 10 | 11 | type uniformResourceProvider struct { 12 | *Cache 13 | } 14 | 15 | var _ resource.Provider = &uniformResourceProvider{} 16 | 17 | func (me *uniformResourceProvider) NewInstance(loc string) (resource.Instance, error) { 18 | return &uniformResource{me.Cache, loc}, nil 19 | } 20 | 21 | type uniformResource struct { 22 | Cache *Cache 23 | Location string 24 | } 25 | 26 | func (me *uniformResource) Get() (io.ReadCloser, error) { 27 | return me.Cache.OpenFile(me.Location, os.O_RDONLY) 28 | } 29 | 30 | func (me *uniformResource) Put(r io.Reader) (err error) { 31 | f, err := me.Cache.OpenFile(me.Location, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) 32 | if err != nil { 33 | return 34 | } 35 | defer f.Close() 36 | _, err = io.Copy(f, r) 37 | return 38 | } 39 | 40 | func (me *uniformResource) ReadAt(b []byte, off int64) (n int, err error) { 41 | f, err := me.Cache.OpenFile(me.Location, os.O_RDONLY) 42 | if err != nil { 43 | return 44 | } 45 | defer f.Close() 46 | return f.ReadAt(b, off) 47 | } 48 | 49 | func (me *uniformResource) WriteAt(b []byte, off int64) (n int, err error) { 50 | f, err := me.Cache.OpenFile(me.Location, os.O_CREATE|os.O_WRONLY) 51 | if err != nil { 52 | return 53 | } 54 | defer f.Close() 55 | return f.WriteAt(b, off) 56 | } 57 | 58 | func (me *uniformResource) Stat() (fi os.FileInfo, err error) { 59 | return me.Cache.Stat(me.Location) 60 | } 61 | 62 | func (me *uniformResource) Delete() error { 63 | return me.Cache.Remove(me.Location) 64 | } 65 | 66 | func (me *uniformResource) Readdirnames() (names []string, err error) { 67 | prefix := me.Location + "/" 68 | me.Cache.WalkItems(func(info ItemInfo) { 69 | //log.Printf("%q %q", me.Location, info.Path) 70 | name := string(info.Path) 71 | if strings.HasPrefix(name, prefix) { 72 | names = append(names, name[len(prefix):]) 73 | } 74 | }) 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /flag.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "sync" 4 | 5 | // Flag represents a boolean value, that signals sync.Cond's when it changes. 6 | // It's not concurrent safe by intention. 7 | type Flag struct { 8 | Conds map[*sync.Cond]struct{} 9 | value bool 10 | } 11 | 12 | func (me *Flag) Set(value bool) { 13 | if value != me.value { 14 | me.broadcastChange() 15 | } 16 | me.value = value 17 | } 18 | 19 | func (me *Flag) Get() bool { 20 | return me.value 21 | } 22 | 23 | func (me *Flag) broadcastChange() { 24 | for cond := range me.Conds { 25 | cond.Broadcast() 26 | } 27 | } 28 | 29 | func (me *Flag) addCond(c *sync.Cond) { 30 | if me.Conds == nil { 31 | me.Conds = make(map[*sync.Cond]struct{}) 32 | } 33 | me.Conds[c] = struct{}{} 34 | } 35 | 36 | // Adds the sync.Cond to all the given Flag's. 37 | func AddCondToFlags(cond *sync.Cond, flags ...*Flag) { 38 | for _, f := range flags { 39 | f.addCond(cond) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /futures/delayed.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import "time" 4 | 5 | func timeoutFuture(timeout time.Duration) *F { 6 | return Start(func() (interface{}, error) { 7 | time.Sleep(timeout) 8 | return nil, nil 9 | }) 10 | } 11 | 12 | type Delayed struct { 13 | Delay time.Duration 14 | Fs []*F 15 | } 16 | -------------------------------------------------------------------------------- /futures/delayed_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/bradfitz/iter" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | // Delay unit, high enough that system slowness doesn't affect timing, but low 14 | // enough to ensure tests are fast. 15 | const u = 20 * time.Millisecond 16 | 17 | func TestAsCompletedDelayed(t *testing.T) { 18 | t.Parallel() 19 | var fs []*F 20 | s := time.Now() 21 | for i := range iter.N(10) { 22 | f := timeoutFuture(time.Duration(i) * u) 23 | f.SetName(fmt.Sprintf("%d", i)) 24 | fs = append(fs, f) 25 | } 26 | as := AsCompletedDelayed( 27 | context.Background(), 28 | []*F{fs[0], fs[2]}, 29 | []Delayed{ 30 | {u, []*F{fs[1]}}, 31 | {3 * u, []*F{fs[0]}}, 32 | }, 33 | ) 34 | a := func(f, when time.Duration) { 35 | t.Helper() 36 | assert.Equal(t, fs[f], <-as) 37 | if time.Since(s) < when*u { 38 | t.Errorf("%d completed too soon", f) 39 | } 40 | if time.Since(s) >= (when+1)*u { 41 | t.Errorf("%d completed too late", f) 42 | } 43 | } 44 | a(0, 0) 45 | a(1, 1) 46 | a(2, 2) 47 | a(0, 2) 48 | _, ok := <-as 49 | assert.False(t, ok) 50 | assert.True(t, time.Since(s) < 4*u) 51 | } 52 | 53 | func TestAsCompletedDelayedContextCanceled(t *testing.T) { 54 | t.Parallel() 55 | var fs []*F 56 | s := time.Now() 57 | for i := range iter.N(10) { 58 | f := timeoutFuture(time.Duration(i) * u) 59 | f.SetName(fmt.Sprintf("%d", i)) 60 | fs = append(fs, f) 61 | } 62 | ctx, cancel := context.WithCancel(context.Background()) 63 | as := AsCompletedDelayed( 64 | ctx, 65 | []*F{fs[0], fs[2]}, 66 | []Delayed{ 67 | {u, []*F{fs[1]}}, 68 | {3 * u, []*F{fs[0]}}, 69 | }, 70 | ) 71 | a := func(f, when time.Duration) { 72 | t.Helper() 73 | assert.Equal(t, fs[f], <-as) 74 | if time.Since(s) < when*u { 75 | t.Errorf("%d completed too soon", f) 76 | } 77 | if time.Since(s) >= (when+1)*u { 78 | t.Errorf("%d completed too late", f) 79 | } 80 | } 81 | a(0, 0) 82 | cancel() 83 | _, ok := <-as 84 | assert.False(t, ok) 85 | assert.True(t, time.Since(s) < 1*u) 86 | } 87 | -------------------------------------------------------------------------------- /futures/funcs.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/anacrolix/missinggo/slices" 9 | "github.com/bradfitz/iter" 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 | } 142 | -------------------------------------------------------------------------------- /futures/future.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | func Start(fn func() (interface{}, error)) *F { 10 | f := &F{ 11 | done: make(chan struct{}), 12 | } 13 | go func() { 14 | f.setResult(fn()) 15 | }() 16 | return f 17 | } 18 | 19 | func StartNoError(fn func() interface{}) *F { 20 | return Start(func() (interface{}, error) { 21 | return fn(), nil 22 | }) 23 | } 24 | 25 | type F struct { 26 | name string 27 | mu sync.Mutex 28 | result interface{} 29 | err error 30 | done chan struct{} 31 | } 32 | 33 | func (f *F) String() string { 34 | if f.name != "" { 35 | return f.name 36 | } 37 | return fmt.Sprintf("future %p", f) 38 | } 39 | 40 | func (f *F) SetName(s string) { 41 | f.name = s 42 | } 43 | 44 | func (f *F) Err() error { 45 | <-f.done 46 | return f.err 47 | } 48 | 49 | // TODO: Just return value. 50 | func (f *F) Result() (interface{}, error) { 51 | <-f.done 52 | f.mu.Lock() 53 | defer f.mu.Unlock() 54 | return f.result, f.err 55 | } 56 | 57 | func (f *F) MustResult() interface{} { 58 | val, err := f.Result() 59 | if err != nil { 60 | panic(err) 61 | } 62 | return val 63 | } 64 | 65 | func (f *F) Done() <-chan struct{} { 66 | return f.done 67 | } 68 | 69 | func (f *F) setResult(result interface{}, err error) { 70 | f.mu.Lock() 71 | defer f.mu.Unlock() 72 | f.result = result 73 | f.err = err 74 | close(f.done) 75 | } 76 | 77 | func (f *F) ScanResult(res interface{}) error { 78 | _res, err := f.Result() 79 | reflect.ValueOf(res).Elem().Set(reflect.ValueOf(_res)) 80 | return err 81 | } 82 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anacrolix/missinggo/v2 2 | 3 | require ( 4 | crawshaw.io/sqlite v0.3.2 5 | github.com/RoaringBitmap/roaring v0.4.23 6 | github.com/anacrolix/envpprof v1.1.0 7 | github.com/anacrolix/log v0.6.0 8 | github.com/anacrolix/missinggo v1.2.1 9 | github.com/anacrolix/stm v0.2.0 10 | github.com/anacrolix/tagflag v1.1.0 11 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 12 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 13 | github.com/dustin/go-humanize v1.0.0 14 | github.com/frankban/quicktest v1.14.6 15 | github.com/google/btree v1.0.0 16 | github.com/huandu/xstrings v1.3.2 17 | github.com/prometheus/client_golang v1.11.1 18 | github.com/prometheus/client_model v0.2.0 19 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 20 | github.com/stretchr/testify v1.4.0 21 | go.opencensus.io v0.22.3 22 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 23 | zombiezen.com/go/sqlite v0.12.0 24 | ) 25 | 26 | require ( 27 | github.com/benbjohnson/immutable v0.2.0 // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect 32 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 33 | github.com/golang/protobuf v1.4.3 // indirect 34 | github.com/golang/snappy v0.0.1 // indirect 35 | github.com/google/go-cmp v0.6.0 // indirect 36 | github.com/google/uuid v1.3.0 // indirect 37 | github.com/kr/pretty v0.3.1 // indirect 38 | github.com/kr/text v0.2.0 // indirect 39 | github.com/mattn/go-isatty v0.0.16 // indirect 40 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 41 | github.com/mschoch/smat v0.2.0 // indirect 42 | github.com/philhofer/fwd v1.0.0 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/prometheus/common v0.26.0 // indirect 46 | github.com/prometheus/procfs v0.6.0 // indirect 47 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 48 | github.com/rogpeppe/go-internal v1.9.0 // indirect 49 | github.com/tinylib/msgp v1.1.2 // indirect 50 | github.com/willf/bitset v1.1.10 // indirect 51 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect 52 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 53 | google.golang.org/protobuf v1.26.0-rc.1 // indirect 54 | gopkg.in/yaml.v2 v2.3.0 // indirect 55 | modernc.org/libc v1.21.5 // indirect 56 | modernc.org/mathutil v1.5.0 // indirect 57 | modernc.org/memory v1.4.0 // indirect 58 | modernc.org/sqlite v1.20.0 // indirect 59 | ) 60 | 61 | go 1.18 62 | -------------------------------------------------------------------------------- /hostmaybeport.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Represents a split host port. 10 | type HostMaybePort struct { 11 | Host string // Just the host, with no port. 12 | Port int // The port if NoPort is false. 13 | NoPort bool // Whether a port is specified. 14 | Err error // The error returned from net.SplitHostPort. 15 | } 16 | 17 | func (me *HostMaybePort) String() string { 18 | if me.NoPort { 19 | return me.Host 20 | } 21 | return net.JoinHostPort(me.Host, strconv.FormatInt(int64(me.Port), 10)) 22 | } 23 | 24 | // Parse a "hostport" string, a concept that floats around the stdlib a lot 25 | // and is painful to work with. If no port is present, what's usually present 26 | // is just the host. 27 | func SplitHostMaybePort(hostport string) HostMaybePort { 28 | host, portStr, err := net.SplitHostPort(hostport) 29 | if err != nil { 30 | if strings.Contains(err.Error(), "missing port") { 31 | return HostMaybePort{ 32 | Host: hostport, 33 | NoPort: true, 34 | } 35 | } 36 | return HostMaybePort{ 37 | Err: err, 38 | } 39 | } 40 | portI64, err := strconv.ParseInt(portStr, 0, 0) 41 | if err != nil { 42 | return HostMaybePort{ 43 | Host: host, 44 | Port: -1, 45 | Err: err, 46 | } 47 | } 48 | return HostMaybePort{ 49 | Host: host, 50 | Port: int(portI64), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /hostmaybeport_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSplitHostMaybePortNoPort(t *testing.T) { 10 | hmp := SplitHostMaybePort("some.domain") 11 | assert.Equal(t, "some.domain", hmp.Host) 12 | assert.True(t, hmp.NoPort) 13 | assert.NoError(t, hmp.Err) 14 | } 15 | 16 | func TestSplitHostMaybePortPort(t *testing.T) { 17 | hmp := SplitHostMaybePort("some.domain:123") 18 | assert.Equal(t, "some.domain", hmp.Host) 19 | assert.Equal(t, 123, hmp.Port) 20 | assert.False(t, hmp.NoPort) 21 | assert.NoError(t, hmp.Err) 22 | } 23 | 24 | func TestSplitHostMaybePortBadPort(t *testing.T) { 25 | hmp := SplitHostMaybePort("some.domain:wat") 26 | assert.Equal(t, "some.domain", hmp.Host) 27 | assert.Equal(t, -1, hmp.Port) 28 | assert.False(t, hmp.NoPort) 29 | assert.Error(t, hmp.Err) 30 | } 31 | -------------------------------------------------------------------------------- /hostport.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | func ParseHostPort(hostport string) (host string, port int, err error) { 9 | host, portStr, err := net.SplitHostPort(hostport) 10 | if err != nil { 11 | return 12 | } 13 | port64, err := strconv.ParseInt(portStr, 0, 0) 14 | if err != nil { 15 | return 16 | } 17 | port = int(port64) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /httpfile/defaultfs.go: -------------------------------------------------------------------------------- 1 | package httpfile 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | var DefaultFS = &FS{ 8 | Client: http.DefaultClient, 9 | } 10 | 11 | // Returns the length of the resource in bytes. 12 | func GetLength(url string) (ret int64, err error) { 13 | return DefaultFS.GetLength(url) 14 | } 15 | -------------------------------------------------------------------------------- /httpfile/file.go: -------------------------------------------------------------------------------- 1 | package httpfile 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | 12 | "github.com/anacrolix/missinggo/httptoo" 13 | 14 | "github.com/anacrolix/missinggo/v2" 15 | ) 16 | 17 | type File struct { 18 | off int64 19 | r io.ReadCloser 20 | rOff int64 21 | length int64 22 | url string 23 | flags int 24 | fs *FS 25 | } 26 | 27 | func (me *File) headLength() (err error) { 28 | l, err := me.fs.GetLength(me.url) 29 | if err != nil { 30 | return 31 | } 32 | if l != -1 { 33 | me.length = l 34 | } 35 | return 36 | } 37 | 38 | func (me *File) prepareReader() (err error) { 39 | if me.r != nil && me.off != me.rOff { 40 | me.r.Close() 41 | me.r = nil 42 | } 43 | if me.r != nil { 44 | return nil 45 | } 46 | if me.flags&missinggo.O_ACCMODE == os.O_WRONLY { 47 | err = errors.New("read flags missing") 48 | return 49 | } 50 | req, err := http.NewRequest("GET", me.url, nil) 51 | if err != nil { 52 | return 53 | } 54 | if me.off != 0 { 55 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-", me.off)) 56 | } 57 | resp, err := me.fs.Client.Do(req) 58 | if err != nil { 59 | return 60 | } 61 | switch resp.StatusCode { 62 | case http.StatusPartialContent: 63 | cr, ok := httptoo.ParseBytesContentRange(resp.Header.Get("Content-Range")) 64 | if !ok || cr.First != me.off { 65 | err = errors.New("bad response") 66 | resp.Body.Close() 67 | return 68 | } 69 | me.length = cr.Length 70 | case http.StatusOK: 71 | if me.off != 0 { 72 | err = errors.New("bad response") 73 | resp.Body.Close() 74 | return 75 | } 76 | if h := resp.Header.Get("Content-Length"); h != "" { 77 | var cl uint64 78 | cl, err = strconv.ParseUint(h, 10, 64) 79 | if err != nil { 80 | resp.Body.Close() 81 | return 82 | } 83 | me.length = int64(cl) 84 | } 85 | case http.StatusNotFound: 86 | err = ErrNotFound 87 | resp.Body.Close() 88 | return 89 | default: 90 | err = errors.New(resp.Status) 91 | resp.Body.Close() 92 | return 93 | } 94 | me.r = resp.Body 95 | me.rOff = me.off 96 | return 97 | } 98 | 99 | func (me *File) Read(b []byte) (n int, err error) { 100 | err = me.prepareReader() 101 | if err != nil { 102 | return 103 | } 104 | n, err = me.r.Read(b) 105 | me.off += int64(n) 106 | me.rOff += int64(n) 107 | return 108 | } 109 | 110 | func (me *File) Seek(offset int64, whence int) (ret int64, err error) { 111 | switch whence { 112 | case os.SEEK_SET: 113 | ret = offset 114 | case os.SEEK_CUR: 115 | ret = me.off + offset 116 | case os.SEEK_END: 117 | // Try to update the resource length. 118 | err = me.headLength() 119 | if err != nil { 120 | if me.length == -1 { 121 | // Don't even have an old value. 122 | return 123 | } 124 | err = nil 125 | } 126 | ret = me.length + offset 127 | default: 128 | err = fmt.Errorf("unhandled whence: %d", whence) 129 | return 130 | } 131 | me.off = ret 132 | return 133 | } 134 | 135 | func (me *File) Write(b []byte) (n int, err error) { 136 | if me.flags&(os.O_WRONLY|os.O_RDWR) == 0 || me.flags&os.O_CREATE == 0 { 137 | err = errors.New("cannot write without write and create flags") 138 | return 139 | } 140 | req, err := http.NewRequest("PATCH", me.url, bytes.NewReader(b)) 141 | if err != nil { 142 | return 143 | } 144 | req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-", me.off)) 145 | req.ContentLength = int64(len(b)) 146 | resp, err := me.fs.Client.Do(req) 147 | if err != nil { 148 | return 149 | } 150 | resp.Body.Close() 151 | if resp.StatusCode != http.StatusPartialContent { 152 | err = errors.New(resp.Status) 153 | return 154 | } 155 | n = len(b) 156 | me.off += int64(n) 157 | return 158 | } 159 | 160 | func (me *File) Close() error { 161 | me.url = "" 162 | me.length = -1 163 | if me.r != nil { 164 | me.r.Close() 165 | me.r = nil 166 | } 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /httpfile/fs.go: -------------------------------------------------------------------------------- 1 | package httpfile 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | type FS struct { 11 | Client *http.Client 12 | } 13 | 14 | func (fs *FS) Delete(urlStr string) (err error) { 15 | req, err := http.NewRequest("DELETE", urlStr, nil) 16 | if err != nil { 17 | return 18 | } 19 | resp, err := fs.Client.Do(req) 20 | if err != nil { 21 | return 22 | } 23 | resp.Body.Close() 24 | if resp.StatusCode == http.StatusNotFound { 25 | err = ErrNotFound 26 | return 27 | } 28 | if resp.StatusCode != 200 { 29 | err = fmt.Errorf("response: %s", resp.Status) 30 | } 31 | return 32 | } 33 | 34 | func (fs *FS) GetLength(url string) (ret int64, err error) { 35 | resp, err := fs.Client.Head(url) 36 | if err != nil { 37 | return 38 | } 39 | resp.Body.Close() 40 | if resp.StatusCode == http.StatusNotFound { 41 | err = ErrNotFound 42 | return 43 | } 44 | return instanceLength(resp) 45 | } 46 | 47 | func (fs *FS) OpenSectionReader(url string, off, n int64) (ret io.ReadCloser, err error) { 48 | req, err := http.NewRequest("GET", url, nil) 49 | if err != nil { 50 | return 51 | } 52 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+n-1)) 53 | resp, err := fs.Client.Do(req) 54 | if err != nil { 55 | return 56 | } 57 | if resp.StatusCode == http.StatusNotFound { 58 | err = ErrNotFound 59 | resp.Body.Close() 60 | return 61 | } 62 | if resp.StatusCode != http.StatusPartialContent { 63 | err = fmt.Errorf("bad response status: %s", resp.Status) 64 | resp.Body.Close() 65 | return 66 | } 67 | ret = resp.Body 68 | return 69 | } 70 | 71 | func (fs *FS) Open(url string, flags int) (ret *File, err error) { 72 | ret = &File{ 73 | url: url, 74 | flags: flags, 75 | length: -1, 76 | fs: fs, 77 | } 78 | if flags&os.O_CREATE == 0 { 79 | err = ret.headLength() 80 | } 81 | return 82 | } 83 | -------------------------------------------------------------------------------- /httpfile/misc.go: -------------------------------------------------------------------------------- 1 | package httpfile 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/anacrolix/missinggo/httptoo" 10 | ) 11 | 12 | var ( 13 | ErrNotFound = os.ErrNotExist 14 | ) 15 | 16 | // ok is false if the response just doesn't specify anything we handle. 17 | func instanceLength(r *http.Response) (l int64, err error) { 18 | switch r.StatusCode { 19 | case http.StatusOK: 20 | l, err = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64) 21 | return 22 | case http.StatusPartialContent: 23 | cr, parseOk := httptoo.ParseBytesContentRange(r.Header.Get("Content-Range")) 24 | l = cr.Length 25 | if !parseOk { 26 | err = errors.New("error parsing Content-Range") 27 | } 28 | return 29 | default: 30 | err = errors.New("unhandled status code") 31 | return 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /httpmux/httpmux.go: -------------------------------------------------------------------------------- 1 | package httpmux 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "path" 8 | "regexp" 9 | "strings" 10 | 11 | "go.opencensus.io/trace" 12 | ) 13 | 14 | type pathParamContextKeyType struct{} 15 | 16 | var pathParamContextKey pathParamContextKeyType 17 | 18 | type Mux struct { 19 | handlers []Handler 20 | } 21 | 22 | func New() *Mux { 23 | return new(Mux) 24 | } 25 | 26 | type Handler struct { 27 | path *regexp.Regexp 28 | userHandler http.Handler 29 | } 30 | 31 | func (h Handler) Pattern() string { 32 | return h.path.String() 33 | } 34 | 35 | func (mux *Mux) GetHandler(r *http.Request) *Handler { 36 | matches := mux.matchingHandlers(r) 37 | if len(matches) == 0 { 38 | return nil 39 | } 40 | return &matches[0].Handler 41 | } 42 | 43 | func (me *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 44 | matches := me.matchingHandlers(r) 45 | if len(matches) == 0 { 46 | http.NotFound(w, r) 47 | return 48 | } 49 | m := matches[0] 50 | ctx := context.WithValue(r.Context(), pathParamContextKey, &PathParams{m}) 51 | ctx, span := trace.StartSpan(ctx, m.Handler.path.String(), trace.WithSpanKind(trace.SpanKindServer)) 52 | defer span.End() 53 | r = r.WithContext(ctx) 54 | defer func() { 55 | r := recover() 56 | if r == http.ErrAbortHandler { 57 | panic(r) 58 | } 59 | if r == nil { 60 | return 61 | } 62 | panic(fmt.Sprintf("while handling %q: %s", m.Handler.path.String(), r)) 63 | }() 64 | m.Handler.userHandler.ServeHTTP(w, r) 65 | } 66 | 67 | type match struct { 68 | Handler Handler 69 | submatches []string 70 | } 71 | 72 | func (me *Mux) matchingHandlers(r *http.Request) (ret []match) { 73 | for _, h := range me.handlers { 74 | subs := h.path.FindStringSubmatch(r.URL.Path) 75 | if subs == nil { 76 | continue 77 | } 78 | ret = append(ret, match{h, subs}) 79 | } 80 | return 81 | } 82 | 83 | func (me *Mux) distinctHandlerRegexp(r *regexp.Regexp) bool { 84 | for _, h := range me.handlers { 85 | if h.path.String() == r.String() { 86 | return false 87 | } 88 | } 89 | return true 90 | } 91 | 92 | func (me *Mux) Handle(path string, h http.Handler) { 93 | expr := "^" + path 94 | if !strings.HasSuffix(expr, "$") { 95 | expr += "$" 96 | } 97 | re, err := regexp.Compile(expr) 98 | if err != nil { 99 | panic(err) 100 | } 101 | if !me.distinctHandlerRegexp(re) { 102 | panic(fmt.Sprintf("path %q is not distinct", path)) 103 | } 104 | me.handlers = append(me.handlers, Handler{re, h}) 105 | } 106 | 107 | func (me *Mux) HandleFunc(path string, hf func(http.ResponseWriter, *http.Request)) { 108 | me.Handle(path, http.HandlerFunc(hf)) 109 | } 110 | 111 | func Path(parts ...string) string { 112 | return path.Join(parts...) 113 | } 114 | 115 | type PathParams struct { 116 | match match 117 | } 118 | 119 | func (me *PathParams) ByName(name string) string { 120 | for i, sn := range me.match.Handler.path.SubexpNames()[1:] { 121 | if sn == name { 122 | return me.match.submatches[i+1] 123 | } 124 | } 125 | return "" 126 | } 127 | 128 | func RequestPathParams(r *http.Request) *PathParams { 129 | ctx := r.Context() 130 | return ctx.Value(pathParamContextKey).(*PathParams) 131 | } 132 | 133 | func PathRegexpParam(name string, re string) string { 134 | return fmt.Sprintf("(?P<%s>%s)", name, re) 135 | } 136 | 137 | func Param(name string) string { 138 | return fmt.Sprintf("(?P<%s>[^/]+)", name) 139 | } 140 | 141 | func RestParam(name string) string { 142 | return fmt.Sprintf("(?P<%s>.*)$", name) 143 | } 144 | 145 | func NonEmptyRestParam(name string) string { 146 | return fmt.Sprintf("(?P<%s>.+)$", name) 147 | } 148 | -------------------------------------------------------------------------------- /httpresponsestatus.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | // todo move to httptoo as ResponseRecorder 4 | 5 | import ( 6 | "bufio" 7 | "net" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | // A http.ResponseWriter that tracks the status of the response. The status 13 | // code, and number of bytes written for example. 14 | type StatusResponseWriter struct { 15 | http.ResponseWriter 16 | Code int 17 | BytesWritten int64 18 | Started time.Time 19 | TimeToFirstByte time.Duration // Time to first byte 20 | GotFirstByte bool 21 | WroteHeader Event 22 | Hijacked bool 23 | } 24 | 25 | var _ interface { 26 | http.ResponseWriter 27 | http.Hijacker 28 | } = (*StatusResponseWriter)(nil) 29 | 30 | func (me *StatusResponseWriter) Write(b []byte) (n int, err error) { 31 | // Exactly how it's done in the standard library. This ensures Code is 32 | // correct. 33 | if !me.WroteHeader.IsSet() { 34 | me.WriteHeader(http.StatusOK) 35 | } 36 | if me.Started.IsZero() { 37 | panic("Started was not initialized") 38 | } 39 | timeBeforeWrite := time.Now() 40 | n, err = me.ResponseWriter.Write(b) 41 | if n > 0 && !me.GotFirstByte { 42 | me.TimeToFirstByte = timeBeforeWrite.Sub(me.Started) 43 | me.GotFirstByte = true 44 | } 45 | me.BytesWritten += int64(n) 46 | return 47 | } 48 | 49 | func (me *StatusResponseWriter) WriteHeader(code int) { 50 | me.ResponseWriter.WriteHeader(code) 51 | if !me.WroteHeader.IsSet() { 52 | me.Code = code 53 | me.WroteHeader.Set() 54 | } 55 | } 56 | 57 | func (me *StatusResponseWriter) Hijack() (c net.Conn, b *bufio.ReadWriter, err error) { 58 | me.Hijacked = true 59 | c, b, err = me.ResponseWriter.(http.Hijacker).Hijack() 60 | if b.Writer.Buffered() != 0 { 61 | panic("unexpected buffered writes") 62 | } 63 | c = responseConn{c, me} 64 | return 65 | } 66 | 67 | type responseConn struct { 68 | net.Conn 69 | s *StatusResponseWriter 70 | } 71 | 72 | func (me responseConn) Write(b []byte) (n int, err error) { 73 | n, err = me.Conn.Write(b) 74 | me.s.BytesWritten += int64(n) 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /httptoo/accept.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/anacrolix/missinggo/mime" 9 | ) 10 | 11 | func ParseAccept(line string) (parsed AcceptDirectives, err error) { 12 | dirs := strings.Split(line, ",") 13 | for _, d := range dirs { 14 | p := AcceptDirective{ 15 | Q: 1, 16 | } 17 | ss := strings.Split(d, ";") 18 | switch len(ss) { 19 | case 2: 20 | p.Q, err = strconv.ParseFloat(ss[1], 32) 21 | if err != nil { 22 | return 23 | } 24 | fallthrough 25 | case 1: 26 | p.MimeType.FromString(ss[0]) 27 | default: 28 | err = fmt.Errorf("error parsing %q", d) 29 | return 30 | } 31 | parsed = append(parsed, p) 32 | } 33 | return 34 | } 35 | 36 | type ( 37 | AcceptDirectives []AcceptDirective 38 | AcceptDirective struct { 39 | MimeType mime.Type 40 | Q float64 41 | } 42 | ) 43 | -------------------------------------------------------------------------------- /httptoo/bytes_content_range.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type BytesContentRange struct { 12 | First, Last, Length int64 13 | } 14 | 15 | type BytesRange struct { 16 | First, Last int64 17 | } 18 | 19 | func (me BytesRange) String() string { 20 | if me.Last == math.MaxInt64 { 21 | return fmt.Sprintf("bytes=%d-", me.First) 22 | } 23 | return fmt.Sprintf("bytes=%d-%d", me.First, me.Last) 24 | } 25 | 26 | var ( 27 | httpBytesRangeRegexp = regexp.MustCompile(`bytes[ =](\d+)-(\d*)`) 28 | ) 29 | 30 | func ParseBytesRange(s string) (ret BytesRange, ok bool) { 31 | ss := httpBytesRangeRegexp.FindStringSubmatch(s) 32 | if ss == nil { 33 | return 34 | } 35 | var err error 36 | ret.First, err = strconv.ParseInt(ss[1], 10, 64) 37 | if err != nil { 38 | return 39 | } 40 | if ss[2] == "" { 41 | ret.Last = math.MaxInt64 42 | } else { 43 | ret.Last, err = strconv.ParseInt(ss[2], 10, 64) 44 | if err != nil { 45 | return 46 | } 47 | } 48 | ok = true 49 | return 50 | } 51 | 52 | func parseUnitRanges(s string) (unit, ranges string) { 53 | s = strings.TrimSpace(s) 54 | i := strings.IndexAny(s, " =") 55 | if i == -1 { 56 | return 57 | } 58 | unit = s[:i] 59 | ranges = s[i+1:] 60 | return 61 | } 62 | 63 | func parseFirstLast(s string) (first, last int64) { 64 | ss := strings.SplitN(s, "-", 2) 65 | first, err := strconv.ParseInt(ss[0], 10, 64) 66 | if err != nil { 67 | panic(err) 68 | } 69 | last, err = strconv.ParseInt(ss[1], 10, 64) 70 | if err != nil { 71 | panic(err) 72 | } 73 | return 74 | } 75 | 76 | func parseContentRange(s string) (ret BytesContentRange) { 77 | ss := strings.SplitN(s, "/", 2) 78 | firstLast := strings.TrimSpace(ss[0]) 79 | if firstLast == "*" { 80 | ret.First = -1 81 | ret.Last = -1 82 | } else { 83 | ret.First, ret.Last = parseFirstLast(firstLast) 84 | } 85 | il := strings.TrimSpace(ss[1]) 86 | if il == "*" { 87 | ret.Length = -1 88 | } else { 89 | var err error 90 | ret.Length, err = strconv.ParseInt(il, 10, 64) 91 | if err != nil { 92 | panic(err) 93 | } 94 | } 95 | return 96 | } 97 | 98 | func ParseBytesContentRange(s string) (ret BytesContentRange, ok bool) { 99 | unit, ranges := parseUnitRanges(s) 100 | if unit != "bytes" { 101 | return 102 | } 103 | ret = parseContentRange(ranges) 104 | ok = true 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /httptoo/bytes_content_range_test.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestParseHTTPContentRange(t *testing.T) { 10 | for _, _case := range []struct { 11 | h string 12 | cr *BytesContentRange 13 | }{ 14 | {"", nil}, 15 | {"1-2/*", nil}, 16 | {"bytes=1-2/3", &BytesContentRange{1, 2, 3}}, 17 | {"bytes=12-34/*", &BytesContentRange{12, 34, -1}}, 18 | {" bytes=12-34/*", &BytesContentRange{12, 34, -1}}, 19 | {" bytes 12-34/56", &BytesContentRange{12, 34, 56}}, 20 | {" bytes=*/56", &BytesContentRange{-1, -1, 56}}, 21 | } { 22 | ret, ok := ParseBytesContentRange(_case.h) 23 | assert.Equal(t, _case.cr != nil, ok) 24 | if _case.cr != nil { 25 | assert.Equal(t, *_case.cr, ret) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /httptoo/client.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | ) 7 | 8 | // Returns the http.Client's TLS Config, traversing and generating any 9 | // defaults along the way to get it. 10 | func ClientTLSConfig(cl *http.Client) *tls.Config { 11 | if cl.Transport == nil { 12 | cl.Transport = http.DefaultTransport 13 | } 14 | tr := cl.Transport.(*http.Transport) 15 | if tr.TLSClientConfig == nil { 16 | tr.TLSClientConfig = &tls.Config{} 17 | } 18 | return tr.TLSClientConfig 19 | } 20 | -------------------------------------------------------------------------------- /httptoo/fs.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | ) 7 | 8 | // Wraps a http.FileSystem, disabling directory listings, per the commonly 9 | // requested feature at https://groups.google.com/forum/#!topic/golang- 10 | // nuts/bStLPdIVM6w . 11 | type JustFilesFilesystem struct { 12 | Fs http.FileSystem 13 | } 14 | 15 | func (fs JustFilesFilesystem) Open(name string) (http.File, error) { 16 | f, err := fs.Fs.Open(name) 17 | if err != nil { 18 | return nil, err 19 | } 20 | d, err := f.Stat() 21 | if err != nil { 22 | f.Close() 23 | return nil, err 24 | } 25 | if d.IsDir() { 26 | f.Close() 27 | // This triggers http.FileServer to show a 404. 28 | return nil, os.ErrNotExist 29 | } 30 | return f, nil 31 | } 32 | -------------------------------------------------------------------------------- /httptoo/gzip.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type gzipResponseWriter struct { 11 | io.Writer 12 | http.ResponseWriter 13 | haveWritten bool 14 | } 15 | 16 | var _ http.ResponseWriter = &gzipResponseWriter{} 17 | 18 | func (w *gzipResponseWriter) Write(b []byte) (int, error) { 19 | if w.haveWritten { 20 | goto write 21 | } 22 | w.haveWritten = true 23 | if w.Header().Get("Content-Type") != "" { 24 | goto write 25 | } 26 | if type_ := http.DetectContentType(b); type_ != "application/octet-stream" { 27 | w.Header().Set("Content-Type", type_) 28 | } 29 | write: 30 | return w.Writer.Write(b) 31 | } 32 | 33 | // Gzips response body if the request says it'll allow it. 34 | func GzipHandler(h http.Handler) http.Handler { 35 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") || w.Header().Get("Content-Encoding") != "" || w.Header().Get("Vary") != "" { 37 | h.ServeHTTP(w, r) 38 | return 39 | } 40 | w.Header().Set("Content-Encoding", "gzip") 41 | w.Header().Set("Vary", "Accept-Encoding") 42 | gz := gzip.NewWriter(w) 43 | defer gz.Close() 44 | h.ServeHTTP(&gzipResponseWriter{ 45 | Writer: gz, 46 | ResponseWriter: w, 47 | }, r) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /httptoo/gzip_test.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "compress/gzip" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | const helloWorld = "hello, world\n" 15 | 16 | func helloWorldHandler(w http.ResponseWriter, r *http.Request) { 17 | // w.Header().Set("Content-Length", strconv.FormatInt(int64(len(helloWorld)), 10)) 18 | w.Write([]byte(helloWorld)) 19 | } 20 | 21 | func requestResponse(h http.Handler, r *http.Request) (*http.Response, error) { 22 | s := httptest.NewServer(h) 23 | defer s.Close() 24 | return http.DefaultClient.Do(r) 25 | } 26 | 27 | func TestGzipHandler(t *testing.T) { 28 | rr := httptest.NewRecorder() 29 | helloWorldHandler(rr, nil) 30 | assert.EqualValues(t, helloWorld, rr.Body.String()) 31 | 32 | rr = httptest.NewRecorder() 33 | GzipHandler(http.HandlerFunc(helloWorldHandler)).ServeHTTP(rr, new(http.Request)) 34 | assert.EqualValues(t, helloWorld, rr.Body.String()) 35 | 36 | rr = httptest.NewRecorder() 37 | r, err := http.NewRequest("GET", "/", nil) 38 | require.NoError(t, err) 39 | r.Header.Set("Accept-Encoding", "gzip") 40 | GzipHandler(http.HandlerFunc(helloWorldHandler)).ServeHTTP(rr, r) 41 | gr, err := gzip.NewReader(rr.Body) 42 | require.NoError(t, err) 43 | defer gr.Close() 44 | b, err := ioutil.ReadAll(gr) 45 | require.NoError(t, err) 46 | assert.EqualValues(t, helloWorld, b) 47 | 48 | s := httptest.NewServer(nil) 49 | s.Config.Handler = GzipHandler(http.HandlerFunc(helloWorldHandler)) 50 | req, err := http.NewRequest("GET", s.URL, nil) 51 | req.Header.Set("Accept-Encoding", "gzip") 52 | resp, err := http.DefaultClient.Do(req) 53 | require.NoError(t, err) 54 | gr.Close() 55 | gr, err = gzip.NewReader(resp.Body) 56 | require.NoError(t, err) 57 | defer gr.Close() 58 | b, err = ioutil.ReadAll(gr) 59 | require.NoError(t, err) 60 | assert.EqualValues(t, helloWorld, b) 61 | assert.EqualValues(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type")) 62 | assert.EqualValues(t, "gzip", resp.Header.Get("Content-Encoding")) 63 | } 64 | -------------------------------------------------------------------------------- /httptoo/headers.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | type Visibility int 10 | 11 | const ( 12 | Default = 0 13 | Public = 1 14 | Private = 2 15 | ) 16 | 17 | type CacheControlHeader struct { 18 | MaxAge time.Duration 19 | Caching Visibility 20 | NoStore bool 21 | } 22 | 23 | func (me *CacheControlHeader) caching() []string { 24 | switch me.Caching { 25 | case Public: 26 | return []string{"public"} 27 | case Private: 28 | return []string{"private"} 29 | default: 30 | return nil 31 | } 32 | } 33 | 34 | func (me *CacheControlHeader) maxAge() []string { 35 | if me.MaxAge == 0 { 36 | return nil 37 | } 38 | d := me.MaxAge 39 | if d < 0 { 40 | d = 0 41 | } 42 | return []string{fmt.Sprintf("max-age=%d", d/time.Second)} 43 | } 44 | 45 | func (me *CacheControlHeader) noStore() []string { 46 | if me.NoStore { 47 | return []string{"no-store"} 48 | } 49 | return nil 50 | } 51 | 52 | func (me *CacheControlHeader) concat(sss ...[]string) (ret []string) { 53 | for _, ss := range sss { 54 | ret = append(ret, ss...) 55 | } 56 | return 57 | } 58 | 59 | func (me CacheControlHeader) String() string { 60 | return strings.Join(me.concat(me.caching(), me.maxAge()), ", ") 61 | } 62 | -------------------------------------------------------------------------------- /httptoo/headers_test.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCacheControlHeaderString(t *testing.T) { 11 | assert.Equal(t, "public, max-age=43200", CacheControlHeader{ 12 | MaxAge: 12 * time.Hour, 13 | Caching: Public, 14 | }.String()) 15 | } 16 | -------------------------------------------------------------------------------- /httptoo/httptoo.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/bradfitz/iter" 9 | 10 | "github.com/anacrolix/missinggo/v2" 11 | ) 12 | 13 | func OriginatingProtocol(r *http.Request) string { 14 | if fp := r.Header.Get("X-Forwarded-Proto"); fp != "" { 15 | return fp 16 | } else if r.TLS != nil { 17 | return "https" 18 | } else { 19 | return "http" 20 | } 21 | } 22 | 23 | // Clears the named cookie for every domain that leads to the current one. 24 | func NukeCookie(w http.ResponseWriter, r *http.Request, name, path string) { 25 | parts := strings.Split(missinggo.SplitHostMaybePort(r.Host).Host, ".") 26 | for i := range iter.N(len(parts) + 1) { // Include the empty domain. 27 | http.SetCookie(w, &http.Cookie{ 28 | Name: name, 29 | MaxAge: -1, 30 | Path: path, 31 | Domain: strings.Join(parts[i:], "."), 32 | }) 33 | } 34 | } 35 | 36 | // Performs quoted-string from http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html 37 | func EncodeQuotedString(s string) string { 38 | return strconv.Quote(s) 39 | } 40 | 41 | // https://httpstatuses.com/499 42 | const StatusClientCancelledRequest = 499 43 | -------------------------------------------------------------------------------- /httptoo/inproc_roundtrip.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "sync" 8 | 9 | "github.com/anacrolix/missinggo/v2" 10 | ) 11 | 12 | type responseWriter struct { 13 | mu sync.Mutex 14 | r http.Response 15 | headerWritten missinggo.Event 16 | bodyWriter io.WriteCloser 17 | bodyClosed missinggo.SynchronizedEvent 18 | } 19 | 20 | var _ interface { 21 | http.ResponseWriter 22 | // We're able to emulate this easily enough. 23 | http.CloseNotifier 24 | } = &responseWriter{} 25 | 26 | // Use Request.Context.Done instead. 27 | func (me *responseWriter) CloseNotify() <-chan bool { 28 | ret := make(chan bool, 1) 29 | go func() { 30 | <-me.bodyClosed.C() 31 | ret <- true 32 | }() 33 | return ret 34 | } 35 | 36 | func (me *responseWriter) Header() http.Header { 37 | if me.r.Header == nil { 38 | me.r.Header = make(http.Header) 39 | } 40 | return me.r.Header 41 | } 42 | 43 | func (me *responseWriter) Write(b []byte) (int, error) { 44 | me.mu.Lock() 45 | if !me.headerWritten.IsSet() { 46 | me.writeHeader(200) 47 | } 48 | me.mu.Unlock() 49 | return me.bodyWriter.Write(b) 50 | } 51 | 52 | func (me *responseWriter) WriteHeader(status int) { 53 | me.mu.Lock() 54 | me.writeHeader(status) 55 | me.mu.Unlock() 56 | } 57 | 58 | func (me *responseWriter) writeHeader(status int) { 59 | if me.headerWritten.IsSet() { 60 | return 61 | } 62 | me.r.StatusCode = status 63 | me.headerWritten.Set() 64 | } 65 | 66 | func (me *responseWriter) runHandler(h http.Handler, req *http.Request) { 67 | var pr *io.PipeReader 68 | pr, me.bodyWriter = io.Pipe() 69 | me.r.Body = struct { 70 | io.Reader 71 | io.Closer 72 | }{pr, eventCloser{pr, &me.bodyClosed}} 73 | // Shouldn't be writing to the response after the handler returns. 74 | defer me.bodyWriter.Close() 75 | // Send a 200 if nothing was written yet. 76 | defer me.WriteHeader(200) 77 | // Wrap the context in the given Request with one that closes when either 78 | // the handler returns, or the response body is closed. 79 | ctx, cancel := context.WithCancel(req.Context()) 80 | defer cancel() 81 | go func() { 82 | <-me.bodyClosed.C() 83 | cancel() 84 | }() 85 | h.ServeHTTP(me, req.WithContext(ctx)) 86 | } 87 | 88 | type eventCloser struct { 89 | c io.Closer 90 | closed *missinggo.SynchronizedEvent 91 | } 92 | 93 | func (me eventCloser) Close() (err error) { 94 | err = me.c.Close() 95 | me.closed.Set() 96 | return 97 | } 98 | 99 | func RoundTripHandler(req *http.Request, h http.Handler) (*http.Response, error) { 100 | rw := responseWriter{} 101 | go rw.runHandler(h, req) 102 | <-rw.headerWritten.LockedChan(&rw.mu) 103 | return &rw.r, nil 104 | } 105 | 106 | type InProcRoundTripper struct { 107 | Handler http.Handler 108 | } 109 | 110 | func (me *InProcRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 111 | return RoundTripHandler(req, me.Handler) 112 | } 113 | -------------------------------------------------------------------------------- /httptoo/request.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | "github.com/anacrolix/missinggo/v2" 8 | ) 9 | 10 | // Request is intended for localhost, either with a localhost name, or 11 | // loopback IP. 12 | func RequestIsForLocalhost(r *http.Request) bool { 13 | hostHost := missinggo.SplitHostMaybePort(r.Host).Host 14 | if ip := net.ParseIP(hostHost); ip != nil { 15 | return ip.IsLoopback() 16 | } 17 | return hostHost == "localhost" 18 | } 19 | 20 | // Request originated from a loopback IP. 21 | func RequestIsFromLocalhost(r *http.Request) bool { 22 | return net.ParseIP(missinggo.SplitHostMaybePort(r.RemoteAddr).Host).IsLoopback() 23 | } 24 | -------------------------------------------------------------------------------- /httptoo/reverse_proxy.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "sync" 11 | ) 12 | 13 | func deepCopy(dst, src interface{}) error { 14 | r, w := io.Pipe() 15 | e := gob.NewEncoder(w) 16 | d := gob.NewDecoder(r) 17 | var decErr, encErr error 18 | var wg sync.WaitGroup 19 | wg.Add(1) 20 | go func() { 21 | defer wg.Done() 22 | decErr = d.Decode(dst) 23 | r.Close() 24 | }() 25 | encErr = e.Encode(src) 26 | // Always returns nil. 27 | w.CloseWithError(encErr) 28 | wg.Wait() 29 | if encErr != nil { 30 | return encErr 31 | } 32 | return decErr 33 | } 34 | 35 | // Takes a request, and alters its destination fields, for proxying. 36 | func RedirectedRequest(r *http.Request, newUrl string) (ret *http.Request, err error) { 37 | u, err := url.Parse(newUrl) 38 | if err != nil { 39 | return 40 | } 41 | ret = new(http.Request) 42 | *ret = *r 43 | ret.Header = nil 44 | err = deepCopy(&ret.Header, r.Header) 45 | if err != nil { 46 | return 47 | } 48 | ret.URL = u 49 | ret.RequestURI = "" 50 | return 51 | } 52 | 53 | func CopyHeaders(w http.ResponseWriter, r *http.Response) { 54 | for h, vs := range r.Header { 55 | for _, v := range vs { 56 | w.Header().Add(h, v) 57 | } 58 | } 59 | } 60 | 61 | func ForwardResponse(w http.ResponseWriter, r *http.Response) { 62 | CopyHeaders(w, r) 63 | w.WriteHeader(r.StatusCode) 64 | // Errors frequently occur writing the body when the client hangs up. 65 | io.Copy(w, r.Body) 66 | r.Body.Close() 67 | } 68 | 69 | func SetOriginRequestForwardingHeaders(o, f *http.Request) { 70 | xff := o.Header.Get("X-Forwarded-For") 71 | hop, _, _ := net.SplitHostPort(f.RemoteAddr) 72 | if xff == "" { 73 | xff = hop 74 | } else { 75 | xff += "," + hop 76 | } 77 | o.Header.Set("X-Forwarded-For", xff) 78 | o.Header.Set("X-Forwarded-Proto", OriginatingProtocol(f)) 79 | } 80 | 81 | // w is for the client response. r is the request to send to the origin 82 | // (already "forwarded"). originUrl is where to send the request. 83 | func ReverseProxyUpgrade(w http.ResponseWriter, r *http.Request, originUrl string) (err error) { 84 | u, err := url.Parse(originUrl) 85 | if err != nil { 86 | return 87 | } 88 | oc, err := net.Dial("tcp", u.Host) 89 | if err != nil { 90 | return 91 | } 92 | defer oc.Close() 93 | err = r.Write(oc) 94 | if err != nil { 95 | return 96 | } 97 | originConnReadBuffer := bufio.NewReader(oc) 98 | originResp, err := http.ReadResponse(originConnReadBuffer, r) 99 | if err != nil { 100 | return 101 | } 102 | if originResp.StatusCode != 101 { 103 | ForwardResponse(w, originResp) 104 | return 105 | } 106 | cc, _, err := w.(http.Hijacker).Hijack() 107 | if err != nil { 108 | return 109 | } 110 | defer cc.Close() 111 | originResp.Write(cc) 112 | go io.Copy(oc, cc) 113 | // Let the origin connection control when this routine returns, as we 114 | // should trust it more. 115 | io.Copy(cc, originConnReadBuffer) 116 | return 117 | } 118 | 119 | func ReverseProxy(w http.ResponseWriter, r *http.Request, originUrl string, client *http.Client) (err error) { 120 | originRequest, err := RedirectedRequest(r, originUrl) 121 | if err != nil { 122 | return 123 | } 124 | SetOriginRequestForwardingHeaders(originRequest, r) 125 | if r.Header.Get("Connection") == "Upgrade" { 126 | return ReverseProxyUpgrade(w, originRequest, originUrl) 127 | } 128 | rt := client.Transport 129 | if rt == nil { 130 | rt = http.DefaultTransport 131 | } 132 | originResp, err := rt.RoundTrip(originRequest) 133 | if err != nil { 134 | return 135 | } 136 | ForwardResponse(w, originResp) 137 | return 138 | } 139 | -------------------------------------------------------------------------------- /httptoo/url.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | // Deep copies a URL. I could call it DeepCopyURL, but what else would you be 9 | // copying when you have a *url.URL? Of note is that the Userinfo is deep 10 | // copied. The returned URL shares no references with the original. 11 | func CopyURL(u *url.URL) (ret *url.URL) { 12 | ret = new(url.URL) 13 | *ret = *u 14 | if u.User != nil { 15 | ret.User = new(url.Userinfo) 16 | *ret.User = *u.User 17 | } 18 | return 19 | } 20 | 21 | // Reconstructs the URL that would have produced the given Request. 22 | // Request.URLs are not fully populated in http.Server handlers. 23 | func RequestedURL(r *http.Request) (ret *url.URL) { 24 | ret = CopyURL(r.URL) 25 | ret.Host = r.Host 26 | ret.Scheme = OriginatingProtocol(r) 27 | return 28 | } 29 | 30 | // The official URL struct parameters, for tracking changes and reference 31 | // here. 32 | // 33 | // Scheme string 34 | // Opaque string // encoded opaque data 35 | // User *Userinfo // username and password information 36 | // Host string // host or host:port 37 | // Path string 38 | // RawPath string // encoded path hint (Go 1.5 and later only; see EscapedPath method) 39 | // ForceQuery bool // append a query ('?') even if RawQuery is empty 40 | // RawQuery string // encoded query values, without '?' 41 | // Fragment string // fragment for references, without '#' 42 | 43 | // Return the first URL extended with elements of the second, in the manner 44 | // that occurs throughout my projects. Noteworthy difference from 45 | // url.URL.ResolveReference is that if the reference has a scheme, the base is 46 | // not completely ignored. 47 | func AppendURL(u, v *url.URL) *url.URL { 48 | u = CopyURL(u) 49 | clobberString(&u.Scheme, v.Scheme) 50 | clobberString(&u.Host, v.Host) 51 | u.Path += v.Path 52 | q := u.Query() 53 | for k, v := range v.Query() { 54 | q[k] = append(q[k], v...) 55 | } 56 | u.RawQuery = q.Encode() 57 | return u 58 | } 59 | 60 | func clobberString(s *string, value string) { 61 | if value != "" { 62 | *s = value 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /httptoo/url_test.go: -------------------------------------------------------------------------------- 1 | package httptoo 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAppendURL(t *testing.T) { 11 | assert.EqualValues(t, "http://localhost:8080/trailing/slash/", AppendURL( 12 | &url.URL{Scheme: "http", Host: "localhost:8080"}, 13 | &url.URL{Path: "/trailing/slash/"}, 14 | ).String()) 15 | assert.EqualValues(t, "ws://localhost:8080/events?ih=harpdarp", AppendURL( 16 | &url.URL{Scheme: "http", Host: "localhost:8080"}, 17 | &url.URL{Scheme: "ws", Path: "/events", RawQuery: "ih=harpdarp"}, 18 | ).String()) 19 | } 20 | -------------------------------------------------------------------------------- /ioutil.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "io" 4 | 5 | type StatWriter struct { 6 | Written int64 7 | w io.Writer 8 | } 9 | 10 | func (me *StatWriter) Write(b []byte) (n int, err error) { 11 | n, err = me.w.Write(b) 12 | me.Written += int64(n) 13 | return 14 | } 15 | 16 | func NewStatWriter(w io.Writer) *StatWriter { 17 | return &StatWriter{w: w} 18 | } 19 | 20 | var ZeroReader zeroReader 21 | 22 | type zeroReader struct{} 23 | 24 | func (me zeroReader) Read(b []byte) (n int, err error) { 25 | for i := range b { 26 | b[i] = 0 27 | } 28 | n = len(b) 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /ipport.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | type IpPort struct { 9 | IP net.IP 10 | Port uint16 11 | } 12 | 13 | func (me IpPort) String() string { 14 | return net.JoinHostPort(me.IP.String(), strconv.FormatUint(uint64(me.Port), 10)) 15 | } 16 | 17 | func IpPortFromNetAddr(na net.Addr) IpPort { 18 | return IpPort{AddrIP(na), uint16(AddrPort(na))} 19 | } 20 | -------------------------------------------------------------------------------- /iter/chain.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | func Chain(fs ...Func) Func { 4 | return func(cb Callback) { 5 | for _, f := range fs { 6 | if !All(cb, f) { 7 | break 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iter/func.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | // Callback receives a value and returns true if another value should be 4 | // received or false to stop iteration. 5 | type Callback func(value interface{}) (more bool) 6 | 7 | // Func iterates by calling Callback for each of its values. 8 | type Func func(Callback) 9 | 10 | func All(cb Callback, fs ...Func) bool { 11 | for _, f := range fs { 12 | all := true 13 | f(func(v interface{}) bool { 14 | all = all && cb(v) 15 | return all 16 | }) 17 | if !all { 18 | return false 19 | } 20 | } 21 | return true 22 | } 23 | 24 | // Calls `cb` with the first value yielded by `f` and then stops iteration. `ok` if `cb` was called 25 | // with a value. Returning the value interface{} would require the caller to keep a 26 | func First(f Func) (value interface{}, ok bool) { 27 | f(func(x interface{}) bool { 28 | value = x 29 | ok = true 30 | return false 31 | }) 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /iter/groupby.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | type groupBy struct { 4 | curKey interface{} 5 | curKeyOk bool 6 | curValue interface{} 7 | keyFunc func(interface{}) interface{} 8 | input Iterator 9 | groupKey interface{} 10 | groupKeyOk bool 11 | } 12 | 13 | type Group interface { 14 | Iterator 15 | Key() interface{} 16 | } 17 | 18 | type group struct { 19 | gb *groupBy 20 | key interface{} 21 | first bool 22 | stopped bool 23 | } 24 | 25 | func (me *group) Stop() { 26 | me.stopped = true 27 | } 28 | 29 | func (me *group) Next() (ok bool) { 30 | if me.stopped { 31 | return false 32 | } 33 | if me.first { 34 | me.first = false 35 | return true 36 | } 37 | me.gb.advance() 38 | if !me.gb.curKeyOk || me.gb.curKey != me.key { 39 | me.Stop() 40 | return 41 | } 42 | ok = true 43 | return 44 | } 45 | 46 | func (me group) Value() (ret interface{}) { 47 | if me.stopped { 48 | panic("iterator stopped") 49 | } 50 | ret = me.gb.curValue 51 | return 52 | } 53 | 54 | func (me group) Key() interface{} { 55 | return me.key 56 | } 57 | 58 | func (me *groupBy) advance() { 59 | me.curKeyOk = me.input.Next() 60 | if me.curKeyOk { 61 | me.curValue = me.input.Value() 62 | me.curKey = me.keyFunc(me.curValue) 63 | } 64 | } 65 | 66 | func (me *groupBy) Next() (ok bool) { 67 | for me.curKey == me.groupKey { 68 | ok = me.input.Next() 69 | if !ok { 70 | return 71 | } 72 | me.curValue = me.input.Value() 73 | me.curKey = me.keyFunc(me.curValue) 74 | me.curKeyOk = true 75 | } 76 | me.groupKey = me.curKey 77 | me.groupKeyOk = true 78 | return true 79 | } 80 | 81 | func (me *groupBy) Value() (ret interface{}) { 82 | return &group{me, me.groupKey, true, false} 83 | } 84 | 85 | func (me *groupBy) Stop() { 86 | } 87 | 88 | // Allows use of nil as a return from the key func. 89 | var uniqueKey = new(int) 90 | 91 | // Group by returns an iterator of iterators over the values of the input 92 | // iterator that consecutively return the same value when input to the key 93 | // function. Note that repeated calls to each value of the GroupBy Iterator 94 | // does not return a new iterator over the values for that key. 95 | func GroupBy(input Iterator, keyFunc func(interface{}) interface{}) Iterator { 96 | if keyFunc == nil { 97 | keyFunc = func(a interface{}) interface{} { return a } 98 | } 99 | return &groupBy{ 100 | input: input, 101 | keyFunc: keyFunc, 102 | groupKey: uniqueKey, 103 | curKey: uniqueKey, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /iter/groupby_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/anacrolix/missinggo/slices" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 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 | } 48 | -------------------------------------------------------------------------------- /iter/head.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | func Head(n int, f Func) Func { 4 | return func(cb Callback) { 5 | if n <= 0 { 6 | return 7 | } 8 | f(func(v interface{}) bool { 9 | n-- 10 | if !cb(v) { 11 | return false 12 | } 13 | return n > 0 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iter/iterable.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/anacrolix/missinggo/v2" 7 | ) 8 | 9 | type Iterable interface { 10 | Iter(Callback) 11 | } 12 | 13 | type iterator struct { 14 | it Iterable 15 | ch chan interface{} 16 | value interface{} 17 | ok bool 18 | mu sync.Mutex 19 | stopped missinggo.Event 20 | } 21 | 22 | func NewIterator(it Iterable) (ret *iterator) { 23 | ret = &iterator{ 24 | it: it, 25 | ch: make(chan interface{}), 26 | } 27 | go func() { 28 | // Have to do this in a goroutine, because the interface is synchronous. 29 | it.Iter(func(value interface{}) bool { 30 | select { 31 | case ret.ch <- value: 32 | return true 33 | case <-ret.stopped.LockedChan(&ret.mu): 34 | return false 35 | } 36 | }) 37 | close(ret.ch) 38 | ret.mu.Lock() 39 | ret.stopped.Set() 40 | ret.mu.Unlock() 41 | }() 42 | return 43 | } 44 | 45 | func (me *iterator) Value() interface{} { 46 | if !me.ok { 47 | panic("no value") 48 | } 49 | return me.value 50 | } 51 | 52 | func (me *iterator) Next() bool { 53 | me.value, me.ok = <-me.ch 54 | return me.ok 55 | } 56 | 57 | func (me *iterator) Stop() { 58 | me.mu.Lock() 59 | me.stopped.Set() 60 | me.mu.Unlock() 61 | } 62 | 63 | func IterableAsSlice(it Iterable) (ret []interface{}) { 64 | it.Iter(func(value interface{}) bool { 65 | ret = append(ret, value) 66 | return true 67 | }) 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /iter/iterator.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import "github.com/anacrolix/missinggo/slices" 4 | 5 | type Iterator interface { 6 | // Advances to the next value. Returns false if there are no more values. 7 | // Must be called before the first value. 8 | Next() bool 9 | // Returns the current value. Should panic when the iterator is in an 10 | // invalid state. 11 | Value() interface{} 12 | // Ceases iteration prematurely. This should occur implicitly if Next 13 | // returns false. 14 | Stop() 15 | } 16 | 17 | func ToFunc(it Iterator) Func { 18 | return func(cb Callback) { 19 | defer it.Stop() 20 | for it.Next() { 21 | if !cb(it.Value()) { 22 | break 23 | } 24 | } 25 | } 26 | } 27 | 28 | type sliceIterator struct { 29 | slice []interface{} 30 | value interface{} 31 | ok bool 32 | } 33 | 34 | func (me *sliceIterator) Next() bool { 35 | if len(me.slice) == 0 { 36 | return false 37 | } 38 | me.value = me.slice[0] 39 | me.slice = me.slice[1:] 40 | me.ok = true 41 | return true 42 | } 43 | 44 | func (me *sliceIterator) Value() interface{} { 45 | if !me.ok { 46 | panic("no value; call Next") 47 | } 48 | return me.value 49 | } 50 | 51 | func (me *sliceIterator) Stop() {} 52 | 53 | func Slice(a []interface{}) Iterator { 54 | return &sliceIterator{ 55 | slice: a, 56 | } 57 | } 58 | 59 | func StringIterator(a string) Iterator { 60 | return Slice(slices.ToEmptyInterface(a)) 61 | } 62 | 63 | func ToSlice(f Func) (ret []interface{}) { 64 | f(func(v interface{}) bool { 65 | ret = append(ret, v) 66 | return true 67 | }) 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /iter/iterator_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIterator(t *testing.T) { 10 | const s = "AAAABBBCCDAABBB" 11 | si := StringIterator(s) 12 | for i := range s { 13 | require.True(t, si.Next()) 14 | require.Equal(t, s[i], si.Value().(byte)) 15 | } 16 | require.False(t, si.Next()) 17 | } 18 | -------------------------------------------------------------------------------- /iter/iterutils.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import "math/rand" 4 | 5 | type seq struct { 6 | i []int 7 | } 8 | 9 | // Creates sequence of values from [0, n) 10 | func newSeq(n int) seq { 11 | return seq{make([]int, n, n)} 12 | } 13 | 14 | func (me seq) Index(i int) (ret int) { 15 | ret = me.i[i] 16 | if ret == 0 { 17 | ret = i 18 | } 19 | return 20 | } 21 | 22 | func (me seq) Len() int { 23 | return len(me.i) 24 | } 25 | 26 | // Remove the nth value from the sequence. 27 | func (me *seq) DeleteIndex(index int) { 28 | me.i[index] = me.Index(me.Len() - 1) 29 | me.i = me.i[:me.Len()-1] 30 | } 31 | 32 | func ForPerm(n int, callback func(i int) (more bool)) bool { 33 | s := newSeq(n) 34 | for s.Len() > 0 { 35 | r := rand.Intn(s.Len()) 36 | if !callback(s.Index(r)) { 37 | return false 38 | } 39 | s.DeleteIndex(r) 40 | } 41 | return true 42 | } 43 | -------------------------------------------------------------------------------- /iter/n.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import "github.com/bradfitz/iter" 4 | 5 | func N(n int) []struct{} { 6 | return iter.N(n) 7 | } 8 | -------------------------------------------------------------------------------- /jitter.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | // Returns random duration in the range [average-plusMinus, 9 | // average+plusMinus]. Negative plusMinus will likely panic. Be aware that if 10 | // plusMinus >= average, you may get a zero or negative Duration. The 11 | // distribution function is unspecified, in case I find a more appropriate one 12 | // in the future. 13 | func JitterDuration(average, plusMinus time.Duration) (ret time.Duration) { 14 | ret = average - plusMinus 15 | ret += time.Duration(rand.Int63n(2*int64(plusMinus) + 1)) 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /jitter_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestJitterDuration(t *testing.T) { 10 | assert.Zero(t, JitterDuration(0, 0)) 11 | assert.Panics(t, func() { JitterDuration(1, -1) }) 12 | } 13 | -------------------------------------------------------------------------------- /leaktest/goleaktest.go: -------------------------------------------------------------------------------- 1 | package leaktest 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | "github.com/bradfitz/iter" 9 | ) 10 | 11 | // Put defer GoroutineLeakCheck(t)() at the top of your test. Make sure the 12 | // goroutine count is steady before your test begins. 13 | func GoroutineLeakCheck(t testing.TB) func() { 14 | if !testing.Verbose() { 15 | return func() {} 16 | } 17 | numStart := runtime.NumGoroutine() 18 | return func() { 19 | var numNow int 20 | wait := time.Millisecond 21 | started := time.Now() 22 | for range iter.N(10) { // 1 second 23 | numNow = runtime.NumGoroutine() 24 | if numNow <= numStart { 25 | break 26 | } 27 | t.Logf("%d excess goroutines after %s", numNow-numStart, time.Since(started)) 28 | time.Sleep(wait) 29 | wait *= 2 30 | } 31 | // I'd print stacks, or treat this as fatal, but I think 32 | // runtime.NumGoroutine is including system routines for which we are 33 | // not provided the stacks, and are spawned unpredictably. 34 | t.Logf("have %d goroutines, started with %d", numNow, numStart) 35 | // select {} 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /limitlen.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | // Deprecated: Use b[:min(len(b), ...)]. 4 | // Sets an upper bound on the len of b. max can be any type 5 | // that will cast to int64. 6 | func LimitLen(b []byte, max ...interface{}) []byte { 7 | return b[:MinInt(len(b), max...)] 8 | } 9 | -------------------------------------------------------------------------------- /mime/mime.go: -------------------------------------------------------------------------------- 1 | package mime 2 | 3 | import "strings" 4 | 5 | type Type struct { 6 | Class string 7 | Specific string 8 | } 9 | 10 | func (t Type) String() string { 11 | return t.Class + "/" + t.Specific 12 | } 13 | 14 | func (t *Type) FromString(s string) { 15 | ss := strings.SplitN(s, "/", 1) 16 | t.Class = ss[0] 17 | t.Specific = ss[1] 18 | } 19 | -------------------------------------------------------------------------------- /minmax.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "reflect" 4 | 5 | // Deprecated: Use max builtin. 6 | func Max(_less interface{}, vals ...interface{}) interface{} { 7 | ret := reflect.ValueOf(vals[0]) 8 | retType := ret.Type() 9 | less := reflect.ValueOf(_less) 10 | for _, _v := range vals[1:] { 11 | v := reflect.ValueOf(_v).Convert(retType) 12 | out := less.Call([]reflect.Value{ret, v}) 13 | if out[0].Bool() { 14 | ret = v 15 | } 16 | } 17 | return ret.Interface() 18 | } 19 | 20 | // Deprecated: Use max builtin. 21 | func MaxInt(first int64, rest ...interface{}) int64 { 22 | return Max(func(l, r interface{}) bool { 23 | return l.(int64) < r.(int64) 24 | }, append([]interface{}{first}, rest...)...).(int64) 25 | } 26 | 27 | // Deprecated: Use min builtin. 28 | func MinInt(first interface{}, rest ...interface{}) int64 { 29 | ret := reflect.ValueOf(first).Int() 30 | for _, _i := range rest { 31 | i := reflect.ValueOf(_i).Int() 32 | if i < ret { 33 | ret = i 34 | } 35 | } 36 | return ret 37 | } 38 | -------------------------------------------------------------------------------- /monotonic.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // Monotonic time represents time since an arbitrary point in the past, where 9 | // the concept of now is only ever moving in a positive direction. 10 | type MonotonicTime struct { 11 | skewedStdTime time.Time 12 | } 13 | 14 | func (me MonotonicTime) Sub(other MonotonicTime) time.Duration { 15 | return me.skewedStdTime.Sub(other.skewedStdTime) 16 | } 17 | 18 | var ( 19 | stdNowFunc = time.Now 20 | monotonicMu sync.Mutex 21 | lastStdNow time.Time 22 | monotonicSkew time.Duration 23 | ) 24 | 25 | func skewedStdNow() time.Time { 26 | monotonicMu.Lock() 27 | defer monotonicMu.Unlock() 28 | stdNow := stdNowFunc() 29 | if !lastStdNow.IsZero() && stdNow.Before(lastStdNow) { 30 | monotonicSkew += lastStdNow.Sub(stdNow) 31 | } 32 | lastStdNow = stdNow 33 | return stdNow.Add(monotonicSkew) 34 | } 35 | 36 | // Consecutive calls always produce the same or greater time than previous 37 | // calls. 38 | func MonotonicNow() MonotonicTime { 39 | return MonotonicTime{skewedStdNow()} 40 | } 41 | 42 | func MonotonicSince(since MonotonicTime) (ret time.Duration) { 43 | return skewedStdNow().Sub(since.skewedStdTime) 44 | } 45 | -------------------------------------------------------------------------------- /monotonic_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // Calls suite with the used time.Now function used by MonotonicNow replaced 11 | // with stdNow for the duration of the call. 12 | func withCustomStdNow(stdNow func() time.Time, suite func()) { 13 | oldStdNow := stdNowFunc 14 | oldSkew := monotonicSkew 15 | defer func() { 16 | stdNowFunc = oldStdNow 17 | monotonicSkew = oldSkew 18 | }() 19 | stdNowFunc = stdNow 20 | suite() 21 | } 22 | 23 | // Returns a time.Now-like function that walks seq returning time.Unix(0, 24 | // seq[i]) in successive calls. 25 | func stdNowSeqFunc(seq []int64) func() time.Time { 26 | var i int 27 | return func() time.Time { 28 | defer func() { i++ }() 29 | return time.Unix(0, seq[i]) 30 | } 31 | } 32 | 33 | func TestMonotonicTime(t *testing.T) { 34 | started := MonotonicNow() 35 | withCustomStdNow(stdNowSeqFunc([]int64{2, 1, 3, 3, 2, 3}), func() { 36 | i0 := MonotonicNow() // 0 37 | i1 := MonotonicNow() // 1 38 | assert.EqualValues(t, 0, i0.Sub(i1)) 39 | assert.EqualValues(t, 2, MonotonicSince(i0)) // 2 40 | assert.EqualValues(t, 2, MonotonicSince(i1)) // 3 41 | i4 := MonotonicNow() 42 | assert.EqualValues(t, 2, i4.Sub(i0)) 43 | assert.EqualValues(t, 2, i4.Sub(i1)) 44 | i5 := MonotonicNow() 45 | assert.EqualValues(t, 3, i5.Sub(i0)) 46 | assert.EqualValues(t, 3, i5.Sub(i1)) 47 | assert.EqualValues(t, 1, i5.Sub(i4)) 48 | }) 49 | // Ensure that skew and time function are restored correctly and within 50 | // reasonable bounds. 51 | assert.True(t, MonotonicSince(started) >= 0 && MonotonicSince(started) < time.Second) 52 | } 53 | -------------------------------------------------------------------------------- /multiless.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | type ( 4 | // A function that returns equality, and less than. Used for lazily evaluating arguments. 5 | SameLessFunc func() (same, less bool) 6 | // A helper for long chains of "less-than" comparisons, where later comparisons are only 7 | // required if earlier ones haven't resolved the comparison. 8 | MultiLess struct { 9 | ok bool 10 | less bool 11 | } 12 | ) 13 | 14 | // True iff the left is less than the right. Will return false if they're equal, or unresolved. 15 | // (Which is okay in certain circumstances.) 16 | func (me *MultiLess) Less() bool { 17 | return me.ok && me.less 18 | } 19 | 20 | // Returns the result of the less-than comparison chains. Panics if the case was not resolved. 21 | func (me *MultiLess) Final() bool { 22 | if !me.ok { 23 | panic("undetermined") 24 | } 25 | return me.less 26 | } 27 | 28 | // Returns less-than, and whether the comparison was definitely resolved. 29 | func (me *MultiLess) FinalOk() (left, ok bool) { 30 | return me.less, me.ok 31 | } 32 | 33 | // `f` is only evaluated if the result is not yet determined. 34 | func (me *MultiLess) Next(f SameLessFunc) { 35 | if me.ok { 36 | return 37 | } 38 | same, less := f() 39 | if same { 40 | return 41 | } 42 | me.ok = true 43 | me.less = less 44 | } 45 | 46 | // Like Next, but the arguments are already evaluated. 47 | func (me *MultiLess) StrictNext(same, less bool) { 48 | me.Next(func() (bool, bool) { return same, less }) 49 | } 50 | 51 | // Compare booleans, where the lesser is the true one, if the other is false. 52 | func (me *MultiLess) NextBool(l, r bool) { 53 | me.StrictNext(l == r, l) 54 | } 55 | 56 | // Next use a common comparison result, where < 0 is less and 0 is equal. 57 | func (me *MultiLess) Compare(i int) { 58 | me.StrictNext(i == 0, i < 0) 59 | } 60 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "strings" 4 | 5 | func IsAddrInUse(err error) bool { 6 | return strings.Contains(err.Error(), "address already in use") 7 | } 8 | -------------------------------------------------------------------------------- /oauth/endpoints.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | type Endpoint struct { 4 | AuthURL string 5 | TokenURL string 6 | ProfileURL string 7 | } 8 | 9 | var ( 10 | FacebookEndpoint = Endpoint{ 11 | AuthURL: "https://www.facebook.com/dialog/oauth", 12 | TokenURL: "https://graph.facebook.com/v2.3/oauth/access_token", 13 | ProfileURL: "https://graph.facebook.com/me", 14 | } 15 | GoogleEndpoint = Endpoint{ 16 | AuthURL: "https://accounts.google.com/o/oauth2/auth", 17 | TokenURL: "https://accounts.google.com/o/oauth2/token", 18 | ProfileURL: "https://www.googleapis.com/oauth2/v2/userinfo", 19 | } 20 | PatreonEndpoint = Endpoint{ 21 | AuthURL: "https://www.patreon.com/oauth2/authorize", 22 | TokenURL: "https://api.patreon.com/oauth2/token", 23 | ProfileURL: "https://api.patreon.com/oauth2/api/current_user", 24 | } 25 | ) 26 | -------------------------------------------------------------------------------- /oauth/oauth.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/anacrolix/missinggo/patreon" 12 | ) 13 | 14 | func SimpleParser(r *http.Response) (UserProfile, error) { 15 | var sup simpleUserProfile 16 | err := json.NewDecoder(r.Body).Decode(&sup) 17 | return sup, err 18 | } 19 | 20 | type Provider struct { 21 | Client *Client 22 | Endpoint *Endpoint 23 | } 24 | 25 | type Wrapper struct { 26 | Scope string 27 | Provider Provider 28 | ProfileParser func(*http.Response) (UserProfile, error) 29 | } 30 | 31 | func (me Wrapper) GetAuthURL(redirectURI, state string) string { 32 | return me.Provider.GetAuthURL(redirectURI, state, me.Scope) 33 | } 34 | 35 | func (me Wrapper) FetchUser(accessToken string) (up UserProfile, err error) { 36 | resp, err := me.Provider.FetchUser(accessToken) 37 | if err != nil { 38 | return 39 | } 40 | defer resp.Body.Close() 41 | return me.ProfileParser(resp) 42 | } 43 | 44 | type Client struct { 45 | ID string 46 | Secret string 47 | } 48 | 49 | func (me *Provider) GetAuthURL(redirectURI, state, scope string) string { 50 | params := []string{ 51 | "client_id", me.Client.ID, 52 | "response_type", "code", 53 | "redirect_uri", redirectURI, 54 | "state", state, 55 | // This will ask again for the given scopes if they're not provided. 56 | "auth_type", "rerequest", 57 | } 58 | if scope != "" { 59 | params = append(params, "scope", scope) 60 | } 61 | return renderEndpointURL(me.Endpoint.AuthURL, params...) 62 | } 63 | 64 | func (me *Provider) ExchangeCode(code string, redirectURI string) (accessToken string, err error) { 65 | v := url.Values{ 66 | "client_id": {me.Client.ID}, 67 | "redirect_uri": {redirectURI}, 68 | "client_secret": {me.Client.Secret}, 69 | "code": {code}, 70 | "grant_type": {"authorization_code"}, 71 | } 72 | resp, err := http.Post(me.Endpoint.TokenURL, "application/x-www-form-urlencoded", bytes.NewBufferString(v.Encode())) 73 | if err != nil { 74 | return 75 | } 76 | var buf bytes.Buffer 77 | io.Copy(&buf, resp.Body) 78 | resp.Body.Close() 79 | var msg map[string]interface{} 80 | err = json.NewDecoder(&buf).Decode(&msg) 81 | if err != nil { 82 | return 83 | } 84 | defer func() { 85 | r := recover() 86 | if r == nil { 87 | return 88 | } 89 | err = fmt.Errorf("bad access_token field in %q: %s", msg, r) 90 | }() 91 | accessToken = msg["access_token"].(string) 92 | return 93 | } 94 | 95 | type simpleUserProfile struct { 96 | Id string `json:"id"` 97 | EmailField string `json:"email"` 98 | } 99 | 100 | var _ UserProfile = simpleUserProfile{} 101 | 102 | func (me simpleUserProfile) IsEmailVerified() bool { 103 | return true 104 | } 105 | 106 | func (me simpleUserProfile) Email() string { 107 | return me.EmailField 108 | } 109 | 110 | type UserProfile interface { 111 | IsEmailVerified() bool 112 | Email() string 113 | } 114 | 115 | // TODO: Allow fields to be specified. 116 | func (me *Provider) FetchUser(accessToken string) (*http.Response, error) { 117 | return http.Get(renderEndpointURL( 118 | me.Endpoint.ProfileURL, 119 | "fields", "email", 120 | "access_token", accessToken, 121 | )) 122 | } 123 | 124 | type PatreonUserProfile struct { 125 | Data patreon.ApiUser `json:"data"` 126 | } 127 | 128 | var _ UserProfile = PatreonUserProfile{} 129 | 130 | func (me PatreonUserProfile) IsEmailVerified() bool { 131 | return me.Data.Attributes.IsEmailVerified 132 | } 133 | 134 | func (me PatreonUserProfile) Email() string { 135 | return me.Data.Attributes.Email 136 | } 137 | 138 | func renderEndpointURL(endpoint string, params ...string) string { 139 | u, err := url.Parse(endpoint) 140 | if err != nil { 141 | panic(err) 142 | } 143 | v := make(url.Values, len(params)/2) 144 | for i := 0; i < len(params); i += 2 { 145 | v.Set(params[i], params[i+1]) 146 | } 147 | u.RawQuery = v.Encode() 148 | return u.String() 149 | } 150 | -------------------------------------------------------------------------------- /oauth/oauth_test.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDecodePatreonUserProfile(t *testing.T) { 12 | var pup PatreonUserProfile 13 | err := json.Unmarshal([]byte( 14 | `{ 15 | "data": { 16 | "attributes": { 17 | "about": null, 18 | "created": "2017-05-12T12:49:31+00:00", 19 | "discord_id": null, 20 | "email": "anacrolix@gmail.com", 21 | "facebook": null, 22 | "facebook_id": "10155425587018447", 23 | "first_name": "Matt", 24 | "full_name": "Matt Joiner", 25 | "gender": 0, 26 | "has_password": false, 27 | "image_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?t=2145916800&w=400&v=506YL5JlU7aaQH-QyEaRXyWoXFs4ia-vcSjjZuv-dXY%3D", 28 | "is_deleted": false, 29 | "is_email_verified": true, 30 | "is_nuked": false, 31 | "is_suspended": false, 32 | "last_name": "Joiner", 33 | "social_connections": { 34 | "deviantart": null, 35 | "discord": null, 36 | "facebook": null, 37 | "spotify": null, 38 | "twitch": null, 39 | "twitter": null, 40 | "youtube": null 41 | }, 42 | "thumb_url": "https://c3.patreon.com/2/patreon-user/wS20eHsYaLMqJeDyL5wyK0egvcXDRNdT28JvjeREJ5T80te19Cmn1YZxZyzd2qab.jpeg?h=100&t=2145916800&w=100&v=SI72bzI4XB5mX0dyfqeZ-Nn4BNTz9FYRSgZ8pLipARg%3D", 43 | "twitch": null, 44 | "twitter": null, 45 | "url": "https://www.patreon.com/anacrolix", 46 | "vanity": "anacrolix", 47 | "youtube": null 48 | }, 49 | "id": "6126463", 50 | "relationships": { 51 | "pledges": { 52 | "data": [] 53 | } 54 | }, 55 | "type": "user" 56 | }, 57 | "links": { 58 | "self": "https://api.patreon.com/user/6126463" 59 | } 60 | }`), &pup) 61 | require.NoError(t, err) 62 | assert.EqualValues(t, "anacrolix@gmail.com", pup.Data.Attributes.Email) 63 | assert.True(t, pup.Data.Attributes.IsEmailVerified) 64 | } 65 | -------------------------------------------------------------------------------- /openflags.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | const O_ACCMODE = os.O_RDONLY | os.O_WRONLY | os.O_RDWR 8 | -------------------------------------------------------------------------------- /orderedmap/google_btree.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import ( 4 | "github.com/anacrolix/missinggo/iter" 5 | "github.com/google/btree" 6 | ) 7 | 8 | type GoogleBTree struct { 9 | bt *btree.BTree 10 | lesser func(l, r interface{}) bool 11 | } 12 | 13 | type googleBTreeItem struct { 14 | less func(l, r interface{}) bool 15 | key interface{} 16 | value interface{} 17 | } 18 | 19 | func (me googleBTreeItem) Less(right btree.Item) bool { 20 | return me.less(me.key, right.(googleBTreeItem).key) 21 | } 22 | 23 | func NewGoogleBTree(lesser func(l, r interface{}) bool) *GoogleBTree { 24 | return &GoogleBTree{ 25 | bt: btree.New(32), 26 | lesser: lesser, 27 | } 28 | } 29 | 30 | func (me *GoogleBTree) Set(key interface{}, value interface{}) { 31 | me.bt.ReplaceOrInsert(googleBTreeItem{me.lesser, key, value}) 32 | } 33 | 34 | func (me *GoogleBTree) Get(key interface{}) interface{} { 35 | ret, _ := me.GetOk(key) 36 | return ret 37 | } 38 | 39 | func (me *GoogleBTree) GetOk(key interface{}) (interface{}, bool) { 40 | item := me.bt.Get(googleBTreeItem{me.lesser, key, nil}) 41 | if item == nil { 42 | return nil, false 43 | } 44 | return item.(googleBTreeItem).value, true 45 | } 46 | 47 | type googleBTreeIter struct { 48 | i btree.Item 49 | bt *btree.BTree 50 | } 51 | 52 | func (me *googleBTreeIter) Next() bool { 53 | if me.bt == nil { 54 | return false 55 | } 56 | if me.i == nil { 57 | me.bt.Ascend(func(i btree.Item) bool { 58 | me.i = i 59 | return false 60 | }) 61 | } else { 62 | var n int 63 | me.bt.AscendGreaterOrEqual(me.i, func(i btree.Item) bool { 64 | n++ 65 | if n == 1 { 66 | return true 67 | } 68 | me.i = i 69 | return false 70 | }) 71 | if n != 2 { 72 | me.i = nil 73 | } 74 | } 75 | return me.i != nil 76 | } 77 | 78 | func (me *googleBTreeIter) Value() interface{} { 79 | return me.i.(googleBTreeItem).value 80 | } 81 | 82 | func (me *googleBTreeIter) Stop() { 83 | me.bt = nil 84 | me.i = nil 85 | } 86 | 87 | func (me *GoogleBTree) Iter(f iter.Callback) { 88 | me.bt.Ascend(func(i btree.Item) bool { 89 | return f(i.(googleBTreeItem).key) 90 | }) 91 | } 92 | 93 | func (me *GoogleBTree) Unset(key interface{}) { 94 | me.bt.Delete(googleBTreeItem{me.lesser, key, nil}) 95 | } 96 | 97 | func (me *GoogleBTree) Len() int { 98 | return me.bt.Len() 99 | } 100 | -------------------------------------------------------------------------------- /orderedmap/orderedmap.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import "github.com/anacrolix/missinggo/iter" 4 | 5 | func New(lesser func(l, r interface{}) bool) OrderedMap { 6 | return NewGoogleBTree(lesser) 7 | } 8 | 9 | type OrderedMap interface { 10 | Get(key interface{}) interface{} 11 | GetOk(key interface{}) (interface{}, bool) 12 | iter.Iterable 13 | Set(key, value interface{}) 14 | Unset(key interface{}) 15 | Len() int 16 | } 17 | -------------------------------------------------------------------------------- /orderedmap/orderedmap_test.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/anacrolix/missinggo/iter" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func slice(om OrderedMap) (ret []interface{}) { 11 | om.Iter(func(i interface{}) bool { 12 | ret = append(ret, om.Get(i)) 13 | return true 14 | }) 15 | return 16 | } 17 | 18 | func TestSimple(t *testing.T) { 19 | om := New(func(l, r interface{}) bool { 20 | return l.(int) < r.(int) 21 | }) 22 | om.Set(3, 1) 23 | om.Set(2, 2) 24 | om.Set(1, 3) 25 | assert.EqualValues(t, []interface{}{3, 2, 1}, slice(om)) 26 | om.Set(3, 2) 27 | om.Unset(2) 28 | assert.EqualValues(t, []interface{}{3, 2}, slice(om)) 29 | om.Set(-1, 4) 30 | assert.EqualValues(t, []interface{}{4, 3, 2}, slice(om)) 31 | } 32 | 33 | func TestIterEmpty(t *testing.T) { 34 | om := New(nil) 35 | it := iter.NewIterator(om) 36 | assert.Panics(t, func() { it.Value() }) 37 | assert.False(t, it.Next()) 38 | it.Stop() 39 | } 40 | -------------------------------------------------------------------------------- /orderedmap/skiplist.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import "github.com/ryszard/goskiplist/skiplist" 4 | 5 | type skiplistOrderedMap struct { 6 | sl *skiplist.SkipList 7 | } 8 | 9 | func NewSkipList(lesser func(l, r interface{}) bool) *skiplistOrderedMap { 10 | return &skiplistOrderedMap{skiplist.NewCustomMap(lesser)} 11 | } 12 | 13 | func (me *skiplistOrderedMap) Set(key interface{}, value interface{}) { 14 | me.sl.Set(key, value) 15 | } 16 | 17 | func (me *skiplistOrderedMap) Get(key interface{}) interface{} { 18 | if me == nil { 19 | return nil 20 | } 21 | ret, _ := me.sl.Get(key) 22 | return ret 23 | } 24 | 25 | func (me *skiplistOrderedMap) GetOk(key interface{}) (interface{}, bool) { 26 | if me == nil { 27 | return nil, false 28 | } 29 | return me.sl.Get(key) 30 | } 31 | 32 | type Iter struct { 33 | it skiplist.Iterator 34 | } 35 | 36 | func (me *Iter) Next() bool { 37 | if me == nil { 38 | return false 39 | } 40 | return me.it.Next() 41 | } 42 | 43 | func (me *Iter) Value() interface{} { 44 | return me.it.Value() 45 | } 46 | 47 | func (me *skiplistOrderedMap) Iter() *Iter { 48 | if me == nil { 49 | return nil 50 | } 51 | return &Iter{me.sl.Iterator()} 52 | } 53 | 54 | func (me *skiplistOrderedMap) Unset(key interface{}) { 55 | if me == nil { 56 | return 57 | } 58 | me.sl.Delete(key) 59 | } 60 | 61 | func (me *skiplistOrderedMap) Len() int { 62 | if me.sl == nil { 63 | return 0 64 | } 65 | return me.sl.Len() 66 | } 67 | -------------------------------------------------------------------------------- /panicif/panicif.go: -------------------------------------------------------------------------------- 1 | package panicif 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/exp/constraints" 6 | "reflect" 7 | ) 8 | 9 | func isNil(x any) (ret bool) { 10 | if x == nil { 11 | return true 12 | } 13 | defer func() { 14 | r := recover() 15 | if r == nil { 16 | return 17 | } 18 | var herp *reflect.ValueError 19 | herp, ok := r.(*reflect.ValueError) 20 | if !ok { 21 | panic(r) 22 | } 23 | if herp.Method != "reflect.Value.IsNil" { 24 | panic(r) 25 | } 26 | }() 27 | return reflect.ValueOf(x).IsNil() 28 | } 29 | 30 | func NotNil(x any) { 31 | if !isNil(x) { 32 | panic(x) 33 | } 34 | } 35 | 36 | func Nil(a any) { 37 | if a == nil { 38 | panic(a) 39 | } 40 | } 41 | 42 | func Err(err error) { 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func NotEq[T comparable](a, b T) { 49 | if a != b { 50 | panic(fmt.Sprintf("%v != %v", a, b)) 51 | } 52 | } 53 | 54 | func Eq[T comparable](a, b T) { 55 | if a == b { 56 | panic(fmt.Sprintf("%v == %v", a, b)) 57 | } 58 | } 59 | 60 | func True(x bool) { 61 | if x { 62 | panic(x) 63 | } 64 | } 65 | 66 | func False(x bool) { 67 | if !x { 68 | panic(x) 69 | } 70 | } 71 | 72 | func SendBlocks[T any](ch chan<- T, t T) { 73 | select { 74 | case ch <- t: 75 | default: 76 | panic("send blocked") 77 | } 78 | } 79 | 80 | func GreaterThan[T constraints.Ordered](a, b T) { 81 | if a > b { 82 | panic(fmt.Sprintf("%v > %v", a, b)) 83 | } 84 | } 85 | 86 | func LessThan[T constraints.Ordered](a, b T) { 87 | if a < b { 88 | panic(fmt.Sprintf("%v < %v", a, b)) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /panicif/panicif_test.go: -------------------------------------------------------------------------------- 1 | package panicif 2 | 3 | import ( 4 | qt "github.com/frankban/quicktest" 5 | "syscall" 6 | "testing" 7 | ) 8 | 9 | func TestUintptrNotNil(t *testing.T) { 10 | var err error = syscall.Errno(0) 11 | c := qt.New(t) 12 | c.Assert(func() { NotNil(err) }, qt.PanicMatches, "errno 0") 13 | NotNil(nil) 14 | NotNil((*int)(nil)) 15 | var i int 16 | c.Assert(func() { NotNil(&i) }, qt.PanicMatches, "0x.*") 17 | err = nil 18 | NotNil(err) 19 | var m map[int]int 20 | NotNil(err) 21 | m = make(map[int]int) 22 | c.Assert(func() { NotNil(m) }, qt.PanicMatches, `map\[\]`) 23 | } 24 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | // Splits the pathname p into Root and Ext, such that Root+Ext==p. 9 | func PathSplitExt(p string) (ret struct { 10 | Root, Ext string 11 | }) { 12 | ret.Ext = path.Ext(p) 13 | ret.Root = p[:len(p)-len(ret.Ext)] 14 | return 15 | } 16 | 17 | func FilePathExists(p string) bool { 18 | _, err := os.Stat(p) 19 | return err == nil 20 | } 21 | -------------------------------------------------------------------------------- /path_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExamplePathSplitExt() { 8 | fmt.Printf("%q\n", PathSplitExt(".cshrc")) 9 | fmt.Printf("%q\n", PathSplitExt("dir/a.ext")) 10 | fmt.Printf("%q\n", PathSplitExt("dir/.rc")) 11 | fmt.Printf("%q\n", PathSplitExt("home/.secret/file")) 12 | // Output: 13 | // {"" ".cshrc"} 14 | // {"dir/a" ".ext"} 15 | // {"dir/" ".rc"} 16 | // {"home/.secret/file" ""} 17 | } 18 | -------------------------------------------------------------------------------- /patreon/patreon.go: -------------------------------------------------------------------------------- 1 | package patreon 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | type PledgesApiResponse struct { 11 | Pledges []struct { 12 | Attributes struct { 13 | AmountCents int `json:"amount_cents"` 14 | } `json:"attributes"` 15 | Relationships struct { 16 | Patron struct { 17 | Data struct { 18 | Id Id `json:"id"` 19 | } `json:"data"` 20 | } `json:"patron"` 21 | } `json:"relationships"` 22 | } `json:"data"` 23 | Included []ApiUser `json:"included"` 24 | } 25 | 26 | type ApiUser struct { 27 | Attributes struct { 28 | Email string `json:"email"` 29 | IsEmailVerified bool `json:"is_email_verified"` 30 | } `json:"attributes"` 31 | Id Id `json:"id"` 32 | } 33 | 34 | type Pledge struct { 35 | Email string 36 | EmailVerified bool 37 | AmountCents int 38 | } 39 | 40 | type Id string 41 | 42 | func makeUserMap(par *PledgesApiResponse) (ret map[Id]*ApiUser) { 43 | ret = make(map[Id]*ApiUser, len(par.Included)) 44 | for i := range par.Included { 45 | au := &par.Included[i] 46 | ret[au.Id] = au 47 | } 48 | return 49 | } 50 | 51 | func ParsePledgesApiResponse(r io.Reader) (ps []Pledge, err error) { 52 | var ar PledgesApiResponse 53 | err = json.NewDecoder(r).Decode(&ar) 54 | if err != nil { 55 | return 56 | } 57 | userMap := makeUserMap(&ar) 58 | for _, p := range ar.Pledges { 59 | u := userMap[p.Relationships.Patron.Data.Id] 60 | ps = append(ps, Pledge{ 61 | Email: u.Attributes.Email, 62 | EmailVerified: u.Attributes.IsEmailVerified, 63 | AmountCents: p.Attributes.AmountCents, 64 | }) 65 | } 66 | return 67 | } 68 | 69 | func GetCampaignPledges(campaign Id, userAccessToken string) (ret []Pledge, err error) { 70 | req, err := http.NewRequest("GET", fmt.Sprintf("https://api.patreon.com/oauth2/api/campaigns/%s/pledges", campaign), nil) 71 | if err != nil { 72 | return 73 | } 74 | req.Header.Set("Authorization", "Bearer "+userAccessToken) 75 | resp, err := http.DefaultClient.Do(req) 76 | if err != nil { 77 | return 78 | } 79 | defer resp.Body.Close() 80 | if resp.StatusCode != 200 { 81 | err = fmt.Errorf("got http response code %d", resp.StatusCode) 82 | return 83 | } 84 | return ParsePledgesApiResponse(resp.Body) 85 | } 86 | -------------------------------------------------------------------------------- /patreon/patreon_test.go: -------------------------------------------------------------------------------- 1 | package patreon 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestParsePledges(t *testing.T) { 12 | f, err := os.Open("testdata/pledges") 13 | require.NoError(t, err) 14 | defer f.Close() 15 | ps, err := ParsePledgesApiResponse(f) 16 | require.NoError(t, err) 17 | assert.EqualValues(t, []Pledge{{ 18 | Email: "yonhyaro@gmail.com", 19 | EmailVerified: true, 20 | AmountCents: 200, 21 | }}, ps) 22 | } 23 | -------------------------------------------------------------------------------- /perf/event.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "math" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Event struct { 10 | Mu sync.RWMutex 11 | Count int64 12 | Total time.Duration 13 | Min time.Duration 14 | Max time.Duration 15 | } 16 | 17 | func (e *Event) Add(t time.Duration) { 18 | e.Mu.Lock() 19 | defer e.Mu.Unlock() 20 | if t > e.Max { 21 | e.Max = t 22 | } 23 | if t < e.Min { 24 | e.Min = t 25 | } 26 | e.Count++ 27 | e.Total += t 28 | } 29 | 30 | func (e *Event) MeanTime() time.Duration { 31 | e.Mu.RLock() 32 | defer e.Mu.RUnlock() 33 | return e.Total / time.Duration(e.Count) 34 | } 35 | 36 | func (e *Event) Init() { 37 | e.Min = math.MaxInt64 38 | } 39 | -------------------------------------------------------------------------------- /perf/events.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "sort" 8 | "sync" 9 | "text/tabwriter" 10 | ) 11 | 12 | var ( 13 | mu sync.RWMutex 14 | events = map[string]*Event{} 15 | ) 16 | 17 | func init() { 18 | http.HandleFunc("/debug/perf", func(w http.ResponseWriter, r *http.Request) { 19 | w.Header().Set("Content-Type", "text/plain; charset=UTF-8") 20 | switch r.FormValue("sort") { 21 | case "desc": 22 | writeEventsTableCustomSort(w, func(l, r NamedEvent) bool { 23 | return l.Name < r.Name 24 | }) 25 | default: 26 | WriteEventsTable(w) 27 | } 28 | }) 29 | } 30 | 31 | type NamedEvent struct { 32 | Name string 33 | Event 34 | } 35 | 36 | func WriteEventsTable(w io.Writer) { 37 | writeEventsTableCustomSort(w, func(l, r NamedEvent) bool { 38 | return l.Total > r.Total 39 | }) 40 | } 41 | 42 | func writeEventsTableCustomSort(w io.Writer, less func(l, r NamedEvent) bool) { 43 | tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) 44 | fmt.Fprint(tw, "description\ttotal\tcount\tmin\tmean\tmax\n") 45 | mu.RLock() 46 | es := make([]NamedEvent, 0, len(events)) 47 | for d, e := range events { 48 | e.Mu.RLock() 49 | es = append(es, NamedEvent{d, *e}) 50 | e.Mu.RUnlock() 51 | } 52 | mu.RUnlock() 53 | sort.Slice(es, func(i, j int) bool { 54 | return less(es[i], es[j]) 55 | }) 56 | for _, ne := range es { 57 | fmt.Fprintf(tw, "%s\t%v\t%v\t%v\t%v\t%v\n", ne.Name, ne.Total, ne.Count, ne.Min, ne.MeanTime(), ne.Max) 58 | } 59 | tw.Flush() 60 | } 61 | -------------------------------------------------------------------------------- /perf/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anacrolix/missinggo/perf 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/anacrolix/envpprof v1.1.0 7 | github.com/anacrolix/missinggo/v2 v2.7.4 8 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 9 | ) 10 | -------------------------------------------------------------------------------- /perf/mutex.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/anacrolix/missinggo/v2" 7 | ) 8 | 9 | type TimedLocker struct { 10 | L sync.Locker 11 | Desc string 12 | } 13 | 14 | func (me *TimedLocker) Lock() { 15 | tr := NewTimer() 16 | me.L.Lock() 17 | tr.Mark(me.Desc) 18 | } 19 | 20 | func (me *TimedLocker) Unlock() { 21 | me.L.Unlock() 22 | } 23 | 24 | type TimedRWLocker struct { 25 | RWL missinggo.RWLocker 26 | WriteDesc string 27 | ReadDesc string 28 | } 29 | 30 | func (me *TimedRWLocker) Lock() { 31 | tr := NewTimer() 32 | me.RWL.Lock() 33 | tr.Mark(me.WriteDesc) 34 | } 35 | 36 | func (me *TimedRWLocker) Unlock() { 37 | me.RWL.Unlock() 38 | } 39 | 40 | func (me *TimedRWLocker) RLock() { 41 | tr := NewTimer() 42 | me.RWL.RLock() 43 | tr.Mark(me.ReadDesc) 44 | } 45 | 46 | func (me *TimedRWLocker) RUnlock() { 47 | me.RWL.RUnlock() 48 | } 49 | -------------------------------------------------------------------------------- /perf/perf_test.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "io/ioutil" 5 | "strconv" 6 | "testing" 7 | 8 | _ "github.com/anacrolix/envpprof" 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 | } 32 | -------------------------------------------------------------------------------- /perf/scope.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | func ScopeTimer(opts ...timerOpt) func() { 8 | t := NewTimer(CallerName(1)) 9 | return func() { t.Mark("returned") } 10 | } 11 | 12 | func ScopeTimerOk(ok *bool) func() { 13 | t := NewTimer(CallerName(1)) 14 | return func() { t.MarkOk(*ok) } 15 | } 16 | 17 | func ScopeTimerErr(err *error) func() { 18 | t := NewTimer(CallerName(1)) 19 | return func() { 20 | r := recover() 21 | if r != nil { 22 | t.Mark("panic") 23 | panic(r) 24 | } 25 | t.MarkErr(*err) 26 | } 27 | } 28 | 29 | func CallerName(skip int) timerOpt { 30 | return Name(getCallerName(skip)) 31 | } 32 | 33 | func getCallerName(skip int) string { 34 | var pc [1]uintptr 35 | runtime.Callers(3+skip, pc[:]) 36 | fs := runtime.CallersFrames(pc[:]) 37 | f, _ := fs.Next() 38 | return f.Func.Name() 39 | } 40 | -------------------------------------------------------------------------------- /perf/timer.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | type Timer struct { 10 | started time.Time 11 | log bool 12 | name string 13 | marked bool 14 | } 15 | 16 | func NewTimer(opts ...timerOpt) (t *Timer) { 17 | t = &Timer{ 18 | started: time.Now(), 19 | } 20 | for _, o := range opts { 21 | o(t) 22 | } 23 | if t.log && t.name != "" { 24 | log.Printf("starting timer %q", t.name) 25 | } 26 | runtime.SetFinalizer(t, func(t *Timer) { 27 | if t.marked { 28 | return 29 | } 30 | log.Printf("timer %#v was never marked", t) 31 | }) 32 | return 33 | } 34 | 35 | type timerOpt func(*Timer) 36 | 37 | func Log(t *Timer) { 38 | t.log = true 39 | } 40 | 41 | func Name(name string) func(*Timer) { 42 | return func(t *Timer) { 43 | t.name = name 44 | } 45 | } 46 | 47 | func (t *Timer) Mark(events ...string) time.Duration { 48 | d := time.Since(t.started) 49 | if len(events) == 0 { 50 | if t.name == "" { 51 | panic("no name or events specified") 52 | } 53 | t.addDuration(t.name, d) 54 | } else { 55 | for _, e := range events { 56 | if t.name != "" { 57 | e = t.name + "/" + e 58 | } 59 | t.addDuration(e, d) 60 | } 61 | } 62 | return d 63 | } 64 | 65 | func (t *Timer) MarkOk(ok bool) { 66 | if ok { 67 | t.Mark("ok") 68 | } else { 69 | t.Mark("not ok") 70 | } 71 | } 72 | 73 | func (t *Timer) MarkErr(err error) { 74 | if err == nil { 75 | t.Mark("success") 76 | } else { 77 | t.Mark("error") 78 | } 79 | } 80 | 81 | func (t *Timer) addDuration(desc string, d time.Duration) { 82 | t.marked = true 83 | mu.RLock() 84 | e := events[desc] 85 | mu.RUnlock() 86 | if e == nil { 87 | mu.Lock() 88 | e = events[desc] 89 | if e == nil { 90 | e = new(Event) 91 | e.Init() 92 | events[desc] = e 93 | } 94 | mu.Unlock() 95 | } 96 | e.Add(d) 97 | if t.log { 98 | if t.name != "" { 99 | log.Printf("timer %q got event %q after %s", t.name, desc, d) 100 | } else { 101 | log.Printf("marking event %q after %s", desc, d) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pproffd/pproffd.go: -------------------------------------------------------------------------------- 1 | // Package pproffd is for detecting resource leaks due to unclosed handles. 2 | package pproffd 3 | 4 | import ( 5 | "io" 6 | "net" 7 | "os" 8 | "runtime/pprof" 9 | ) 10 | 11 | var enabled = func() bool { 12 | _, ok := os.LookupEnv("PPROFFD") 13 | return ok 14 | }() 15 | 16 | var p *pprof.Profile 17 | 18 | func init() { 19 | if enabled { 20 | p = pprof.NewProfile("fds") 21 | } 22 | } 23 | 24 | type fd int 25 | 26 | func (me *fd) Closed() { 27 | if enabled { 28 | p.Remove(me) 29 | } 30 | } 31 | 32 | func add(skip int) (ret *fd) { 33 | if enabled { 34 | ret = new(fd) 35 | p.Add(ret, skip+2) 36 | } 37 | return 38 | } 39 | 40 | type Wrapped interface { 41 | Wrapped() io.Closer 42 | } 43 | 44 | type CloseWrapper struct { 45 | fd *fd 46 | c io.Closer 47 | } 48 | 49 | func (me CloseWrapper) Wrapped() io.Closer { 50 | return me.c 51 | } 52 | 53 | func (me CloseWrapper) Close() error { 54 | me.fd.Closed() 55 | return me.c.Close() 56 | } 57 | 58 | func NewCloseWrapper(c io.Closer) CloseWrapper { 59 | // TODO: Check enabled? 60 | return CloseWrapper{ 61 | fd: add(2), 62 | c: c, 63 | } 64 | } 65 | 66 | type wrappedNetConn struct { 67 | net.Conn 68 | CloseWrapper 69 | } 70 | 71 | func (me wrappedNetConn) Close() error { 72 | return me.CloseWrapper.Close() 73 | } 74 | 75 | // Tracks a net.Conn until Close() is explicitly called. 76 | func WrapNetConn(nc net.Conn) net.Conn { 77 | if !enabled { 78 | return nc 79 | } 80 | if nc == nil { 81 | return nil 82 | } 83 | return wrappedNetConn{ 84 | nc, 85 | NewCloseWrapper(nc), 86 | } 87 | } 88 | 89 | type OSFile interface { 90 | io.Reader 91 | io.Seeker 92 | io.Closer 93 | io.Writer 94 | Stat() (os.FileInfo, error) 95 | io.ReaderAt 96 | io.WriterAt 97 | Wrapped 98 | } 99 | 100 | type wrappedOSFile struct { 101 | *os.File 102 | CloseWrapper 103 | } 104 | 105 | func (me wrappedOSFile) Close() error { 106 | return me.CloseWrapper.Close() 107 | } 108 | 109 | type unwrappedOsFile struct { 110 | *os.File 111 | } 112 | 113 | func (me unwrappedOsFile) Wrapped() io.Closer { 114 | return me.File 115 | } 116 | 117 | func WrapOSFile(f *os.File) OSFile { 118 | if !enabled { 119 | return unwrappedOsFile{f} 120 | } 121 | return &wrappedOSFile{f, NewCloseWrapper(f)} 122 | } 123 | -------------------------------------------------------------------------------- /prioritybitmap/mapset.go: -------------------------------------------------------------------------------- 1 | package prioritybitmap 2 | 3 | func newMapSet() Set { 4 | return mapSet{ 5 | m: make(map[int]struct{}), 6 | } 7 | } 8 | 9 | type mapSet struct { 10 | m map[int]struct{} 11 | } 12 | 13 | func (m mapSet) Has(bit int) bool { 14 | _, ok := m.m[bit] 15 | return ok 16 | } 17 | 18 | func (m mapSet) Delete(bit int) { 19 | delete(m.m, bit) 20 | } 21 | 22 | func (m mapSet) Len() int { 23 | return len(m.m) 24 | } 25 | 26 | func (m mapSet) Set(bit int) { 27 | m.m[bit] = struct{}{} 28 | } 29 | 30 | func (m mapSet) Range(f func(int) bool) { 31 | for bit := range m.m { 32 | if !f(bit) { 33 | break 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /prioritybitmap/prioritybitmap.go: -------------------------------------------------------------------------------- 1 | // Package prioritybitmap implements a set of integers ordered by attached 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 | // The interface used for non-singleton bit-sets for each priority level. 13 | type Set interface { 14 | Has(bit int) bool 15 | Delete(bit int) 16 | Len() int 17 | Set(bit int) 18 | Range(f func(int) bool) 19 | } 20 | 21 | // Maintains set of ints ordered by priority. 22 | type PriorityBitmap struct { 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 | // If not set, is initialized to the default map[int]struct{} implementation on first use. 28 | NewSet func() Set 29 | bitSets sync.Pool 30 | } 31 | 32 | var _ bitmap.Interface = (*PriorityBitmap)(nil) 33 | 34 | func (me *PriorityBitmap) Contains(bit int) bool { 35 | _, ok := me.priorities[bit] 36 | return ok 37 | } 38 | 39 | func (me *PriorityBitmap) Len() int { 40 | return len(me.priorities) 41 | } 42 | 43 | func (me *PriorityBitmap) Clear() { 44 | me.om = nil 45 | me.priorities = nil 46 | } 47 | 48 | func (me *PriorityBitmap) deleteBit(bit int) (priority int, ok bool) { 49 | priority, ok = me.priorities[bit] 50 | if !ok { 51 | return 52 | } 53 | switch v := me.om.Get(priority).(type) { 54 | case int: 55 | if v != bit { 56 | panic("invariant broken") 57 | } 58 | case Set: 59 | if !v.Has(bit) { 60 | panic("invariant broken") 61 | } 62 | v.Delete(bit) 63 | if v.Len() != 0 { 64 | return 65 | } 66 | me.bitSets.Put(v) 67 | default: 68 | panic(v) 69 | } 70 | me.om.Unset(priority) 71 | if me.om.Len() == 0 { 72 | me.om = nil 73 | } 74 | return 75 | } 76 | 77 | func bitLess(l, r interface{}) bool { 78 | return l.(int) < r.(int) 79 | } 80 | 81 | // Returns true if the priority is changed, or the bit wasn't present. 82 | func (me *PriorityBitmap) Set(bit int, priority int) bool { 83 | if p, ok := me.priorities[bit]; ok && p == priority { 84 | return false 85 | } 86 | if oldPriority, deleted := me.deleteBit(bit); deleted && oldPriority == priority { 87 | panic("should have already returned") 88 | } 89 | if me.priorities == nil { 90 | me.priorities = make(map[int]int) 91 | } 92 | me.priorities[bit] = priority 93 | if me.om == nil { 94 | me.om = orderedmap.New(bitLess) 95 | } 96 | _v, ok := me.om.GetOk(priority) 97 | if !ok { 98 | // No other bits with this priority, set it to a lone int. 99 | me.om.Set(priority, bit) 100 | return true 101 | } 102 | switch v := _v.(type) { 103 | case int: 104 | newV := func() Set { 105 | i := me.bitSets.Get() 106 | if i == nil { 107 | if me.NewSet == nil { 108 | me.NewSet = newMapSet 109 | } 110 | return me.NewSet() 111 | } else { 112 | return i.(Set) 113 | } 114 | }() 115 | newV.Set(v) 116 | newV.Set(bit) 117 | me.om.Set(priority, newV) 118 | case Set: 119 | v.Set(bit) 120 | default: 121 | panic(v) 122 | } 123 | return true 124 | } 125 | 126 | func (me *PriorityBitmap) Remove(bit int) bool { 127 | if _, ok := me.deleteBit(bit); !ok { 128 | return false 129 | } 130 | delete(me.priorities, bit) 131 | if len(me.priorities) == 0 { 132 | me.priorities = nil 133 | } 134 | if me.om != nil && me.om.Len() == 0 { 135 | me.om = nil 136 | } 137 | return true 138 | } 139 | 140 | func (me *PriorityBitmap) Iter(f iter.Callback) { 141 | me.IterTyped(func(i int) bool { 142 | return f(i) 143 | }) 144 | } 145 | 146 | func (me *PriorityBitmap) IterTyped(_f func(i bitmap.BitIndex) bool) bool { 147 | if me == nil || me.om == nil { 148 | return true 149 | } 150 | f := func(i int) bool { 151 | return _f(i) 152 | } 153 | return iter.All(func(key interface{}) bool { 154 | value := me.om.Get(key) 155 | switch v := value.(type) { 156 | case int: 157 | return f(v) 158 | case Set: 159 | v.Range(func(i int) bool { 160 | return f(i) 161 | }) 162 | default: 163 | panic(v) 164 | } 165 | return true 166 | }, me.om.Iter) 167 | } 168 | 169 | func (me *PriorityBitmap) IsEmpty() bool { 170 | if me.om == nil { 171 | return true 172 | } 173 | return me.om.Len() == 0 174 | } 175 | 176 | // ok is false if the bit is not set. 177 | func (me *PriorityBitmap) GetPriority(bit int) (prio int, ok bool) { 178 | prio, ok = me.priorities[bit] 179 | return 180 | } 181 | -------------------------------------------------------------------------------- /prioritybitmap/prioritybitmap_test.go: -------------------------------------------------------------------------------- 1 | package prioritybitmap 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/anacrolix/missinggo/iter" 8 | "github.com/stretchr/testify/assert" 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 | } 72 | -------------------------------------------------------------------------------- /prometheus/expvar.go: -------------------------------------------------------------------------------- 1 | package xprometheus 2 | 3 | import ( 4 | "encoding/json" 5 | "expvar" 6 | "fmt" 7 | "strconv" 8 | 9 | "github.com/bradfitz/iter" 10 | "github.com/prometheus/client_golang/prometheus" 11 | ) 12 | 13 | // A Prometheus collector that exposes all vars. 14 | type expvarCollector struct { 15 | descs map[int]*prometheus.Desc 16 | } 17 | 18 | func NewExpvarCollector() expvarCollector { 19 | return expvarCollector{ 20 | descs: make(map[int]*prometheus.Desc), 21 | } 22 | } 23 | 24 | const ( 25 | fqName = "go_expvar" 26 | help = "All expvars" 27 | ) 28 | 29 | var desc = prometheus.NewDesc(fqName, help, nil, nil) 30 | 31 | // Describe implements Collector. 32 | func (e expvarCollector) Describe(ch chan<- *prometheus.Desc) { 33 | ch <- desc 34 | } 35 | 36 | // Collect implements Collector. 37 | func (e expvarCollector) Collect(ch chan<- prometheus.Metric) { 38 | expvar.Do(func(kv expvar.KeyValue) { 39 | // I think this is very noisy, and there seems to be good support for exporting its 40 | // information in a more structured way. 41 | if kv.Key == "memstats" { 42 | return 43 | } 44 | collector{ 45 | f: func(m prometheus.Metric) { 46 | ch <- m 47 | }, 48 | labelValues: []string{kv.Key}, 49 | descs: e.descs, 50 | }.collectVar(kv.Value) 51 | }) 52 | } 53 | 54 | func labels(n int) (ls []string) { 55 | for i := range iter.N(n) { 56 | ls = append(ls, "key"+strconv.FormatInt(int64(i), 10)) 57 | } 58 | return 59 | } 60 | 61 | type collector struct { 62 | f func(prometheus.Metric) 63 | labelValues []string 64 | descs map[int]*prometheus.Desc 65 | } 66 | 67 | func (c *collector) newMetric(f float64) { 68 | c.f(prometheus.MustNewConstMetric( 69 | c.desc(), 70 | prometheus.UntypedValue, 71 | float64(f), 72 | c.labelValues...)) 73 | } 74 | 75 | func (c collector) desc() *prometheus.Desc { 76 | d, ok := c.descs[len(c.labelValues)] 77 | if !ok { 78 | d = prometheus.NewDesc(fqName, "", labels(len(c.labelValues)), nil) 79 | c.descs[len(c.labelValues)] = d 80 | } 81 | return d 82 | } 83 | 84 | func (c collector) metricError(err error) { 85 | c.f(prometheus.NewInvalidMetric(c.desc(), err)) 86 | } 87 | 88 | func (c collector) withLabelValue(lv string) collector { 89 | //if !utf8.ValidString(lv) { 90 | // lv = strconv.Quote(lv) 91 | //} 92 | c.labelValues = append(c.labelValues, lv) 93 | return c 94 | } 95 | 96 | func (c collector) collectJsonValue(v interface{}) { 97 | switch v := v.(type) { 98 | case float64: 99 | c.newMetric(v) 100 | case map[string]interface{}: 101 | for k, v := range v { 102 | c.withLabelValue(k).collectJsonValue(v) 103 | } 104 | case bool: 105 | if v { 106 | c.newMetric(1) 107 | } else { 108 | c.newMetric(0) 109 | } 110 | case string: 111 | c.f(prometheus.MustNewConstMetric( 112 | prometheus.NewDesc("go_expvar", "", 113 | append(labels(len(c.labelValues)), "value"), 114 | nil), 115 | prometheus.UntypedValue, 116 | 1, 117 | append(c.labelValues, v)..., 118 | )) 119 | case []interface{}: 120 | for i, v := range v { 121 | c.withLabelValue(strconv.FormatInt(int64(i), 10)).collectJsonValue(v) 122 | } 123 | default: 124 | c.metricError(fmt.Errorf("unhandled json value type %T", v)) 125 | } 126 | } 127 | 128 | func (c collector) collectVar(v expvar.Var) { 129 | //switch _v := v.(type) { 130 | //case *expvar.Map: 131 | // _v.Do(func(kv expvar.KeyValue) { 132 | // c.withLabelValue(kv.Key).collectVar(kv.Value) 133 | // }) 134 | // return 135 | //} 136 | var jv interface{} 137 | if err := json.Unmarshal([]byte(v.String()), &jv); err != nil { 138 | c.metricError(fmt.Errorf("error unmarshaling Var json: %s", err)) 139 | } 140 | c.collectJsonValue(jv) 141 | } 142 | -------------------------------------------------------------------------------- /prometheus/expvar_test.go: -------------------------------------------------------------------------------- 1 | package xprometheus 2 | 3 | import ( 4 | "expvar" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/bradfitz/iter" 10 | "github.com/prometheus/client_golang/prometheus" 11 | dto "github.com/prometheus/client_model/go" 12 | ) 13 | 14 | func BenchmarkExpvarCollector_Collect(b *testing.B) { 15 | ec := NewExpvarCollector() 16 | ch := make(chan prometheus.Metric) 17 | n := 0 18 | var wg sync.WaitGroup 19 | wg.Add(1) 20 | go func() { 21 | defer wg.Done() 22 | for range ch { 23 | n++ 24 | } 25 | }() 26 | b.ReportAllocs() 27 | for range iter.N(b.N) { 28 | ec.Collect(ch) 29 | } 30 | close(ch) 31 | wg.Wait() 32 | b.Logf("collected %d metrics (%f per collect)", n, float64(n)/float64(b.N)) 33 | } 34 | 35 | func TestCollectInvalidJsonStringChar(t *testing.T) { 36 | c := collector{ 37 | descs: make(map[int]*prometheus.Desc), 38 | f: func(m prometheus.Metric) { 39 | var iom dto.Metric 40 | err := m.Write(&iom) 41 | if err != nil { 42 | t.Fatal(err) 43 | } else { 44 | t.Log(iom) 45 | } 46 | }, 47 | } 48 | v := new(expvar.Map).Init() 49 | v.Add(fmt.Sprintf("received query %q", "find\xdfnode"), 1) 50 | c.collectVar(v) 51 | } 52 | -------------------------------------------------------------------------------- /pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type PubSub[T any] struct { 8 | mu sync.Mutex 9 | next chan item[T] 10 | closed bool 11 | } 12 | 13 | type item[T any] struct { 14 | value T 15 | next chan item[T] 16 | } 17 | 18 | type Subscription[T any] struct { 19 | next chan item[T] 20 | Values chan T 21 | mu sync.Mutex 22 | closed chan struct{} 23 | } 24 | 25 | func (me *PubSub[T]) init() { 26 | me.next = make(chan item[T], 1) 27 | } 28 | 29 | func (me *PubSub[T]) lazyInit() { 30 | me.mu.Lock() 31 | defer me.mu.Unlock() 32 | if me.closed { 33 | return 34 | } 35 | if me.next == nil { 36 | me.init() 37 | } 38 | } 39 | 40 | func (me *PubSub[T]) Publish(v T) { 41 | me.lazyInit() 42 | next := make(chan item[T], 1) 43 | i := item[T]{v, next} 44 | me.mu.Lock() 45 | if !me.closed { 46 | me.next <- i 47 | me.next = next 48 | } 49 | me.mu.Unlock() 50 | } 51 | 52 | func (me *Subscription[T]) Close() { 53 | me.mu.Lock() 54 | defer me.mu.Unlock() 55 | select { 56 | case <-me.closed: 57 | default: 58 | close(me.closed) 59 | } 60 | } 61 | 62 | func (me *Subscription[T]) runner() { 63 | defer close(me.Values) 64 | for { 65 | select { 66 | case i, ok := <-me.next: 67 | if !ok { 68 | me.Close() 69 | return 70 | } 71 | // Send the value back into the channel for someone else. This 72 | // won't block because the channel has a capacity of 1, and this 73 | // is currently the only copy of this value being sent to this 74 | // channel. 75 | me.next <- i 76 | // The next value comes from the channel given to us by the value 77 | // we just got. 78 | me.next = i.next 79 | select { 80 | case me.Values <- i.value: 81 | case <-me.closed: 82 | return 83 | } 84 | case <-me.closed: 85 | return 86 | } 87 | } 88 | } 89 | 90 | func (me *PubSub[T]) Subscribe() (ret *Subscription[T]) { 91 | me.lazyInit() 92 | ret = &Subscription[T]{ 93 | closed: make(chan struct{}), 94 | Values: make(chan T), 95 | } 96 | me.mu.Lock() 97 | ret.next = me.next 98 | me.mu.Unlock() 99 | go ret.runner() 100 | return 101 | } 102 | 103 | func (me *PubSub[T]) Close() { 104 | me.mu.Lock() 105 | defer me.mu.Unlock() 106 | if me.closed { 107 | return 108 | } 109 | if me.next != nil { 110 | close(me.next) 111 | } 112 | me.closed = true 113 | } 114 | -------------------------------------------------------------------------------- /pubsub/pubsub_test.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/bradfitz/iter" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDoubleClose(t *testing.T) { 13 | var ps PubSub[any] 14 | ps.Close() 15 | ps.Close() 16 | } 17 | 18 | func testBroadcast(t testing.TB, subs, vals int) { 19 | var ps PubSub[int] 20 | var wg sync.WaitGroup 21 | for range iter.N(subs) { 22 | wg.Add(1) 23 | s := ps.Subscribe() 24 | go func() { 25 | defer wg.Done() 26 | var e int 27 | for i := range s.Values { 28 | assert.Equal(t, e, i) 29 | e++ 30 | } 31 | assert.Equal(t, vals, e) 32 | }() 33 | } 34 | for i := range iter.N(vals) { 35 | ps.Publish(i) 36 | } 37 | ps.Close() 38 | wg.Wait() 39 | } 40 | 41 | func TestBroadcast(t *testing.T) { 42 | testBroadcast(t, 100, 10) 43 | } 44 | 45 | func BenchmarkBroadcast(b *testing.B) { 46 | for range iter.N(b.N) { 47 | testBroadcast(b, 10, 1000) 48 | } 49 | } 50 | 51 | func TestCloseSubscription(t *testing.T) { 52 | var ps PubSub[int] 53 | ps.Publish(1) 54 | s := ps.Subscribe() 55 | select { 56 | case <-s.Values: 57 | t.FailNow() 58 | default: 59 | } 60 | ps.Publish(2) 61 | s2 := ps.Subscribe() 62 | ps.Publish(3) 63 | require.Equal(t, 2, <-s.Values) 64 | require.EqualValues(t, 3, <-s.Values) 65 | s.Close() 66 | _, ok := <-s.Values 67 | require.False(t, ok) 68 | ps.Publish(4) 69 | ps.Close() 70 | require.Equal(t, 3, <-s2.Values) 71 | require.Equal(t, 4, <-s2.Values) 72 | require.Zero(t, <-s2.Values) 73 | s2.Close() 74 | } 75 | -------------------------------------------------------------------------------- /reader_context.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "context" 4 | 5 | type ContextedReader struct { 6 | R ReadContexter 7 | Ctx context.Context 8 | } 9 | 10 | func (me ContextedReader) Read(b []byte) (int, error) { 11 | return me.R.ReadContext(me.Ctx, b) 12 | } 13 | 14 | type ReadContexter interface { 15 | ReadContext(context.Context, []byte) (int, error) 16 | } 17 | -------------------------------------------------------------------------------- /refclose/refclose.go: -------------------------------------------------------------------------------- 1 | package refclose 2 | 3 | import ( 4 | "runtime/pprof" 5 | "sync" 6 | ) 7 | 8 | var profile = pprof.NewProfile("refs") 9 | 10 | type RefPool struct { 11 | mu sync.Mutex 12 | rs map[interface{}]*resource 13 | } 14 | 15 | type Closer func() 16 | 17 | func (me *RefPool) inc(key interface{}) { 18 | me.mu.Lock() 19 | defer me.mu.Unlock() 20 | r := me.rs[key] 21 | if r == nil { 22 | r = new(resource) 23 | if me.rs == nil { 24 | me.rs = make(map[interface{}]*resource) 25 | } 26 | me.rs[key] = r 27 | } 28 | r.numRefs++ 29 | } 30 | 31 | func (me *RefPool) dec(key interface{}) { 32 | me.mu.Lock() 33 | defer me.mu.Unlock() 34 | r := me.rs[key] 35 | r.numRefs-- 36 | if r.numRefs > 0 { 37 | return 38 | } 39 | if r.numRefs < 0 { 40 | panic(r.numRefs) 41 | } 42 | r.closer() 43 | delete(me.rs, key) 44 | } 45 | 46 | type resource struct { 47 | closer Closer 48 | numRefs int 49 | } 50 | 51 | func (me *RefPool) NewRef(key interface{}) (ret *Ref) { 52 | me.inc(key) 53 | ret = &Ref{ 54 | pool: me, 55 | key: key, 56 | } 57 | profile.Add(ret, 0) 58 | return 59 | } 60 | 61 | type Ref struct { 62 | mu sync.Mutex 63 | pool *RefPool 64 | key interface{} 65 | closed bool 66 | } 67 | 68 | func (me *Ref) SetCloser(closer Closer) { 69 | me.pool.mu.Lock() 70 | defer me.pool.mu.Unlock() 71 | me.pool.rs[me.key].closer = closer 72 | } 73 | 74 | func (me *Ref) panicIfClosed() { 75 | if me.closed { 76 | panic("ref is closed") 77 | } 78 | } 79 | 80 | func (me *Ref) Release() { 81 | me.mu.Lock() 82 | defer me.mu.Unlock() 83 | me.panicIfClosed() 84 | profile.Remove(me) 85 | me.pool.dec(me.key) 86 | } 87 | 88 | func (me *Ref) Key() interface{} { 89 | me.mu.Lock() 90 | defer me.mu.Unlock() 91 | me.panicIfClosed() 92 | return me.key 93 | } 94 | -------------------------------------------------------------------------------- /refclose/refclose_test.go: -------------------------------------------------------------------------------- 1 | package refclose 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/bradfitz/iter" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type refTest struct { 12 | pool RefPool 13 | key interface{} 14 | objs map[*object]struct{} 15 | t *testing.T 16 | } 17 | 18 | func (me *refTest) run() { 19 | me.objs = make(map[*object]struct{}) 20 | var ( 21 | mu sync.Mutex 22 | curObj *object 23 | wg sync.WaitGroup 24 | ) 25 | for range iter.N(1000) { 26 | wg.Add(1) 27 | go func() { 28 | defer wg.Done() 29 | ref := me.pool.NewRef(me.key) 30 | mu.Lock() 31 | if curObj == nil { 32 | curObj = new(object) 33 | me.objs[curObj] = struct{}{} 34 | } 35 | // obj := curObj 36 | mu.Unlock() 37 | ref.SetCloser(func() { 38 | mu.Lock() 39 | if curObj.closed { 40 | panic("object already closed") 41 | } 42 | curObj.closed = true 43 | curObj = nil 44 | mu.Unlock() 45 | }) 46 | ref.Release() 47 | }() 48 | } 49 | wg.Wait() 50 | me.t.Logf("created %d objects", len(me.objs)) 51 | assert.True(me.t, len(me.objs) >= 1) 52 | for obj := range me.objs { 53 | assert.True(me.t, obj.closed) 54 | } 55 | } 56 | 57 | type object struct { 58 | closed bool 59 | } 60 | 61 | func Test(t *testing.T) { 62 | (&refTest{ 63 | key: 3, 64 | t: t, 65 | }).run() 66 | } 67 | -------------------------------------------------------------------------------- /reqctx/lazy.go: -------------------------------------------------------------------------------- 1 | package reqctx 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/anacrolix/missinggo/futures" 8 | ) 9 | 10 | var lazyValuesContextKey = new(byte) 11 | 12 | func WithLazyMiddleware() func(http.Handler) http.Handler { 13 | return func(h http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | r = WithLazy(r) 16 | h.ServeHTTP(w, r) 17 | }) 18 | } 19 | } 20 | 21 | func WithLazy(r *http.Request) *http.Request { 22 | if r.Context().Value(lazyValuesContextKey) == nil { 23 | r = r.WithContext(context.WithValue(r.Context(), lazyValuesContextKey, &LazyValues{r: r})) 24 | } 25 | return r 26 | } 27 | 28 | func GetLazyValues(ctx context.Context) *LazyValues { 29 | return ctx.Value(lazyValuesContextKey).(*LazyValues) 30 | } 31 | 32 | type LazyValues struct { 33 | values map[interface{}]*futures.F 34 | r *http.Request 35 | } 36 | 37 | func (me *LazyValues) Get(val *lazyValue) *futures.F { 38 | f := me.values[val.key] 39 | if f != nil { 40 | return f 41 | } 42 | f = futures.Start(func() (interface{}, error) { 43 | return val.get(me.r) 44 | }) 45 | if me.values == nil { 46 | me.values = make(map[interface{}]*futures.F) 47 | } 48 | me.values[val.key] = f 49 | return f 50 | } 51 | 52 | func NewLazyValue(get func(r *http.Request) (interface{}, error)) *lazyValue { 53 | val := &lazyValue{ 54 | get: get, 55 | } 56 | val.key = val 57 | return val 58 | } 59 | 60 | type lazyValue struct { 61 | key interface{} 62 | get func(r *http.Request) (interface{}, error) 63 | } 64 | 65 | func (me *lazyValue) Get(r *http.Request) *futures.F { 66 | return me.GetContext(r.Context()) 67 | } 68 | 69 | func (me *lazyValue) GetContext(ctx context.Context) *futures.F { 70 | return GetLazyValues(ctx).Get(me) 71 | } 72 | 73 | func (me *lazyValue) Prefetch(r *http.Request) { 74 | me.Get(r) 75 | } 76 | 77 | func (me *lazyValue) PrefetchMiddleware(h http.Handler) http.Handler { 78 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 79 | me.Prefetch(r) 80 | h.ServeHTTP(w, r) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /reqctx/reqctx.go: -------------------------------------------------------------------------------- 1 | package reqctx 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/anacrolix/missinggo/expect" 8 | ) 9 | 10 | func SetNewValue(r *http.Request, key, value interface{}) *http.Request { 11 | expect.Nil(r.Context().Value(key)) 12 | expect.NotNil(value) 13 | return r.WithContext(context.WithValue(r.Context(), key, value)) 14 | } 15 | -------------------------------------------------------------------------------- /reqctx/value.go: -------------------------------------------------------------------------------- 1 | package reqctx 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/anacrolix/missinggo/expect" 8 | ) 9 | 10 | func NewValue() *contextValue { 11 | return &contextValue{new(byte)} 12 | } 13 | 14 | type contextValue struct { 15 | key interface{} 16 | } 17 | 18 | func (me contextValue) Get(ctx context.Context) interface{} { 19 | return ctx.Value(me.key) 20 | } 21 | 22 | // Sets the value on the Request. It must not have been already set. 23 | func (me contextValue) SetRequestOnce(r *http.Request, val interface{}) *http.Request { 24 | expect.Nil(me.Get(r.Context())) 25 | return r.WithContext(context.WithValue(r.Context(), me.key, val)) 26 | } 27 | 28 | // Returns a middleware that sets the value in the Request's Context. 29 | func (me contextValue) SetMiddleware(val interface{}) func(http.Handler) http.Handler { 30 | return func(h http.Handler) http.Handler { 31 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 32 | r = me.SetRequestOnce(r, val) 33 | h.ServeHTTP(w, r) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resource/http.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | // Provides access to resources through a http.Client. 16 | type HTTPProvider struct { 17 | Client *http.Client 18 | } 19 | 20 | var _ Provider = &HTTPProvider{} 21 | 22 | func (me *HTTPProvider) NewInstance(urlStr string) (r Instance, err error) { 23 | _r := new(httpInstance) 24 | _r.URL, err = url.Parse(urlStr) 25 | if err != nil { 26 | return 27 | } 28 | _r.Client = me.Client 29 | if _r.Client == nil { 30 | _r.Client = http.DefaultClient 31 | } 32 | r = _r 33 | return 34 | } 35 | 36 | type httpInstance struct { 37 | Client *http.Client 38 | URL *url.URL 39 | } 40 | 41 | var _ Instance = &httpInstance{} 42 | 43 | func mustNewRequest(method, urlStr string, body io.Reader) *http.Request { 44 | req, err := http.NewRequest(method, urlStr, body) 45 | if err != nil { 46 | panic(err) 47 | } 48 | return req 49 | } 50 | 51 | func responseError(r *http.Response) error { 52 | if r.StatusCode == http.StatusNotFound { 53 | return os.ErrNotExist 54 | } 55 | return errors.New(r.Status) 56 | } 57 | 58 | func (me *httpInstance) Get() (ret io.ReadCloser, err error) { 59 | resp, err := me.Client.Get(me.URL.String()) 60 | if err != nil { 61 | return 62 | } 63 | if resp.StatusCode == http.StatusOK { 64 | ret = resp.Body 65 | return 66 | } 67 | resp.Body.Close() 68 | err = responseError(resp) 69 | return 70 | } 71 | 72 | func (me *httpInstance) Put(r io.Reader) (err error) { 73 | resp, err := me.Client.Do(mustNewRequest("PUT", me.URL.String(), r)) 74 | if err != nil { 75 | return 76 | } 77 | resp.Body.Close() 78 | if resp.StatusCode == http.StatusOK { 79 | return 80 | } 81 | err = responseError(resp) 82 | return 83 | } 84 | 85 | func (me *httpInstance) ReadAt(b []byte, off int64) (n int, err error) { 86 | req := mustNewRequest("GET", me.URL.String(), nil) 87 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1)) 88 | resp, err := me.Client.Do(req) 89 | if err != nil { 90 | return 91 | } 92 | defer resp.Body.Close() 93 | switch resp.StatusCode { 94 | case http.StatusPartialContent: 95 | case http.StatusRequestedRangeNotSatisfiable: 96 | err = io.EOF 97 | return 98 | default: 99 | err = responseError(resp) 100 | return 101 | } 102 | // TODO: This will crash if ContentLength was not provided (-1). Do 103 | // something about that. 104 | b = b[:resp.ContentLength] 105 | return io.ReadFull(resp.Body, b) 106 | } 107 | 108 | func (me *httpInstance) WriteAt(b []byte, off int64) (n int, err error) { 109 | req := mustNewRequest("PATCH", me.URL.String(), bytes.NewReader(b)) 110 | req.ContentLength = int64(len(b)) 111 | req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1)) 112 | resp, err := me.Client.Do(req) 113 | if err != nil { 114 | return 115 | } 116 | resp.Body.Close() 117 | if resp.StatusCode != http.StatusOK { 118 | err = responseError(resp) 119 | } 120 | n = len(b) 121 | return 122 | } 123 | 124 | func (me *httpInstance) Stat() (fi os.FileInfo, err error) { 125 | resp, err := me.Client.Head(me.URL.String()) 126 | if err != nil { 127 | return 128 | } 129 | resp.Body.Close() 130 | if resp.StatusCode == http.StatusNotFound { 131 | err = os.ErrNotExist 132 | return 133 | } 134 | if resp.StatusCode != http.StatusOK { 135 | err = errors.New(resp.Status) 136 | return 137 | } 138 | var _fi httpFileInfo 139 | if h := resp.Header.Get("Last-Modified"); h != "" { 140 | _fi.lastModified, err = time.Parse(http.TimeFormat, h) 141 | if err != nil { 142 | err = fmt.Errorf("error parsing Last-Modified header: %s", err) 143 | return 144 | } 145 | } 146 | if h := resp.Header.Get("Content-Length"); h != "" { 147 | _fi.contentLength, err = strconv.ParseInt(h, 10, 64) 148 | if err != nil { 149 | err = fmt.Errorf("error parsing Content-Length header: %s", err) 150 | return 151 | } 152 | } 153 | fi = _fi 154 | return 155 | } 156 | 157 | func (me *httpInstance) Delete() (err error) { 158 | resp, err := me.Client.Do(mustNewRequest("DELETE", me.URL.String(), nil)) 159 | if err != nil { 160 | return 161 | } 162 | err = responseError(resp) 163 | resp.Body.Close() 164 | return 165 | } 166 | 167 | type httpFileInfo struct { 168 | lastModified time.Time 169 | contentLength int64 170 | } 171 | 172 | var _ os.FileInfo = httpFileInfo{} 173 | 174 | func (fi httpFileInfo) IsDir() bool { 175 | return false 176 | } 177 | 178 | func (fi httpFileInfo) Mode() os.FileMode { 179 | return 0 180 | } 181 | 182 | func (fi httpFileInfo) Name() string { 183 | return "" 184 | } 185 | 186 | func (fi httpFileInfo) Size() int64 { 187 | return fi.contentLength 188 | } 189 | 190 | func (fi httpFileInfo) ModTime() time.Time { 191 | return fi.lastModified 192 | } 193 | 194 | func (fi httpFileInfo) Sys() interface{} { 195 | return nil 196 | } 197 | -------------------------------------------------------------------------------- /resource/osfile.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // Provides access to resources through the native OS filesystem. 9 | type OSFileProvider struct{} 10 | 11 | var _ Provider = OSFileProvider{} 12 | 13 | func (me OSFileProvider) NewInstance(filePath string) (r Instance, err error) { 14 | return &osFileInstance{filePath}, nil 15 | } 16 | 17 | type osFileInstance struct { 18 | path string 19 | } 20 | 21 | var _ Instance = &osFileInstance{} 22 | 23 | func (me *osFileInstance) Get() (ret io.ReadCloser, err error) { 24 | return os.Open(me.path) 25 | } 26 | 27 | func (me *osFileInstance) Put(r io.Reader) (err error) { 28 | f, err := os.OpenFile(me.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) 29 | if err != nil { 30 | return 31 | } 32 | defer f.Close() 33 | _, err = io.Copy(f, r) 34 | return 35 | } 36 | 37 | func (me *osFileInstance) ReadAt(b []byte, off int64) (n int, err error) { 38 | f, err := os.Open(me.path) 39 | if err != nil { 40 | return 41 | } 42 | defer f.Close() 43 | return f.ReadAt(b, off) 44 | } 45 | 46 | func (me *osFileInstance) WriteAt(b []byte, off int64) (n int, err error) { 47 | f, err := os.OpenFile(me.path, os.O_CREATE|os.O_WRONLY, 0640) 48 | if err != nil { 49 | return 50 | } 51 | defer f.Close() 52 | return f.WriteAt(b, off) 53 | } 54 | 55 | func (me *osFileInstance) Stat() (fi os.FileInfo, err error) { 56 | return os.Stat(me.path) 57 | } 58 | 59 | func (me *osFileInstance) Delete() error { 60 | return os.Remove(me.path) 61 | } 62 | -------------------------------------------------------------------------------- /resource/provider.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | type Provider interface { 4 | NewInstance(string) (Instance, error) 5 | } 6 | 7 | // TranslatedProvider manipulates resource locations, so as to allow 8 | // sandboxing, or relative paths for example. 9 | type TranslatedProvider struct { 10 | // The underlying Provider. 11 | BaseProvider Provider 12 | // Some location used in calculating final locations. 13 | BaseLocation string 14 | // Function that takes BaseLocation, and the caller location and returns 15 | // the location to be used with the BaseProvider. 16 | JoinLocations func(base, rel string) string 17 | } 18 | 19 | func (me TranslatedProvider) NewInstance(rel string) (Instance, error) { 20 | return me.BaseProvider.NewInstance(me.JoinLocations(me.BaseLocation, rel)) 21 | } 22 | -------------------------------------------------------------------------------- /resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // An Instance represents the content at some location accessed through some 9 | // Provider. It's the data at some URL. 10 | type Instance interface { 11 | Get() (io.ReadCloser, error) 12 | Put(io.Reader) error 13 | Stat() (os.FileInfo, error) 14 | io.ReaderAt 15 | WriteAt([]byte, int64) (int, error) 16 | Delete() error 17 | } 18 | 19 | type DirInstance interface { 20 | Readdirnames() ([]string, error) 21 | } 22 | 23 | // Creates a io.ReadSeeker to an Instance. 24 | func ReadSeeker(r Instance) io.ReadSeeker { 25 | fi, err := r.Stat() 26 | if err != nil { 27 | return nil 28 | } 29 | return io.NewSectionReader(r, 0, fi.Size()) 30 | } 31 | 32 | // Move instance content, deleting the source if it succeeds. 33 | func Move(from, to Instance) (err error) { 34 | rc, err := from.Get() 35 | if err != nil { 36 | return 37 | } 38 | defer rc.Close() 39 | err = to.Put(rc) 40 | if err != nil { 41 | return 42 | } 43 | from.Delete() 44 | return 45 | } 46 | 47 | func Exists(i Instance) bool { 48 | _, err := i.Stat() 49 | return err == nil 50 | } 51 | -------------------------------------------------------------------------------- /rle.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | // A RunLengthEncoder counts successive duplicate elements and emits the 4 | // element and the run length when the element changes or the encoder is 5 | // flushed. 6 | type RunLengthEncoder interface { 7 | // Add a series of identical elements to the stream. 8 | Append(element interface{}, count uint64) 9 | // Emit the current element and its count if non-zero without waiting for 10 | // the element to change. 11 | Flush() 12 | } 13 | 14 | type runLengthEncoder struct { 15 | eachRun func(element interface{}, count uint64) 16 | element interface{} 17 | count uint64 18 | } 19 | 20 | // Creates a new RunLengthEncoder. eachRun is called when an element and its 21 | // count is emitted, per the RunLengthEncoder interface. 22 | func NewRunLengthEncoder(eachRun func(element interface{}, count uint64)) RunLengthEncoder { 23 | return &runLengthEncoder{ 24 | eachRun: eachRun, 25 | } 26 | } 27 | 28 | func (me *runLengthEncoder) Append(element interface{}, count uint64) { 29 | if element == me.element { 30 | me.count += count 31 | return 32 | } 33 | if me.count != 0 { 34 | me.eachRun(me.element, me.count) 35 | } 36 | me.count = count 37 | me.element = element 38 | } 39 | 40 | func (me *runLengthEncoder) Flush() { 41 | if me.count == 0 { 42 | return 43 | } 44 | me.eachRun(me.element, me.count) 45 | me.count = 0 46 | } 47 | -------------------------------------------------------------------------------- /rle_test.go: -------------------------------------------------------------------------------- 1 | package missinggo_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/anacrolix/missinggo/v2" 7 | ) 8 | 9 | func ExampleNewRunLengthEncoder() { 10 | var s string 11 | rle := missinggo.NewRunLengthEncoder(func(e interface{}, count uint64) { 12 | s += fmt.Sprintf("%d%c", count, e) 13 | }) 14 | for _, e := range "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW" { 15 | rle.Append(e, 1) 16 | } 17 | rle.Flush() 18 | fmt.Println(s) 19 | // Output: 12W1B12W3B24W1B14W 20 | } 21 | -------------------------------------------------------------------------------- /runid/crawshaw/crawshaw-runid.go: -------------------------------------------------------------------------------- 1 | package crawshaw_runid 2 | 3 | import ( 4 | "crawshaw.io/sqlite" 5 | "crawshaw.io/sqlite/sqlitex" 6 | "github.com/anacrolix/missinggo/expect" 7 | 8 | "github.com/anacrolix/missinggo/v2/runid" 9 | ) 10 | 11 | func New(db *sqlite.Conn) *runid.T { 12 | err := sqlitex.ExecScript(db, ` 13 | CREATE TABLE if not exists runs (started datetime default (datetime('now'))); 14 | insert into runs default values; 15 | `) 16 | expect.Nil(err) 17 | ret := runid.T(db.LastInsertRowID()) 18 | return &ret 19 | } 20 | -------------------------------------------------------------------------------- /runid/sqlite3.go: -------------------------------------------------------------------------------- 1 | package runid 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/anacrolix/missinggo/expect" 8 | ) 9 | 10 | type T int64 11 | 12 | func New(db *sql.DB) (ret *T) { 13 | ctx := context.Background() 14 | conn, err := db.Conn(ctx) 15 | expect.Nil(err) 16 | defer func() { 17 | expect.Nil(conn.Close()) 18 | }() 19 | _, err = conn.ExecContext(ctx, `CREATE TABLE if not exists runs (started datetime default (datetime('now')))`) 20 | expect.Nil(err) 21 | res, err := conn.ExecContext(ctx, "insert into runs default values") 22 | expect.Nil(err) 23 | expect.OneRowAffected(res) 24 | expect.Nil(conn.QueryRowContext(ctx, "select last_insert_rowid()").Scan(&ret)) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /runid/zombiezen/zombiezen-runid.go: -------------------------------------------------------------------------------- 1 | package zombiezen_runid 2 | 3 | import ( 4 | "github.com/anacrolix/missinggo/expect" 5 | "zombiezen.com/go/sqlite" 6 | "zombiezen.com/go/sqlite/sqlitex" 7 | 8 | "github.com/anacrolix/missinggo/v2/runid" 9 | ) 10 | 11 | func New(db *sqlite.Conn) *runid.T { 12 | err := sqlitex.ExecScript(db, ` 13 | CREATE TABLE if not exists runs (started datetime default (datetime('now'))); 14 | insert into runs default values; 15 | `) 16 | expect.Nil(err) 17 | ret := runid.T(db.LastInsertRowID()) 18 | return &ret 19 | } 20 | -------------------------------------------------------------------------------- /section_read_seeker.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | type sectionReadSeeker struct { 10 | base io.ReadSeeker 11 | off, size int64 12 | } 13 | 14 | type ReadSeekContexter interface { 15 | io.ReadSeeker 16 | ReadContexter 17 | } 18 | 19 | // Returns a ReadSeeker on a section of another ReadSeeker. 20 | func NewSectionReadSeeker(base io.ReadSeeker, off, size int64) (ret ReadSeekContexter) { 21 | ret = §ionReadSeeker{ 22 | base: base, 23 | off: off, 24 | size: size, 25 | } 26 | seekOff, err := ret.Seek(0, io.SeekStart) 27 | if err != nil { 28 | panic(err) 29 | } 30 | if seekOff != 0 { 31 | panic(seekOff) 32 | } 33 | return 34 | } 35 | 36 | func (me *sectionReadSeeker) Seek(off int64, whence int) (ret int64, err error) { 37 | switch whence { 38 | case io.SeekStart: 39 | off += me.off 40 | case io.SeekCurrent: 41 | case io.SeekEnd: 42 | off += me.off + me.size 43 | whence = io.SeekStart 44 | default: 45 | err = fmt.Errorf("unhandled whence: %d", whence) 46 | return 47 | } 48 | ret, err = me.base.Seek(off, whence) 49 | ret -= me.off 50 | return 51 | } 52 | 53 | func (me *sectionReadSeeker) ReadContext(ctx context.Context, b []byte) (int, error) { 54 | off, err := me.Seek(0, io.SeekCurrent) 55 | if err != nil { 56 | return 0, err 57 | } 58 | left := me.size - off 59 | if left <= 0 { 60 | return 0, io.EOF 61 | } 62 | b = LimitLen(b, left) 63 | if rc, ok := me.base.(ReadContexter); ok { 64 | return rc.ReadContext(ctx, b) 65 | } 66 | if ctx != context.Background() { 67 | // Can't handle cancellation. 68 | panic(ctx) 69 | } 70 | return me.base.Read(b) 71 | } 72 | 73 | func (me *sectionReadSeeker) Read(b []byte) (int, error) { 74 | return me.ReadContext(context.Background(), b) 75 | } 76 | -------------------------------------------------------------------------------- /section_read_seeker_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSectionReadSeekerReadBeyondEnd(t *testing.T) { 13 | base := bytes.NewReader([]byte{1, 2, 3}) 14 | srs := NewSectionReadSeeker(base, 1, 1) 15 | dest := new(bytes.Buffer) 16 | n, err := io.Copy(dest, srs) 17 | assert.EqualValues(t, 1, n) 18 | assert.NoError(t, err) 19 | } 20 | 21 | func TestSectionReadSeekerSeekEnd(t *testing.T) { 22 | base := bytes.NewReader([]byte{1, 2, 3}) 23 | srs := NewSectionReadSeeker(base, 1, 1) 24 | off, err := srs.Seek(0, os.SEEK_END) 25 | assert.NoError(t, err) 26 | assert.EqualValues(t, 1, off) 27 | } 28 | -------------------------------------------------------------------------------- /section_writer.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "io" 4 | 5 | type SectionWriter struct { 6 | w io.WriterAt 7 | off, len int64 8 | } 9 | 10 | func NewSectionWriter(w io.WriterAt, off, len int64) *SectionWriter { 11 | return &SectionWriter{w, off, len} 12 | } 13 | 14 | func (me *SectionWriter) WriteAt(b []byte, off int64) (n int, err error) { 15 | if off >= me.len { 16 | err = io.EOF 17 | return 18 | } 19 | if off+int64(len(b)) > me.len { 20 | b = b[:me.len-off] 21 | } 22 | return me.w.WriteAt(b, me.off+off) 23 | } 24 | -------------------------------------------------------------------------------- /selfcert.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "log" 11 | "math/big" 12 | "time" 13 | ) 14 | 15 | func publicKey(priv interface{}) interface{} { 16 | switch k := priv.(type) { 17 | case *rsa.PrivateKey: 18 | return &k.PublicKey 19 | case *ecdsa.PrivateKey: 20 | return &k.PublicKey 21 | default: 22 | return nil 23 | } 24 | } 25 | 26 | // Creates a self-signed certificate in memory for use with tls.Config. 27 | func NewSelfSignedCertificate() (cert tls.Certificate, err error) { 28 | cert.PrivateKey, err = rsa.GenerateKey(rand.Reader, 2048) 29 | if err != nil { 30 | return 31 | } 32 | notBefore := time.Now() 33 | notAfter := notBefore.Add(365 * 24 * time.Hour) 34 | 35 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 36 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 37 | if err != nil { 38 | log.Fatalf("failed to generate serial number: %s", err) 39 | } 40 | 41 | template := x509.Certificate{ 42 | SerialNumber: serialNumber, 43 | Subject: pkix.Name{ 44 | Organization: []string{"Acme Co"}, 45 | }, 46 | NotBefore: notBefore, 47 | NotAfter: notAfter, 48 | 49 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 50 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 51 | BasicConstraintsValid: true, 52 | } 53 | 54 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(cert.PrivateKey), cert.PrivateKey) 55 | if err != nil { 56 | log.Fatalf("Failed to create certificate: %s", err) 57 | } 58 | cert.Certificate = [][]byte{derBytes} 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /singleflight.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import "sync" 4 | 5 | type ongoing struct { 6 | // Waiters can proceed, someone finished the task. 7 | do sync.Mutex 8 | users int 9 | } 10 | 11 | type SingleFlight struct { 12 | mu sync.Mutex 13 | ongoing map[string]*ongoing 14 | } 15 | 16 | type Operation struct { 17 | sf *SingleFlight 18 | id string 19 | } 20 | 21 | func (op Operation) Unlock() { 22 | op.sf.Unlock(op.id) 23 | } 24 | 25 | func (me *SingleFlight) Lock(id string) Operation { 26 | me.mu.Lock() 27 | on, ok := me.ongoing[id] 28 | if !ok { 29 | on = new(ongoing) 30 | if me.ongoing == nil { 31 | me.ongoing = make(map[string]*ongoing) 32 | } 33 | me.ongoing[id] = on 34 | } 35 | on.users++ 36 | me.mu.Unlock() 37 | on.do.Lock() 38 | return Operation{me, id} 39 | } 40 | 41 | func (me *SingleFlight) Unlock(id string) { 42 | me.mu.Lock() 43 | on := me.ongoing[id] 44 | on.do.Unlock() 45 | on.users-- 46 | if on.users == 0 { 47 | delete(me.ongoing, id) 48 | } 49 | me.mu.Unlock() 50 | } 51 | -------------------------------------------------------------------------------- /slicepool/slicepool.go: -------------------------------------------------------------------------------- 1 | package slicepool 2 | 3 | import ( 4 | "sync" 5 | 6 | "golang.org/x/exp/constraints" 7 | ) 8 | 9 | type Pool[T any] struct { 10 | p sync.Pool 11 | } 12 | 13 | func (me *Pool[T]) Put(b *[]T) { 14 | // Why bother? 15 | if cap(*b) == 0 { 16 | return 17 | } 18 | *b = (*b)[:0] 19 | me.p.Put(b) 20 | } 21 | 22 | // Get one of whatever is available. If there's no indication of required cap, then presumably 23 | // pushing an item later will help raise the caps present in the pool. 24 | func (me *Pool[T]) Get() (ret *[]T) { 25 | gotAny := me.p.Get() 26 | if gotAny == nil { 27 | return new([]T) 28 | } 29 | ret = gotAny.(*[]T) 30 | *ret = (*ret)[:0] 31 | return ret 32 | } 33 | 34 | func GetMinCap[PoolT any, Cap constraints.Integer](pool *Pool[PoolT], minCap Cap) *[]PoolT { 35 | for { 36 | gotAny := pool.p.Get() 37 | if gotAny == nil { 38 | slice := make([]PoolT, 0, minCap) 39 | return &slice 40 | } 41 | b := gotAny.(*[]PoolT) 42 | if cap(*b) >= int(minCap) { 43 | return b 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /slices/cast.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/bradfitz/iter" 7 | ) 8 | 9 | // Returns a copy of all the elements of slice []T as a slice of interface{}. 10 | func ToEmptyInterface(slice interface{}) (ret []interface{}) { 11 | v := reflect.ValueOf(slice) 12 | l := v.Len() 13 | ret = make([]interface{}, 0, l) 14 | for i := range iter.N(v.Len()) { 15 | ret = append(ret, v.Index(i).Interface()) 16 | } 17 | return 18 | } 19 | 20 | // Makes and sets a slice at *ptrTo, and type asserts all the elements from 21 | // from to it. 22 | func MakeInto(ptrTo interface{}, from interface{}) { 23 | fromSliceValue := reflect.ValueOf(from) 24 | fromLen := fromSliceValue.Len() 25 | if fromLen == 0 { 26 | return 27 | } 28 | // Deref the pointer to slice. 29 | slicePtrValue := reflect.ValueOf(ptrTo) 30 | if slicePtrValue.Kind() != reflect.Ptr { 31 | panic("destination is not a pointer") 32 | } 33 | destSliceValue := slicePtrValue.Elem() 34 | // The type of the elements of the destination slice. 35 | destSliceElemType := destSliceValue.Type().Elem() 36 | destSliceValue.Set(reflect.MakeSlice(destSliceValue.Type(), fromLen, fromLen)) 37 | for i := range iter.N(fromSliceValue.Len()) { 38 | // The value inside the interface in the slice element. 39 | itemValue := fromSliceValue.Index(i) 40 | if itemValue.Kind() == reflect.Interface { 41 | itemValue = itemValue.Elem() 42 | } 43 | convertedItem := itemValue.Convert(destSliceElemType) 44 | destSliceValue.Index(i).Set(convertedItem) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /slices/cast_test.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type herp int 10 | 11 | func TestCastSliceInterface(t *testing.T) { 12 | var dest []herp 13 | MakeInto(&dest, []interface{}{herp(1), herp(2)}) 14 | assert.Len(t, dest, 2) 15 | assert.EqualValues(t, 1, dest[0]) 16 | assert.EqualValues(t, 2, dest[1]) 17 | } 18 | 19 | func TestCastSliceInts(t *testing.T) { 20 | var dest []int 21 | MakeInto(&dest, []uint32{1, 2}) 22 | assert.Len(t, dest, 2) 23 | assert.EqualValues(t, 1, dest[0]) 24 | assert.EqualValues(t, 2, dest[1]) 25 | } 26 | -------------------------------------------------------------------------------- /slices/doc.go: -------------------------------------------------------------------------------- 1 | // Package slices has several utilities for operating on slices given Go's 2 | // lack of generic types. Many functions take an argument of type func(l, r T) 3 | // bool, that's expected to compute l < r where T is T in []T, the type of the 4 | // given slice. 5 | package slices 6 | -------------------------------------------------------------------------------- /slices/filter.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import "reflect" 4 | 5 | // sl []T, f is func(*T) bool. 6 | func FilterInPlace(sl interface{}, f interface{}) { 7 | v := reflect.ValueOf(sl).Elem() 8 | j := 0 9 | for i := 0; i < v.Len(); i++ { 10 | e := v.Index(i) 11 | if reflect.ValueOf(f).Call([]reflect.Value{e.Addr()})[0].Bool() { 12 | v.Index(j).Set(e) 13 | j++ 14 | } 15 | } 16 | v.SetLen(j) 17 | } 18 | -------------------------------------------------------------------------------- /slices/map.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import "reflect" 4 | 5 | type MapItem struct { 6 | Key, Elem interface{} 7 | } 8 | 9 | // Creates a []struct{Key K; Value V} for map[K]V. 10 | func FromMap(m interface{}) (slice []MapItem) { 11 | mapValue := reflect.ValueOf(m) 12 | for _, key := range mapValue.MapKeys() { 13 | slice = append(slice, MapItem{key.Interface(), mapValue.MapIndex(key).Interface()}) 14 | } 15 | return 16 | } 17 | 18 | // Returns all the elements []T, from m where m is map[K]T. 19 | func FromMapElems(m interface{}) interface{} { 20 | inValue := reflect.ValueOf(m) 21 | outValue := reflect.MakeSlice(reflect.SliceOf(inValue.Type().Elem()), inValue.Len(), inValue.Len()) 22 | for i, key := range inValue.MapKeys() { 23 | outValue.Index(i).Set(inValue.MapIndex(key)) 24 | } 25 | return outValue.Interface() 26 | } 27 | 28 | // Returns all the elements []K, from m where m is map[K]T. 29 | func FromMapKeys(m interface{}) interface{} { 30 | inValue := reflect.ValueOf(m) 31 | outValue := reflect.MakeSlice(reflect.SliceOf(inValue.Type().Key()), inValue.Len(), inValue.Len()) 32 | for i, key := range inValue.MapKeys() { 33 | outValue.Index(i).Set(key) 34 | } 35 | return outValue.Interface() 36 | } 37 | 38 | // f: (T)T, input: []T, outout: []T 39 | func Map(f, input interface{}) interface{} { 40 | inputValue := reflect.ValueOf(input) 41 | funcValue := reflect.ValueOf(f) 42 | _len := inputValue.Len() 43 | retValue := reflect.MakeSlice(reflect.TypeOf(input), _len, _len) 44 | for i := 0; i < _len; i++ { 45 | out := funcValue.Call([]reflect.Value{inputValue.Index(i)}) 46 | retValue.Index(i).Set(out[0]) 47 | } 48 | return retValue.Interface() 49 | } 50 | -------------------------------------------------------------------------------- /slices/map_test.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFromMap(t *testing.T) { 10 | sl := FromMap(map[string]int{"two": 2, "one": 1}) 11 | assert.Len(t, sl, 2) 12 | Sort(sl, func(left, right MapItem) bool { 13 | return left.Key.(string) < right.Key.(string) 14 | }) 15 | assert.EqualValues(t, []MapItem{{"one", 1}, {"two", 2}}, sl) 16 | } 17 | -------------------------------------------------------------------------------- /slices/sort.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "container/heap" 5 | "reflect" 6 | "sort" 7 | ) 8 | 9 | // Sorts the slice in place. Returns sl for convenience. 10 | func Sort(sl interface{}, less interface{}) interface{} { 11 | sorter := sorter{ 12 | sl: reflect.ValueOf(sl), 13 | less: reflect.ValueOf(less), 14 | } 15 | sort.Sort(&sorter) 16 | return sorter.sl.Interface() 17 | } 18 | 19 | // Creates a modifiable copy of a slice reference. Because you can't modify 20 | // non-pointer types inside an interface{}. 21 | func addressableSlice(slice interface{}) reflect.Value { 22 | v := reflect.ValueOf(slice) 23 | p := reflect.New(v.Type()) 24 | p.Elem().Set(v) 25 | return p.Elem() 26 | } 27 | 28 | // Returns a "container/heap".Interface for the provided slice. 29 | func HeapInterface(sl interface{}, less interface{}) heap.Interface { 30 | ret := &sorter{ 31 | sl: addressableSlice(sl), 32 | less: reflect.ValueOf(less), 33 | } 34 | heap.Init(ret) 35 | return ret 36 | } 37 | -------------------------------------------------------------------------------- /slices/sort_test.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSort(t *testing.T) { 10 | a := []int{3, 2, 1} 11 | Sort(a, func(left, right int) bool { 12 | return left < right 13 | }) 14 | assert.EqualValues(t, []int{1, 2, 3}, a) 15 | } 16 | -------------------------------------------------------------------------------- /slices/sorter.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | import "reflect" 4 | 5 | type sorter struct { 6 | sl reflect.Value 7 | less reflect.Value 8 | } 9 | 10 | func (s *sorter) Len() int { 11 | return s.sl.Len() 12 | } 13 | 14 | func (s *sorter) Less(i, j int) bool { 15 | return s.less.Call([]reflect.Value{ 16 | s.sl.Index(i), 17 | s.sl.Index(j), 18 | })[0].Bool() 19 | } 20 | 21 | func (s *sorter) Swap(i, j int) { 22 | t := reflect.New(s.sl.Type().Elem()).Elem() 23 | t.Set(s.sl.Index(i)) 24 | s.sl.Index(i).Set(s.sl.Index(j)) 25 | s.sl.Index(j).Set(t) 26 | } 27 | 28 | func (s *sorter) Pop() interface{} { 29 | ret := s.sl.Index(s.sl.Len() - 1).Interface() 30 | s.sl.SetLen(s.sl.Len() - 1) 31 | return ret 32 | } 33 | 34 | func (s *sorter) Push(val interface{}) { 35 | s.sl = reflect.Append(s.sl, reflect.ValueOf(val)) 36 | } 37 | -------------------------------------------------------------------------------- /sqlite.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | ) 7 | 8 | type SqliteTime time.Time 9 | 10 | var _ sql.Scanner = (*SqliteTime)(nil) 11 | 12 | func (me *SqliteTime) Scan(src interface{}) error { 13 | var tt time.Time 14 | tt, err := time.Parse("2006-01-02 15:04:05", string(src.([]byte))) 15 | *me = SqliteTime(tt) 16 | return err 17 | } 18 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "runtime" 7 | ) 8 | 9 | func WriteStack(w io.Writer, stack []uintptr) { 10 | for _, pc := range stack { 11 | if pc == 0 { 12 | break 13 | } 14 | pc-- 15 | f := runtime.FuncForPC(pc) 16 | if f.Name() == "runtime.goexit" { 17 | continue 18 | } 19 | file, line := f.FileLine(pc) 20 | fmt.Fprintf(w, "# %s:\t%s:%d\n", f.Name(), file, line) 21 | } 22 | fmt.Fprintf(w, "\n") 23 | } 24 | -------------------------------------------------------------------------------- /strbool.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | func StringTruth(s string) (ret bool) { 10 | s = strings.TrimFunc(s, func(r rune) bool { 11 | return r == 0 || unicode.IsSpace(r) 12 | }) 13 | if s == "" { 14 | return false 15 | } 16 | ret, err := strconv.ParseBool(s) 17 | if err == nil { 18 | return 19 | } 20 | i, err := strconv.ParseInt(s, 0, 0) 21 | if err == nil { 22 | return i != 0 23 | } 24 | return true 25 | } 26 | -------------------------------------------------------------------------------- /strbool_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestStringTruth(t *testing.T) { 10 | for _, s := range []string{ 11 | "", 12 | " ", 13 | "\n", 14 | "\x00", 15 | "0", 16 | } { 17 | t.Run(s, func(t *testing.T) { 18 | assert.False(t, StringTruth(s)) 19 | }) 20 | } 21 | for _, s := range []string{ 22 | " 1", 23 | "t", 24 | } { 25 | t.Run(s, func(t *testing.T) { 26 | assert.True(t, StringTruth(s)) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /strcase.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/huandu/xstrings" 7 | ) 8 | 9 | func KebabCase(s string) string { 10 | return strings.Replace(xstrings.ToSnakeCase(s), "_", "-", -1) 11 | } 12 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type RWLocker interface { 8 | sync.Locker 9 | RLock() 10 | RUnlock() 11 | } 12 | -------------------------------------------------------------------------------- /testing.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "regexp" 5 | "runtime" 6 | ) 7 | 8 | // It will be the one and only identifier after a package specifier. 9 | var testNameRegexp = regexp.MustCompile(`\.(Test[\p{L}_\p{N}]*)`) 10 | 11 | // Returns the name of the test function from the call stack. See 12 | // http://stackoverflow.com/q/35535635/149482 for another method. 13 | func GetTestName() string { 14 | pc := make([]uintptr, 32) 15 | n := runtime.Callers(0, pc) 16 | for i := 0; i < n; i++ { 17 | name := runtime.FuncForPC(pc[i]).Name() 18 | ms := testNameRegexp.FindStringSubmatch(name) 19 | if ms == nil { 20 | continue 21 | } 22 | return ms[1] 23 | } 24 | panic("test name could not be recovered") 25 | } 26 | -------------------------------------------------------------------------------- /testing_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // Since GetTestName panics if the test name isn't found, it'll be easy to 10 | // expand the tests if we find weird cases. 11 | func TestGetTestName(t *testing.T) { 12 | assert.EqualValues(t, "TestGetTestName", GetTestName()) 13 | } 14 | 15 | func TestGetSubtestName(t *testing.T) { 16 | t.Run("hello", func(t *testing.T) { 17 | assert.Contains(t, "TestGetSubtestName", GetTestName()) 18 | }) 19 | t.Run("world", func(t *testing.T) { 20 | assert.Contains(t, "TestGetSubtestName", GetTestName()) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /timer.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "math" 5 | "time" 6 | ) 7 | 8 | // Returns a time.Timer that calls f. The timer is initially stopped. 9 | func StoppedFuncTimer(f func()) (t *time.Timer) { 10 | t = time.AfterFunc(math.MaxInt64, f) 11 | if !t.Stop() { 12 | panic("timer already fired") 13 | } 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /timer_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/bradfitz/iter" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTimerDrain(t *testing.T) { 12 | tr := time.NewTimer(0) 13 | <-tr.C 14 | select { 15 | case <-tr.C: 16 | assert.Fail(t, "shouldn't have received again on the the expired timer") 17 | default: 18 | } 19 | tr.Reset(1) 20 | select { 21 | case <-tr.C: 22 | assert.Fail(t, "received too soon") 23 | default: 24 | } 25 | time.Sleep(1) 26 | <-tr.C 27 | // Stop() should return false, as it just fired. 28 | assert.False(t, tr.Stop()) 29 | tr.Reset(0) 30 | // Check we receive again after a Reset(). 31 | <-tr.C 32 | } 33 | 34 | func TestTimerDoesNotFireAfterStop(t *testing.T) { 35 | t.Skip("the standard library implementation is broken") 36 | fail := make(chan struct{}) 37 | done := make(chan struct{}) 38 | defer close(done) 39 | for range iter.N(1000) { 40 | tr := time.NewTimer(0) 41 | tr.Stop() 42 | // There may or may not be a value in the channel now. But definitely 43 | // one should not be added after we receive it. 44 | select { 45 | case <-tr.C: 46 | default: 47 | } 48 | // Now set the timer to trigger in hour. It definitely shouldn't be 49 | // receivable now for an hour. 50 | tr.Reset(time.Hour) 51 | go func() { 52 | select { 53 | case <-tr.C: 54 | // As soon as the channel receives, notify failure. 55 | fail <- struct{}{} 56 | case <-done: 57 | } 58 | }() 59 | } 60 | select { 61 | case <-fail: 62 | t.FailNow() 63 | case <-time.After(100 * time.Millisecond): 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tls.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "crypto/tls" 5 | "strings" 6 | ) 7 | 8 | // Select the best named certificate per the usual behaviour if 9 | // c.GetCertificate is nil, and c.NameToCertificate is not. 10 | func BestNamedCertificate(c *tls.Config, clientHello *tls.ClientHelloInfo) (*tls.Certificate, bool) { 11 | name := strings.ToLower(clientHello.ServerName) 12 | for len(name) > 0 && name[len(name)-1] == '.' { 13 | name = name[:len(name)-1] 14 | } 15 | 16 | if cert, ok := c.NameToCertificate[name]; ok { 17 | return cert, true 18 | } 19 | 20 | // try replacing labels in the name with wildcards until we get a 21 | // match. 22 | labels := strings.Split(name, ".") 23 | for i := range labels { 24 | labels[i] = "*" 25 | candidate := strings.Join(labels, ".") 26 | if cert, ok := c.NameToCertificate[candidate]; ok { 27 | return cert, true 28 | } 29 | } 30 | 31 | return nil, false 32 | } 33 | -------------------------------------------------------------------------------- /units.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | const MiB = 1 << 20 4 | -------------------------------------------------------------------------------- /url.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | ) 7 | 8 | // Returns URL opaque as an unrooted path. 9 | func URLOpaquePath(u *url.URL) string { 10 | if u.Opaque != "" { 11 | return u.Opaque 12 | } 13 | return u.Path 14 | } 15 | 16 | // Cleans the (absolute) URL path, removing unnecessary . and .. elements. See 17 | // "net/http".cleanPath. 18 | func CleanURLPath(p string) string { 19 | if p == "" { 20 | return "/" 21 | } 22 | if p[0] != '/' { 23 | p = "/" + p 24 | } 25 | cp := path.Clean(p) 26 | // Add the trailing slash back, as it's relevant to a URL. 27 | if p[len(p)-1] == '/' && cp != "/" { 28 | cp += "/" 29 | } 30 | return cp 31 | } 32 | 33 | func URLJoinSubPath(base, rel string) string { 34 | baseURL, err := url.Parse(base) 35 | if err != nil { 36 | // Honey badger doesn't give a fuck. 37 | panic(err) 38 | } 39 | rel = CleanURLPath(rel) 40 | baseURL.Path = path.Join(baseURL.Path, rel) 41 | return baseURL.String() 42 | } 43 | 44 | // This exists because it's nontrivial to get everything after the scheme in a URL. You can't just 45 | // use URL.Opaque because path handling kicks in if there's a slash after the scheme's colon. 46 | func PopScheme(u *url.URL) (scheme string, popped string) { 47 | // We can copy just the part we modify. We can't modify it in place because the caller could pass 48 | // through directly from the app arguments, and they don't know how deep our mutation goes. 49 | uCopy := *u 50 | scheme = uCopy.Scheme 51 | uCopy.Scheme = "" 52 | popped = uCopy.String() 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /url_test.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | qt "github.com/frankban/quicktest" 5 | "net/url" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestURLOpaquePath(t *testing.T) { 12 | assert.Equal(t, "sqlite3://sqlite3.db", (&url.URL{Scheme: "sqlite3", Path: "sqlite3.db"}).String()) 13 | u, err := url.Parse("sqlite3:sqlite3.db") 14 | assert.NoError(t, err) 15 | assert.Equal(t, "sqlite3.db", URLOpaquePath(u)) 16 | assert.Equal(t, "sqlite3:sqlite3.db", (&url.URL{Scheme: "sqlite3", Opaque: "sqlite3.db"}).String()) 17 | assert.Equal(t, "sqlite3:/sqlite3.db", (&url.URL{Scheme: "sqlite3", Opaque: "/sqlite3.db"}).String()) 18 | u, err = url.Parse("sqlite3:/sqlite3.db") 19 | assert.NoError(t, err) 20 | assert.Equal(t, "/sqlite3.db", u.Path) 21 | assert.Equal(t, "/sqlite3.db", URLOpaquePath(u)) 22 | } 23 | 24 | func testSchemePopping(t *testing.T, opaque string, expectedPath string) { 25 | searchDb := &url.URL{ 26 | Scheme: "caterwaul", 27 | Opaque: "pebble:" + opaque, 28 | } 29 | c := qt.New(t) 30 | scheme, poppedUrlStr := PopScheme(searchDb) 31 | c.Check(scheme, qt.Equals, "caterwaul") 32 | poppedUrl, err := url.Parse(poppedUrlStr) 33 | c.Assert(err, qt.IsNil) 34 | scheme, poppedUrlStr = PopScheme(poppedUrl) 35 | c.Check(scheme, qt.Equals, "pebble") 36 | c.Check(poppedUrlStr, qt.Equals, expectedPath) 37 | } 38 | 39 | func TestSchemePopping(t *testing.T) { 40 | testSchemePopping(t, "caterwaul-pebble-search", "caterwaul-pebble-search") 41 | testSchemePopping(t, "/home/derp/cove/caterwaul-pebble-search", "/home/derp/cove/caterwaul-pebble-search") 42 | testSchemePopping(t, `C:\Users\derp\LocalData\`, `C:\Users\derp\LocalData\`) 43 | } 44 | -------------------------------------------------------------------------------- /wait_event.go: -------------------------------------------------------------------------------- 1 | package missinggo 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | func WaitEvents(l sync.Locker, evs ...*Event) { 9 | cases := make([]reflect.SelectCase, 0, len(evs)) 10 | for _, ev := range evs { 11 | cases = append(cases, reflect.SelectCase{ 12 | Dir: reflect.SelectRecv, 13 | Chan: reflect.ValueOf(ev.C()), 14 | }) 15 | } 16 | l.Unlock() 17 | reflect.Select(cases) 18 | l.Lock() 19 | } 20 | -------------------------------------------------------------------------------- /x/panic.go: -------------------------------------------------------------------------------- 1 | package x 2 | 3 | // Panic if error. Just fucking add exceptions, please. 4 | func Pie(err error) { 5 | if err != nil { 6 | panic(err) 7 | } 8 | } 9 | --------------------------------------------------------------------------------