Caption support (#2462)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
cj
2022-05-05 20:59:28 -05:00
committed by GitHub
parent ab1b30ffb7
commit c1a096a1a6
114 changed files with 16899 additions and 17 deletions

8
vendor/github.com/asticode/go-astikit/.travis.sh generated vendored Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
if [ "$(go list -m all)" != "github.com/asticode/go-astikit" ]; then
echo "This repo doesn't allow any external dependencies"
exit 1
else
echo "cheers!"
fi

15
vendor/github.com/asticode/go-astikit/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
language: go
go:
- 1.x
- tip
install:
- bash .travis.sh
- go get -t ./...
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
matrix:
allow_failures:
- go: tip
script:
- go test -race -v -coverprofile=coverage.out
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

21
vendor/github.com/asticode/go-astikit/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Quentin Renard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
vendor/github.com/asticode/go-astikit/README.md generated vendored Normal file
View File

@@ -0,0 +1,6 @@
[![GoReportCard](http://goreportcard.com/badge/github.com/asticode/go-astikit)](http://goreportcard.com/report/github.com/asticode/go-astikit)
[![GoDoc](https://godoc.org/github.com/asticode/go-astikit?status.svg)](https://godoc.org/github.com/asticode/go-astikit)
[![Travis](https://travis-ci.org/asticode/go-astikit.svg?branch=master)](https://travis-ci.org/asticode/go-astikit#)
[![Coveralls](https://coveralls.io/repos/github/asticode/go-astikit/badge.svg?branch=master)](https://coveralls.io/github/asticode/go-astikit)
`astikit` is a set of golang helpers that don't require any external dependencies.

214
vendor/github.com/asticode/go-astikit/archive.go generated vendored Normal file
View File

@@ -0,0 +1,214 @@
package astikit
import (
"archive/zip"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// internal shouldn't lead with a "/"
func zipInternalPath(p string) (external, internal string) {
if items := strings.Split(p, ".zip"); len(items) > 1 {
external = items[0] + ".zip"
internal = strings.TrimPrefix(strings.Join(items[1:], ".zip"), string(os.PathSeparator))
return
}
external = p
return
}
// Zip zips a src into a dst
// Possible dst formats are:
// - /path/to/zip.zip
// - /path/to/zip.zip/root/path
func Zip(ctx context.Context, dst, src string) (err error) {
// Get external/internal path
externalPath, internalPath := zipInternalPath(dst)
// Make sure the directory exists
if err = os.MkdirAll(filepath.Dir(externalPath), DefaultDirMode); err != nil {
return fmt.Errorf("astikit: mkdirall %s failed: %w", filepath.Dir(externalPath), err)
}
// Create destination file
var dstFile *os.File
if dstFile, err = os.Create(externalPath); err != nil {
return fmt.Errorf("astikit: creating %s failed: %w", externalPath, err)
}
defer dstFile.Close()
// Create zip writer
var zw = zip.NewWriter(dstFile)
defer zw.Close()
// Walk
if err = filepath.Walk(src, func(path string, info os.FileInfo, e error) (err error) {
// Process error
if e != nil {
err = e
return
}
// Init header
var h *zip.FileHeader
if h, err = zip.FileInfoHeader(info); err != nil {
return fmt.Errorf("astikit: initializing zip header failed: %w", err)
}
// Set header info
h.Name = filepath.Join(internalPath, strings.TrimPrefix(path, src))
if info.IsDir() {
h.Name += string(os.PathSeparator)
} else {
h.Method = zip.Deflate
}
// Create writer
var w io.Writer
if w, err = zw.CreateHeader(h); err != nil {
return fmt.Errorf("astikit: creating zip header failed: %w", err)
}
// If path is dir, stop here
if info.IsDir() {
return
}
// Open path
var walkFile *os.File
if walkFile, err = os.Open(path); err != nil {
return fmt.Errorf("astikit: opening %s failed: %w", path, err)
}
defer walkFile.Close()
// Copy
if _, err = Copy(ctx, w, walkFile); err != nil {
return fmt.Errorf("astikit: copying failed: %w", err)
}
return
}); err != nil {
return fmt.Errorf("astikit: walking failed: %w", err)
}
return
}
// Unzip unzips a src into a dst
// Possible src formats are:
// - /path/to/zip.zip
// - /path/to/zip.zip/root/path
func Unzip(ctx context.Context, dst, src string) (err error) {
// Get external/internal path
externalPath, internalPath := zipInternalPath(src)
// Make sure the destination exists
if err = os.MkdirAll(dst, DefaultDirMode); err != nil {
return fmt.Errorf("astikit: mkdirall %s failed: %w", dst, err)
}
// Open overall reader
var r *zip.ReadCloser
if r, err = zip.OpenReader(externalPath); err != nil {
return fmt.Errorf("astikit: opening overall zip reader on %s failed: %w", externalPath, err)
}
defer r.Close()
// Loop through files to determine their type
var dirs, files, symlinks = make(map[string]*zip.File), make(map[string]*zip.File), make(map[string]*zip.File)
for _, f := range r.File {
// Validate internal path
if internalPath != "" && !strings.HasPrefix(f.Name, internalPath) {
continue
}
var p = filepath.Join(dst, strings.TrimPrefix(f.Name, internalPath))
// Check file type
if f.FileInfo().Mode()&os.ModeSymlink != 0 {
symlinks[p] = f
} else if f.FileInfo().IsDir() {
dirs[p] = f
} else {
files[p] = f
}
}
// Invalid internal path
if internalPath != "" && len(dirs) == 0 && len(files) == 0 && len(symlinks) == 0 {
return fmt.Errorf("astikit: content in archive does not match specified internal path %s", internalPath)
}
// Create dirs
for p, f := range dirs {
if err = os.MkdirAll(p, f.FileInfo().Mode().Perm()); err != nil {
return fmt.Errorf("astikit: mkdirall %s failed: %w", p, err)
}
}
// Create files
for p, f := range files {
if err = createZipFile(ctx, f, p); err != nil {
return fmt.Errorf("astikit: creating zip file into %s failed: %w", p, err)
}
}
// Create symlinks
for p, f := range symlinks {
if err = createZipSymlink(f, p); err != nil {
return fmt.Errorf("astikit: creating zip symlink into %s failed: %w", p, err)
}
}
return
}
func createZipFile(ctx context.Context, f *zip.File, p string) (err error) {
// Open file reader
var fr io.ReadCloser
if fr, err = f.Open(); err != nil {
return fmt.Errorf("astikit: opening zip reader on file %s failed: %w", f.Name, err)
}
defer fr.Close()
// Since dirs don't always come up we make sure the directory of the file exists with default
// file mode
if err = os.MkdirAll(filepath.Dir(p), DefaultDirMode); err != nil {
return fmt.Errorf("astikit: mkdirall %s failed: %w", filepath.Dir(p), err)
}
// Open the file
var fl *os.File
if fl, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.FileInfo().Mode().Perm()); err != nil {
return fmt.Errorf("astikit: opening file %s failed: %w", p, err)
}
defer fl.Close()
// Copy
if _, err = Copy(ctx, fl, fr); err != nil {
return fmt.Errorf("astikit: copying %s into %s failed: %w", f.Name, p, err)
}
return
}
func createZipSymlink(f *zip.File, p string) (err error) {
// Open file reader
var fr io.ReadCloser
if fr, err = f.Open(); err != nil {
return fmt.Errorf("astikit: opening zip reader on file %s failed: %w", f.Name, err)
}
defer fr.Close()
// If file is a symlink we retrieve the target path that is in the content of the file
var b []byte
if b, err = ioutil.ReadAll(fr); err != nil {
return fmt.Errorf("astikit: ioutil.Readall on %s failed: %w", f.Name, err)
}
// Create the symlink
if err = os.Symlink(string(b), p); err != nil {
return fmt.Errorf("astikit: creating symlink from %s to %s failed: %w", string(b), p, err)
}
return
}

8
vendor/github.com/asticode/go-astikit/astikit.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
package astikit
import "os"
// Default modes
var (
DefaultDirMode os.FileMode = 0755
)

297
vendor/github.com/asticode/go-astikit/binary.go generated vendored Normal file
View File

@@ -0,0 +1,297 @@
package astikit
import (
"encoding/binary"
"errors"
"io"
)
// BitsWriter represents an object that can write individual bits into a writer
// in a developer-friendly way. Check out the Write method for more information.
// This is particularly helpful when you want to build a slice of bytes based
// on individual bits for testing purposes.
type BitsWriter struct {
bo binary.ByteOrder
cache byte
cacheLen byte
bsCache []byte
w io.Writer
writeCb BitsWriterWriteCallback
}
type BitsWriterWriteCallback func([]byte)
// BitsWriterOptions represents BitsWriter options
type BitsWriterOptions struct {
ByteOrder binary.ByteOrder
// WriteCallback is called every time when full byte is written
WriteCallback BitsWriterWriteCallback
Writer io.Writer
}
// NewBitsWriter creates a new BitsWriter
func NewBitsWriter(o BitsWriterOptions) (w *BitsWriter) {
w = &BitsWriter{
bo: o.ByteOrder,
bsCache: make([]byte, 1),
w: o.Writer,
writeCb: o.WriteCallback,
}
if w.bo == nil {
w.bo = binary.BigEndian
}
return
}
func (w *BitsWriter) SetWriteCallback(cb BitsWriterWriteCallback) {
w.writeCb = cb
}
// Write writes bits into the writer. Bits are only written when there are
// enough to create a byte. When using a string or a bool, bits are added
// from left to right as if
// Available types are:
// - string("10010"): processed as n bits, n being the length of the input
// - []byte: processed as n bytes, n being the length of the input
// - bool: processed as one bit
// - uint8/uint16/uint32/uint64: processed as n bits, if type is uintn
func (w *BitsWriter) Write(i interface{}) error {
// Transform input into "10010" format
switch a := i.(type) {
case string:
for _, r := range a {
var err error
if r == '1' {
err = w.writeBit(1)
} else {
err = w.writeBit(0)
}
if err != nil {
return err
}
}
case []byte:
for _, b := range a {
if err := w.writeFullByte(b); err != nil {
return err
}
}
case bool:
if a {
return w.writeBit(1)
} else {
return w.writeBit(0)
}
case uint8:
return w.writeFullByte(a)
case uint16:
return w.writeFullInt(uint64(a), 2)
case uint32:
return w.writeFullInt(uint64(a), 4)
case uint64:
return w.writeFullInt(a, 8)
default:
return errors.New("astikit: invalid type")
}
return nil
}
// Writes exactly n bytes from bs
// Writes first n bytes of bs if len(bs) > n
// Pads with padByte at the end if len(bs) < n
func (w *BitsWriter) WriteBytesN(bs []byte, n int, padByte uint8) error {
if len(bs) >= n {
return w.Write(bs[:n])
}
if err := w.Write(bs); err != nil {
return err
}
// no bytes.Repeat here to avoid allocation
for i := 0; i < n-len(bs); i++ {
if err := w.Write(padByte); err != nil {
return err
}
}
return nil
}
func (w *BitsWriter) writeFullInt(in uint64, len int) error {
if w.bo == binary.BigEndian {
for i := len - 1; i >= 0; i-- {
err := w.writeFullByte(byte((in >> (i * 8)) & 0xff))
if err != nil {
return err
}
}
} else {
for i := 0; i < len; i++ {
err := w.writeFullByte(byte((in >> (i * 8)) & 0xff))
if err != nil {
return err
}
}
}
return nil
}
func (w *BitsWriter) flushBsCache() error {
if _, err := w.w.Write(w.bsCache); err != nil {
return err
}
if w.writeCb != nil {
w.writeCb(w.bsCache)
}
return nil
}
func (w *BitsWriter) writeFullByte(b byte) error {
if w.cacheLen == 0 {
w.bsCache[0] = b
} else {
w.bsCache[0] = w.cache | (b >> w.cacheLen)
w.cache = b << (8 - w.cacheLen)
}
return w.flushBsCache()
}
func (w *BitsWriter) writeBit(bit byte) error {
w.cache = w.cache | (bit)<<(7-w.cacheLen)
w.cacheLen++
if w.cacheLen == 8 {
w.bsCache[0] = w.cache
if err := w.flushBsCache(); err != nil {
return err
}
w.cacheLen = 0
w.cache = 0
}
return nil
}
// WriteN writes the input into n bits
func (w *BitsWriter) WriteN(i interface{}, n int) error {
var toWrite uint64
switch a := i.(type) {
case uint8:
toWrite = uint64(a)
case uint16:
toWrite = uint64(a)
case uint32:
toWrite = uint64(a)
case uint64:
toWrite = a
default:
return errors.New("astikit: invalid type")
}
for i := n - 1; i >= 0; i-- {
err := w.writeBit(byte(toWrite>>i) & 0x1)
if err != nil {
return err
}
}
return nil
}
// BitsWriterBatch allows to chain multiple Write* calls and check for error only once
// For more info see https://github.com/asticode/go-astikit/pull/6
type BitsWriterBatch struct {
err error
w *BitsWriter
}
func NewBitsWriterBatch(w *BitsWriter) BitsWriterBatch {
return BitsWriterBatch{
w: w,
}
}
// Calls BitsWriter.Write if there was no write error before
func (b *BitsWriterBatch) Write(i interface{}) {
if b.err == nil {
b.err = b.w.Write(i)
}
}
// Calls BitsWriter.WriteN if there was no write error before
func (b *BitsWriterBatch) WriteN(i interface{}, n int) {
if b.err == nil {
b.err = b.w.WriteN(i, n)
}
}
// Calls BitsWriter.WriteBytesN if there was no write error before
func (b *BitsWriterBatch) WriteBytesN(bs []byte, n int, padByte uint8) {
if b.err == nil {
b.err = b.w.WriteBytesN(bs, n, padByte)
}
}
// Returns first write error
func (b *BitsWriterBatch) Err() error {
return b.err
}
var byteHamming84Tab = [256]uint8{
0x01, 0xff, 0xff, 0x08, 0xff, 0x0c, 0x04, 0xff, 0xff, 0x08, 0x08, 0x08, 0x06, 0xff, 0xff, 0x08,
0xff, 0x0a, 0x02, 0xff, 0x06, 0xff, 0xff, 0x0f, 0x06, 0xff, 0xff, 0x08, 0x06, 0x06, 0x06, 0xff,
0xff, 0x0a, 0x04, 0xff, 0x04, 0xff, 0x04, 0x04, 0x00, 0xff, 0xff, 0x08, 0xff, 0x0d, 0x04, 0xff,
0x0a, 0x0a, 0xff, 0x0a, 0xff, 0x0a, 0x04, 0xff, 0xff, 0x0a, 0x03, 0xff, 0x06, 0xff, 0xff, 0x0e,
0x01, 0x01, 0x01, 0xff, 0x01, 0xff, 0xff, 0x0f, 0x01, 0xff, 0xff, 0x08, 0xff, 0x0d, 0x05, 0xff,
0x01, 0xff, 0xff, 0x0f, 0xff, 0x0f, 0x0f, 0x0f, 0xff, 0x0b, 0x03, 0xff, 0x06, 0xff, 0xff, 0x0f,
0x01, 0xff, 0xff, 0x09, 0xff, 0x0d, 0x04, 0xff, 0xff, 0x0d, 0x03, 0xff, 0x0d, 0x0d, 0xff, 0x0d,
0xff, 0x0a, 0x03, 0xff, 0x07, 0xff, 0xff, 0x0f, 0x03, 0xff, 0x03, 0x03, 0xff, 0x0d, 0x03, 0xff,
0xff, 0x0c, 0x02, 0xff, 0x0c, 0x0c, 0xff, 0x0c, 0x00, 0xff, 0xff, 0x08, 0xff, 0x0c, 0x05, 0xff,
0x02, 0xff, 0x02, 0x02, 0xff, 0x0c, 0x02, 0xff, 0xff, 0x0b, 0x02, 0xff, 0x06, 0xff, 0xff, 0x0e,
0x00, 0xff, 0xff, 0x09, 0xff, 0x0c, 0x04, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x0e,
0xff, 0x0a, 0x02, 0xff, 0x07, 0xff, 0xff, 0x0e, 0x00, 0xff, 0xff, 0x0e, 0xff, 0x0e, 0x0e, 0x0e,
0x01, 0xff, 0xff, 0x09, 0xff, 0x0c, 0x05, 0xff, 0xff, 0x0b, 0x05, 0xff, 0x05, 0xff, 0x05, 0x05,
0xff, 0x0b, 0x02, 0xff, 0x07, 0xff, 0xff, 0x0f, 0x0b, 0x0b, 0xff, 0x0b, 0xff, 0x0b, 0x05, 0xff,
0xff, 0x09, 0x09, 0x09, 0x07, 0xff, 0xff, 0x09, 0x00, 0xff, 0xff, 0x09, 0xff, 0x0d, 0x05, 0xff,
0x07, 0xff, 0xff, 0x09, 0x07, 0x07, 0x07, 0xff, 0xff, 0x0b, 0x03, 0xff, 0x07, 0xff, 0xff, 0x0e,
}
// ByteHamming84Decode hamming 8/4 decodes
func ByteHamming84Decode(i uint8) (o uint8, ok bool) {
o = byteHamming84Tab[i]
if o == 0xff {
return
}
ok = true
return
}
var byteParityTab = [256]uint8{
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
}
// ByteParity returns the byte parity
func ByteParity(i uint8) (o uint8, ok bool) {
ok = byteParityTab[i] == 1
o = i & 0x7f
return
}

164
vendor/github.com/asticode/go-astikit/bytes.go generated vendored Normal file
View File

@@ -0,0 +1,164 @@
package astikit
import "fmt"
// BytesIterator represents an object capable of iterating sequentially and safely
// through a slice of bytes. This is particularly useful when you need to iterate
// through a slice of bytes and don't want to check for "index out of range" errors
// manually.
type BytesIterator struct {
bs []byte
offset int
}
// NewBytesIterator creates a new BytesIterator
func NewBytesIterator(bs []byte) *BytesIterator {
return &BytesIterator{bs: bs}
}
// NextByte returns the next byte
func (i *BytesIterator) NextByte() (b byte, err error) {
if len(i.bs) < i.offset+1 {
err = fmt.Errorf("astikit: slice length is %d, offset %d is invalid", len(i.bs), i.offset)
return
}
b = i.bs[i.offset]
i.offset++
return
}
// NextBytes returns the n next bytes
func (i *BytesIterator) NextBytes(n int) (bs []byte, err error) {
if len(i.bs) < i.offset+n {
err = fmt.Errorf("astikit: slice length is %d, offset %d is invalid", len(i.bs), i.offset+n)
return
}
bs = make([]byte, n)
copy(bs, i.bs[i.offset:i.offset+n])
i.offset += n
return
}
// NextBytesNoCopy returns the n next bytes
// Be careful with this function as it doesn't make a copy of returned data.
// bs will point to internal BytesIterator buffer.
// If you need to modify returned bytes or store it for some time, use NextBytes instead
func (i *BytesIterator) NextBytesNoCopy(n int) (bs []byte, err error) {
if len(i.bs) < i.offset+n {
err = fmt.Errorf("astikit: slice length is %d, offset %d is invalid", len(i.bs), i.offset+n)
return
}
bs = i.bs[i.offset : i.offset+n]
i.offset += n
return
}
// Seek seeks to the nth byte
func (i *BytesIterator) Seek(n int) {
i.offset = n
}
// Skip skips the n previous/next bytes
func (i *BytesIterator) Skip(n int) {
i.offset += n
}
// HasBytesLeft checks whether there are bytes left
func (i *BytesIterator) HasBytesLeft() bool {
return i.offset < len(i.bs)
}
// Offset returns the offset
func (i *BytesIterator) Offset() int {
return i.offset
}
// Dump dumps the rest of the slice
func (i *BytesIterator) Dump() (bs []byte) {
if !i.HasBytesLeft() {
return
}
bs = make([]byte, len(i.bs)-i.offset)
copy(bs, i.bs[i.offset:len(i.bs)])
i.offset = len(i.bs)
return
}
// Len returns the slice length
func (i *BytesIterator) Len() int {
return len(i.bs)
}
const (
padRight = "right"
padLeft = "left"
)
type bytesPadder struct {
cut bool
direction string
length int
repeat byte
}
func newBytesPadder(repeat byte, length int) *bytesPadder {
return &bytesPadder{
direction: padLeft,
length: length,
repeat: repeat,
}
}
func (p *bytesPadder) pad(i []byte) []byte {
if len(i) == p.length {
return i
} else if len(i) > p.length {
if p.cut {
return i[:p.length]
}
return i
} else {
o := make([]byte, len(i))
copy(o, i)
for idx := 0; idx < p.length-len(i); idx++ {
if p.direction == padRight {
o = append(o, p.repeat)
} else {
o = append([]byte{p.repeat}, o...)
}
o = append(o, p.repeat)
}
o = o[:p.length]
return o
}
}
// PadOption represents a Pad option
type PadOption func(p *bytesPadder)
// PadCut is a PadOption
// It indicates to the padder it must cut the input to the provided length
// if its original length is bigger
func PadCut(p *bytesPadder) { p.cut = true }
// PadLeft is a PadOption
// It indicates additionnal bytes have to be added to the left
func PadLeft(p *bytesPadder) { p.direction = padLeft }
// PadRight is a PadOption
// It indicates additionnal bytes have to be added to the right
func PadRight(p *bytesPadder) { p.direction = padRight }
// BytesPad pads the slice of bytes with additionnal options
func BytesPad(i []byte, repeat byte, length int, options ...PadOption) []byte {
p := newBytesPadder(repeat, length)
for _, o := range options {
o(p)
}
return p.pad(i)
}
// StrPad pads the string with additionnal options
func StrPad(i string, repeat rune, length int, options ...PadOption) string {
return string(BytesPad([]byte(i), byte(repeat), length, options...))
}

57
vendor/github.com/asticode/go-astikit/defer.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package astikit
import (
"sync"
)
// CloseFunc is a method that closes something
type CloseFunc func() error
// Closer is an object that can close several things
type Closer struct {
fs []CloseFunc
m *sync.Mutex
}
// NewCloser creates a new closer
func NewCloser() *Closer {
return &Closer{
m: &sync.Mutex{},
}
}
// Close implements the io.Closer interface
func (c *Closer) Close() error {
// Lock
c.m.Lock()
defer c.m.Unlock()
// Loop through closers
err := NewErrors()
for _, f := range c.fs {
err.Add(f())
}
// Reset closers
c.fs = []CloseFunc{}
// Return
if err.IsNil() {
return nil
}
return err
}
// Add adds a close func at the beginning of the list
func (c *Closer) Add(f CloseFunc) {
c.m.Lock()
defer c.m.Unlock()
c.fs = append([]CloseFunc{f}, c.fs...)
}
// NewChild creates a new child closer
func (c *Closer) NewChild() (child *Closer) {
child = NewCloser()
c.Add(child.Close)
return
}

71
vendor/github.com/asticode/go-astikit/errors.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package astikit
import (
"errors"
"strings"
"sync"
)
// Errors is an error containing multiple errors
type Errors struct {
m *sync.Mutex // Locks p
p []error
}
// NewErrors creates new errors
func NewErrors(errs ...error) *Errors {
return &Errors{
m: &sync.Mutex{},
p: errs,
}
}
// Add adds a new error
func (errs *Errors) Add(err error) {
if err == nil {
return
}
errs.m.Lock()
defer errs.m.Unlock()
errs.p = append(errs.p, err)
}
// IsNil checks whether the error is nil
func (errs *Errors) IsNil() bool {
errs.m.Lock()
defer errs.m.Unlock()
return len(errs.p) == 0
}
// Loop loops through the errors
func (errs *Errors) Loop(fn func(idx int, err error) bool) {
errs.m.Lock()
defer errs.m.Unlock()
for idx, err := range errs.p {
if stop := fn(idx, err); stop {
return
}
}
}
// Error implements the error interface
func (errs *Errors) Error() string {
errs.m.Lock()
defer errs.m.Unlock()
var ss []string
for _, err := range errs.p {
ss = append(ss, err.Error())
}
return strings.Join(ss, " && ")
}
// ErrorCause returns the cause of an error
func ErrorCause(err error) error {
for {
if u := errors.Unwrap(err); u != nil {
err = u
continue
}
return err
}
}

104
vendor/github.com/asticode/go-astikit/exec.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
package astikit
import (
"context"
"fmt"
"os/exec"
"strings"
"sync"
)
// Statuses
const (
ExecStatusCrashed = "crashed"
ExecStatusRunning = "running"
ExecStatusStopped = "stopped"
)
// ExecHandler represents an object capable of handling the execution of a cmd
type ExecHandler struct {
cancel context.CancelFunc
ctx context.Context
err error
o sync.Once
stopped bool
}
// Status returns the cmd status
func (h *ExecHandler) Status() string {
if h.ctx.Err() != nil {
if h.stopped || h.err == nil {
return ExecStatusStopped
}
return ExecStatusCrashed
}
return ExecStatusRunning
}
// Stop stops the cmd
func (h *ExecHandler) Stop() {
h.o.Do(func() {
h.cancel()
h.stopped = true
})
}
// ExecCmdOptions represents exec options
type ExecCmdOptions struct {
Args []string
CmdAdapter func(cmd *exec.Cmd, h *ExecHandler) error
Name string
StopFunc func(cmd *exec.Cmd) error
}
// ExecCmd executes a cmd
// The process will be stopped when the worker stops
func ExecCmd(w *Worker, o ExecCmdOptions) (h *ExecHandler, err error) {
// Create handler
h = &ExecHandler{}
h.ctx, h.cancel = context.WithCancel(w.Context())
// Create command
cmd := exec.Command(o.Name, o.Args...)
// Adapt command
if o.CmdAdapter != nil {
if err = o.CmdAdapter(cmd, h); err != nil {
err = fmt.Errorf("astikit: adapting cmd failed: %w", err)
return
}
}
// Start
w.Logger().Infof("astikit: starting %s", strings.Join(cmd.Args, " "))
if err = cmd.Start(); err != nil {
err = fmt.Errorf("astikit: executing %s: %w", strings.Join(cmd.Args, " "), err)
return
}
// Handle context
go func() {
// Wait for context to be done
<-h.ctx.Done()
// Get stop func
f := func() error { return cmd.Process.Kill() }
if o.StopFunc != nil {
f = func() error { return o.StopFunc(cmd) }
}
// Stop
if err = f(); err != nil {
w.Logger().Error(fmt.Errorf("astikit: stopping cmd failed: %w", err))
return
}
}()
// Execute in a task
w.NewTask().Do(func() {
h.err = cmd.Wait()
h.cancel()
w.Logger().Infof("astikit: status is now %s for %s", h.Status(), strings.Join(cmd.Args, " "))
})
return
}

48
vendor/github.com/asticode/go-astikit/flag.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package astikit
import (
"os"
"strings"
)
// FlagCmd retrieves the command from the input Args
func FlagCmd() (o string) {
if len(os.Args) >= 2 && os.Args[1][0] != '-' {
o = os.Args[1]
os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
}
return
}
// FlagStrings represents a flag that can be set several times and
// stores unique string values
type FlagStrings struct {
Map map[string]bool
Slice *[]string
}
// NewFlagStrings creates a new FlagStrings
func NewFlagStrings() FlagStrings {
return FlagStrings{
Map: make(map[string]bool),
Slice: &[]string{},
}
}
// String implements the flag.Value interface
func (f FlagStrings) String() string {
if f.Slice == nil {
return ""
}
return strings.Join(*f.Slice, ",")
}
// Set implements the flag.Value interface
func (f FlagStrings) Set(i string) error {
if _, ok := f.Map[i]; ok {
return nil
}
f.Map[i] = true
*f.Slice = append(*f.Slice, i)
return nil
}

60
vendor/github.com/asticode/go-astikit/float.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package astikit
import (
"bytes"
"fmt"
"strconv"
)
// Rational represents a rational
type Rational struct{ den, num int }
// NewRational creates a new rational
func NewRational(num, den int) *Rational {
return &Rational{
den: den,
num: num,
}
}
// Num returns the rational num
func (r *Rational) Num() int {
return r.num
}
// Den returns the rational den
func (r *Rational) Den() int {
return r.den
}
// ToFloat64 returns the rational as a float64
func (r *Rational) ToFloat64() float64 {
return float64(r.num) / float64(r.den)
}
// MarshalText implements the TextMarshaler interface
func (r *Rational) MarshalText() (b []byte, err error) {
b = []byte(fmt.Sprintf("%d/%d", r.num, r.den))
return
}
// UnmarshalText implements the TextUnmarshaler interface
func (r *Rational) UnmarshalText(b []byte) (err error) {
r.num = 0
r.den = 1
if len(b) == 0 {
return
}
items := bytes.Split(b, []byte("/"))
if r.num, err = strconv.Atoi(string(items[0])); err != nil {
err = fmt.Errorf("astikit: atoi of %s failed: %w", string(items[0]), err)
return
}
if len(items) > 1 {
if r.den, err = strconv.Atoi(string(items[1])); err != nil {
err = fmt.Errorf("astifloat: atoi of %s failed: %w", string(items[1]), err)
return
}
}
return
}

632
vendor/github.com/asticode/go-astikit/http.go generated vendored Normal file
View File

@@ -0,0 +1,632 @@
package astikit
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
var ErrHTTPSenderUnmarshaledError = errors.New("astikit: unmarshaled error")
// ServeHTTPOptions represents serve options
type ServeHTTPOptions struct {
Addr string
Handler http.Handler
}
// ServeHTTP spawns an HTTP server
func ServeHTTP(w *Worker, o ServeHTTPOptions) {
// Create server
s := &http.Server{Addr: o.Addr, Handler: o.Handler}
// Execute in a task
w.NewTask().Do(func() {
// Log
w.Logger().Infof("astikit: serving on %s", o.Addr)
// Serve
var done = make(chan error)
go func() {
if err := s.ListenAndServe(); err != nil {
done <- err
}
}()
// Wait for context or done to be done
select {
case <-w.ctx.Done():
if w.ctx.Err() != context.Canceled {
w.Logger().Error(fmt.Errorf("astikit: context error: %w", w.ctx.Err()))
}
case err := <-done:
if err != nil {
w.Logger().Error(fmt.Errorf("astikit: serving failed: %w", err))
}
}
// Shutdown
w.Logger().Infof("astikit: shutting down server on %s", o.Addr)
if err := s.Shutdown(context.Background()); err != nil {
w.Logger().Error(fmt.Errorf("astikit: shutting down server on %s failed: %w", o.Addr, err))
}
})
}
// HTTPClient represents an HTTP client
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// HTTPSender represents an object capable of sending http requests
type HTTPSender struct {
client HTTPClient
l SeverityLogger
retryFunc HTTPSenderRetryFunc
retryMax int
retrySleep time.Duration
timeout time.Duration
}
// HTTPSenderRetryFunc is a function that decides whether to retry an HTTP request
type HTTPSenderRetryFunc func(resp *http.Response) error
// HTTPSenderOptions represents HTTPSender options
type HTTPSenderOptions struct {
Client HTTPClient
Logger StdLogger
RetryFunc HTTPSenderRetryFunc
RetryMax int
RetrySleep time.Duration
Timeout time.Duration
}
// NewHTTPSender creates a new HTTP sender
func NewHTTPSender(o HTTPSenderOptions) (s *HTTPSender) {
s = &HTTPSender{
client: o.Client,
l: AdaptStdLogger(o.Logger),
retryFunc: o.RetryFunc,
retryMax: o.RetryMax,
retrySleep: o.RetrySleep,
timeout: o.Timeout,
}
if s.client == nil {
s.client = &http.Client{}
}
if s.retryFunc == nil {
s.retryFunc = s.defaultHTTPRetryFunc
}
return
}
func (s *HTTPSender) defaultHTTPRetryFunc(resp *http.Response) error {
if resp.StatusCode >= http.StatusInternalServerError {
return fmt.Errorf("astikit: invalid status code %d", resp.StatusCode)
}
return nil
}
// Send sends a new *http.Request
func (s *HTTPSender) Send(req *http.Request) (*http.Response, error) {
return s.SendWithTimeout(req, s.timeout)
}
// SendWithTimeout sends a new *http.Request with a timeout
func (s *HTTPSender) SendWithTimeout(req *http.Request, timeout time.Duration) (resp *http.Response, err error) {
// Set name
name := req.Method + " request"
if req.URL != nil {
name += " to " + req.URL.String()
}
// Timeout
if timeout > 0 {
// Create context
ctx, cancel := context.WithTimeout(req.Context(), timeout)
defer cancel()
// Update request
req = req.WithContext(ctx)
// Update name
name += " with timeout " + timeout.String()
}
// Loop
// We start at retryMax + 1 so that it runs at least once even if retryMax == 0
tries := 0
for retriesLeft := s.retryMax + 1; retriesLeft > 0; retriesLeft-- {
// Get request name
nr := name + " (" + strconv.Itoa(s.retryMax-retriesLeft+2) + "/" + strconv.Itoa(s.retryMax+1) + ")"
tries++
// Send request
s.l.Debugf("astikit: sending %s", nr)
if resp, err = s.client.Do(req); err != nil {
// Retry if error is temporary, stop here otherwise
if netError, ok := err.(net.Error); !ok || !netError.Temporary() {
err = fmt.Errorf("astikit: sending %s failed: %w", nr, err)
return
}
} else if err = req.Context().Err(); err != nil {
err = fmt.Errorf("astikit: request context failed: %w", err)
return
} else {
err = s.retryFunc(resp)
}
// Retry
if err != nil {
if retriesLeft > 1 {
s.l.Errorf("astikit: sending %s failed, sleeping %s and retrying... (%d retries left): %w", nr, s.retrySleep, retriesLeft-1, err)
time.Sleep(s.retrySleep)
}
continue
}
// Return if conditions for retrying were not met
return
}
// Max retries limit reached
err = fmt.Errorf("astikit: sending %s failed after %d tries: %w", name, tries, err)
return
}
// HTTPSendJSONOptions represents SendJSON options
type HTTPSendJSONOptions struct {
BodyError interface{}
BodyIn interface{}
BodyOut interface{}
Headers map[string]string
Method string
URL string
}
// SendJSON sends a new JSON HTTP request
func (s *HTTPSender) SendJSON(o HTTPSendJSONOptions) (err error) {
// Marshal body in
var bi io.Reader
if o.BodyIn != nil {
bb := &bytes.Buffer{}
if err = json.NewEncoder(bb).Encode(o.BodyIn); err != nil {
err = fmt.Errorf("astikit: marshaling body in failed: %w", err)
return
}
bi = bb
}
// Create request
var req *http.Request
if req, err = http.NewRequest(o.Method, o.URL, bi); err != nil {
err = fmt.Errorf("astikit: creating request failed: %w", err)
return
}
// Add headers
for k, v := range o.Headers {
req.Header.Set(k, v)
}
// Send request
var resp *http.Response
if resp, err = s.Send(req); err != nil {
err = fmt.Errorf("astikit: sending request failed: %w", err)
return
}
defer resp.Body.Close()
// Process status code
if code := resp.StatusCode; code < 200 || code > 299 {
// Try unmarshaling error
if o.BodyError != nil {
if err2 := json.NewDecoder(resp.Body).Decode(o.BodyError); err2 == nil {
err = ErrHTTPSenderUnmarshaledError
return
}
}
// Default error
err = fmt.Errorf("astikit: invalid status code %d", code)
return
}
// Unmarshal body out
if o.BodyOut != nil {
if err = json.NewDecoder(resp.Body).Decode(o.BodyOut); err != nil {
err = fmt.Errorf("astikit: unmarshaling failed: %w", err)
return
}
}
return
}
// HTTPResponseFunc is a func that can process an $http.Response
type HTTPResponseFunc func(resp *http.Response) error
func defaultHTTPResponseFunc(resp *http.Response) (err error) {
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
err = fmt.Errorf("astikit: invalid status code %d", resp.StatusCode)
return
}
return
}
// HTTPDownloader represents an object capable of downloading several HTTP srcs simultaneously
// and doing stuff to the results
type HTTPDownloader struct {
bp *BufferPool
l *GoroutineLimiter
responseFunc HTTPResponseFunc
s *HTTPSender
}
// HTTPDownloaderOptions represents HTTPDownloader options
type HTTPDownloaderOptions struct {
Limiter GoroutineLimiterOptions
ResponseFunc HTTPResponseFunc
Sender HTTPSenderOptions
}
// NewHTTPDownloader creates a new HTTPDownloader
func NewHTTPDownloader(o HTTPDownloaderOptions) (d *HTTPDownloader) {
d = &HTTPDownloader{
bp: NewBufferPool(),
l: NewGoroutineLimiter(o.Limiter),
responseFunc: o.ResponseFunc,
s: NewHTTPSender(o.Sender),
}
if d.responseFunc == nil {
d.responseFunc = defaultHTTPResponseFunc
}
return
}
// Close closes the downloader properly
func (d *HTTPDownloader) Close() error {
return d.l.Close()
}
type HTTPDownloaderSrc struct {
Body io.Reader
Header http.Header
Method string
URL string
}
// It is the responsibility of the caller to call i.Close()
type httpDownloaderFunc func(ctx context.Context, idx int, i *BufferPoolItem) error
func (d *HTTPDownloader) do(ctx context.Context, fn httpDownloaderFunc, idx int, src HTTPDownloaderSrc) (err error) {
// Defaults
if src.Method == "" {
src.Method = http.MethodGet
}
// Create request
var r *http.Request
if r, err = http.NewRequestWithContext(ctx, src.Method, src.URL, src.Body); err != nil {
err = fmt.Errorf("astikit: creating request to %s failed: %w", src.URL, err)
return
}
// Copy header
for k := range src.Header {
r.Header.Set(k, src.Header.Get(k))
}
// Send request
var resp *http.Response
if resp, err = d.s.Send(r); err != nil {
err = fmt.Errorf("astikit: sending request to %s failed: %w", src.URL, err)
return
}
defer resp.Body.Close()
// Create buffer pool item
buf := d.bp.New()
// Process response
if err = d.responseFunc(resp); err != nil {
err = fmt.Errorf("astikit: response for request to %s is invalid: %w", src.URL, err)
return
}
// Copy body
if _, err = Copy(ctx, buf, resp.Body); err != nil {
err = fmt.Errorf("astikit: copying body of %s failed: %w", src.URL, err)
return
}
// Custom
if err = fn(ctx, idx, buf); err != nil {
err = fmt.Errorf("astikit: custom callback on %s failed: %w", src.URL, err)
return
}
return
}
func (d *HTTPDownloader) download(ctx context.Context, srcs []HTTPDownloaderSrc, fn httpDownloaderFunc) (err error) {
// Nothing to download
if len(srcs) == 0 {
return nil
}
// Loop through srcs
wg := &sync.WaitGroup{}
wg.Add(len(srcs))
for idx, src := range srcs {
func(idx int, src HTTPDownloaderSrc) {
// Update error with ctx
if ctx.Err() != nil {
err = ctx.Err()
}
// Do nothing if error
if err != nil {
wg.Done()
return
}
// Do
d.l.Do(func() {
// Task is done
defer wg.Done()
// Do
if errD := d.do(ctx, fn, idx, src); errD != nil && err == nil {
err = errD
return
}
})
}(idx, src)
}
// Wait
wg.Wait()
return
}
// DownloadInDirectory downloads in parallel a set of srcs and saves them in a dst directory
func (d *HTTPDownloader) DownloadInDirectory(ctx context.Context, dst string, srcs ...HTTPDownloaderSrc) error {
return d.download(ctx, srcs, func(ctx context.Context, idx int, buf *BufferPoolItem) (err error) {
// Make sure to close buffer
defer buf.Close()
// Make sure destination directory exists
if err = os.MkdirAll(dst, DefaultDirMode); err != nil {
err = fmt.Errorf("astikit: mkdirall %s failed: %w", dst, err)
return
}
// Create destination file
var f *os.File
dst := filepath.Join(dst, filepath.Base(srcs[idx].URL))
if f, err = os.Create(dst); err != nil {
err = fmt.Errorf("astikit: creating %s failed: %w", dst, err)
return
}
defer f.Close()
// Copy buffer
if _, err = Copy(ctx, f, buf); err != nil {
err = fmt.Errorf("astikit: copying content to %s failed: %w", dst, err)
return
}
return
})
}
// DownloadInWriter downloads in parallel a set of srcs and concatenates them in a writer while
// maintaining the initial order
func (d *HTTPDownloader) DownloadInWriter(ctx context.Context, dst io.Writer, srcs ...HTTPDownloaderSrc) error {
// Init
type chunk struct {
buf *BufferPoolItem
idx int
}
var cs []chunk
var m sync.Mutex // Locks cs
var requiredIdx int
// Make sure to close all buffers
defer func() {
for _, c := range cs {
c.buf.Close()
}
}()
// Download
return d.download(ctx, srcs, func(ctx context.Context, idx int, buf *BufferPoolItem) (err error) {
// Lock
m.Lock()
defer m.Unlock()
// Check where to insert chunk
var idxInsert = -1
for idxChunk := 0; idxChunk < len(cs); idxChunk++ {
if idx < cs[idxChunk].idx {
idxInsert = idxChunk
break
}
}
// Create chunk
c := chunk{
buf: buf,
idx: idx,
}
// Add chunk
if idxInsert > -1 {
cs = append(cs[:idxInsert], append([]chunk{c}, cs[idxInsert:]...)...)
} else {
cs = append(cs, c)
}
// Loop through chunks
for idxChunk := 0; idxChunk < len(cs); idxChunk++ {
// Get chunk
c := cs[idxChunk]
// The chunk should be copied
if c.idx == requiredIdx {
// Copy chunk content
// Do not check error right away since we still want to close the buffer
// and remove the chunk
_, err = Copy(ctx, dst, c.buf)
// Close buffer
c.buf.Close()
// Remove chunk
requiredIdx++
cs = append(cs[:idxChunk], cs[idxChunk+1:]...)
idxChunk--
// Check error
if err != nil {
err = fmt.Errorf("astikit: copying chunk #%d to dst failed: %w", c.idx, err)
return
}
}
}
return
})
}
// DownloadInFile downloads in parallel a set of srcs and concatenates them in a dst file while
// maintaining the initial order
func (d *HTTPDownloader) DownloadInFile(ctx context.Context, dst string, srcs ...HTTPDownloaderSrc) (err error) {
// Make sure destination directory exists
if err = os.MkdirAll(filepath.Dir(dst), DefaultDirMode); err != nil {
err = fmt.Errorf("astikit: mkdirall %s failed: %w", filepath.Dir(dst), err)
return
}
// Create destination file
var f *os.File
if f, err = os.Create(dst); err != nil {
err = fmt.Errorf("astikit: creating %s failed: %w", dst, err)
return
}
defer f.Close()
// Download in writer
return d.DownloadInWriter(ctx, f, srcs...)
}
// HTTPMiddleware represents an HTTP middleware
type HTTPMiddleware func(http.Handler) http.Handler
// ChainHTTPMiddlewares chains HTTP middlewares
func ChainHTTPMiddlewares(h http.Handler, ms ...HTTPMiddleware) http.Handler {
return ChainHTTPMiddlewaresWithPrefix(h, []string{}, ms...)
}
// ChainHTTPMiddlewaresWithPrefix chains HTTP middlewares if one of prefixes is present
func ChainHTTPMiddlewaresWithPrefix(h http.Handler, prefixes []string, ms ...HTTPMiddleware) http.Handler {
for _, m := range ms {
if m == nil {
continue
}
if len(prefixes) == 0 {
h = m(h)
} else {
t := h
h = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
for _, prefix := range prefixes {
if strings.HasPrefix(r.URL.EscapedPath(), prefix) {
m(t).ServeHTTP(rw, r)
return
}
}
t.ServeHTTP(rw, r)
})
}
}
return h
}
func handleHTTPBasicAuth(username, password string, rw http.ResponseWriter, r *http.Request) bool {
if u, p, ok := r.BasicAuth(); !ok || u != username || p != password {
rw.Header().Set("WWW-Authenticate", "Basic Realm=Please enter your credentials")
rw.WriteHeader(http.StatusUnauthorized)
return true
}
return false
}
// HTTPMiddlewareBasicAuth adds basic HTTP auth to an HTTP handler
func HTTPMiddlewareBasicAuth(username, password string) HTTPMiddleware {
if username == "" && password == "" {
return nil
}
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Handle basic auth
if handleHTTPBasicAuth(username, password, rw, r) {
return
}
// Next handler
h.ServeHTTP(rw, r)
})
}
}
func setHTTPContentType(contentType string, rw http.ResponseWriter) {
rw.Header().Set("Content-Type", contentType)
}
// HTTPMiddlewareContentType adds a content type to an HTTP handler
func HTTPMiddlewareContentType(contentType string) HTTPMiddleware {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Set content type
setHTTPContentType(contentType, rw)
// Next handler
h.ServeHTTP(rw, r)
})
}
}
func setHTTPHeaders(vs map[string]string, rw http.ResponseWriter) {
for k, v := range vs {
rw.Header().Set(k, v)
}
}
// HTTPMiddlewareHeaders adds headers to an HTTP handler
func HTTPMiddlewareHeaders(vs map[string]string) HTTPMiddleware {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Set headers
setHTTPHeaders(vs, rw)
// Next handler
h.ServeHTTP(rw, r)
})
}
}
// HTTPMiddlewareCORSHeaders adds CORS headers to an HTTP handler
func HTTPMiddlewareCORSHeaders() HTTPMiddleware {
return HTTPMiddlewareHeaders(map[string]string{
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Origin": "*",
})
}

121
vendor/github.com/asticode/go-astikit/io.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
package astikit
import (
"bytes"
"context"
"io"
)
// Copy is a copy with a context
func Copy(ctx context.Context, dst io.Writer, src io.Reader) (int64, error) {
return io.Copy(dst, NewCtxReader(ctx, src))
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
// NopCloser returns a WriteCloser with a no-op Close method wrapping
// the provided Writer w.
func NopCloser(w io.Writer) io.WriteCloser {
return nopCloser{w}
}
// CtxReader represents a reader with a context
type CtxReader struct {
ctx context.Context
reader io.Reader
}
// NewCtxReader creates a reader with a context
func NewCtxReader(ctx context.Context, r io.Reader) *CtxReader {
return &CtxReader{
ctx: ctx,
reader: r,
}
}
// Read implements the io.Reader interface
func (r *CtxReader) Read(p []byte) (n int, err error) {
// Check context
if err = r.ctx.Err(); err != nil {
return
}
// Read
return r.reader.Read(p)
}
// WriterAdapter represents an object that can adapt a Writer
type WriterAdapter struct {
buffer *bytes.Buffer
o WriterAdapterOptions
}
// WriterAdapterOptions represents WriterAdapter options
type WriterAdapterOptions struct {
Callback func(i []byte)
Split []byte
}
// NewWriterAdapter creates a new WriterAdapter
func NewWriterAdapter(o WriterAdapterOptions) *WriterAdapter {
return &WriterAdapter{
buffer: &bytes.Buffer{},
o: o,
}
}
// Close closes the adapter properly
func (w *WriterAdapter) Close() error {
if w.buffer.Len() > 0 {
w.write(w.buffer.Bytes())
}
return nil
}
// Write implements the io.Writer interface
func (w *WriterAdapter) Write(i []byte) (n int, err error) {
// Update n to avoid broken pipe error
defer func() {
n = len(i)
}()
// Split
if len(w.o.Split) > 0 {
// Split bytes are not present, write in buffer
if !bytes.Contains(i, w.o.Split) {
w.buffer.Write(i)
return
}
// Loop in split items
items := bytes.Split(i, w.o.Split)
for i := 0; i < len(items)-1; i++ {
// If this is the first item, prepend the buffer
if i == 0 {
items[i] = append(w.buffer.Bytes(), items[i]...)
w.buffer.Reset()
}
// Write
w.write(items[i])
}
// Add remaining to buffer
w.buffer.Write(items[len(items)-1])
return
}
// By default, forward the bytes
w.write(i)
return
}
func (w *WriterAdapter) write(i []byte) {
if w.o.Callback != nil {
w.o.Callback(i)
}
}

101
vendor/github.com/asticode/go-astikit/limiter.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package astikit
import (
"context"
"sync"
"time"
)
// Limiter represents a limiter
type Limiter struct {
buckets map[string]*LimiterBucket
m *sync.Mutex // Locks buckets
}
// NewLimiter creates a new limiter
func NewLimiter() *Limiter {
return &Limiter{
buckets: make(map[string]*LimiterBucket),
m: &sync.Mutex{},
}
}
// Add adds a new bucket
func (l *Limiter) Add(name string, cap int, period time.Duration) *LimiterBucket {
l.m.Lock()
defer l.m.Unlock()
if _, ok := l.buckets[name]; !ok {
l.buckets[name] = newLimiterBucket(cap, period)
}
return l.buckets[name]
}
// Bucket retrieves a bucket from the limiter
func (l *Limiter) Bucket(name string) (b *LimiterBucket, ok bool) {
l.m.Lock()
defer l.m.Unlock()
b, ok = l.buckets[name]
return
}
// Close closes the limiter properly
func (l *Limiter) Close() {
l.m.Lock()
defer l.m.Unlock()
for _, b := range l.buckets {
b.Close()
}
}
// LimiterBucket represents a limiter bucket
type LimiterBucket struct {
cancel context.CancelFunc
cap int
ctx context.Context
count int
period time.Duration
o *sync.Once
}
// newLimiterBucket creates a new bucket
func newLimiterBucket(cap int, period time.Duration) (b *LimiterBucket) {
b = &LimiterBucket{
cap: cap,
count: 0,
period: period,
o: &sync.Once{},
}
b.ctx, b.cancel = context.WithCancel(context.Background())
go b.tick()
return
}
// Inc increments the bucket count
func (b *LimiterBucket) Inc() bool {
if b.count >= b.cap {
return false
}
b.count++
return true
}
// tick runs a ticker to purge the bucket
func (b *LimiterBucket) tick() {
var t = time.NewTicker(b.period)
defer t.Stop()
for {
select {
case <-t.C:
b.count = 0
case <-b.ctx.Done():
return
}
}
}
// close closes the bucket properly
func (b *LimiterBucket) Close() {
b.o.Do(func() {
b.cancel()
})
}

171
vendor/github.com/asticode/go-astikit/logger.go generated vendored Normal file
View File

@@ -0,0 +1,171 @@
package astikit
import (
"context"
)
// CompleteLogger represents a complete logger
type CompleteLogger interface {
StdLogger
SeverityLogger
SeverityCtxLogger
}
// StdLogger represents a standard logger
type StdLogger interface {
Fatal(v ...interface{})
Fatalf(format string, v ...interface{})
Print(v ...interface{})
Printf(format string, v ...interface{})
}
// SeverityLogger represents a severity logger
type SeverityLogger interface {
Debug(v ...interface{})
Debugf(format string, v ...interface{})
Error(v ...interface{})
Errorf(format string, v ...interface{})
Info(v ...interface{})
Infof(format string, v ...interface{})
Warn(v ...interface{})
Warnf(format string, v ...interface{})
}
// SeverityCtxLogger represents a severity with context logger
type SeverityCtxLogger interface {
DebugC(ctx context.Context, v ...interface{})
DebugCf(ctx context.Context, format string, v ...interface{})
ErrorC(ctx context.Context, v ...interface{})
ErrorCf(ctx context.Context, format string, v ...interface{})
FatalC(ctx context.Context, v ...interface{})
FatalCf(ctx context.Context, format string, v ...interface{})
InfoC(ctx context.Context, v ...interface{})
InfoCf(ctx context.Context, format string, v ...interface{})
WarnC(ctx context.Context, v ...interface{})
WarnCf(ctx context.Context, format string, v ...interface{})
}
type completeLogger struct {
print, debug, error, fatal, info, warn func(v ...interface{})
printf, debugf, errorf, fatalf, infof, warnf func(format string, v ...interface{})
debugC, errorC, fatalC, infoC, warnC func(ctx context.Context, v ...interface{})
debugCf, errorCf, fatalCf, infoCf, warnCf func(ctx context.Context, format string, v ...interface{})
}
func newCompleteLogger() *completeLogger {
return &completeLogger{
debug: func(v ...interface{}) {},
debugf: func(format string, v ...interface{}) {},
debugC: func(ctx context.Context, v ...interface{}) {},
debugCf: func(ctx context.Context, format string, v ...interface{}) {},
error: func(v ...interface{}) {},
errorf: func(format string, v ...interface{}) {},
errorC: func(ctx context.Context, v ...interface{}) {},
errorCf: func(ctx context.Context, format string, v ...interface{}) {},
fatal: func(v ...interface{}) {},
fatalf: func(format string, v ...interface{}) {},
fatalC: func(ctx context.Context, v ...interface{}) {},
fatalCf: func(ctx context.Context, format string, v ...interface{}) {},
info: func(v ...interface{}) {},
infof: func(format string, v ...interface{}) {},
infoC: func(ctx context.Context, v ...interface{}) {},
infoCf: func(ctx context.Context, format string, v ...interface{}) {},
print: func(v ...interface{}) {},
printf: func(format string, v ...interface{}) {},
warn: func(v ...interface{}) {},
warnf: func(format string, v ...interface{}) {},
warnC: func(ctx context.Context, v ...interface{}) {},
warnCf: func(ctx context.Context, format string, v ...interface{}) {},
}
}
func (l *completeLogger) Debug(v ...interface{}) { l.debug(v...) }
func (l *completeLogger) Debugf(format string, v ...interface{}) { l.debugf(format, v...) }
func (l *completeLogger) DebugC(ctx context.Context, v ...interface{}) { l.debugC(ctx, v...) }
func (l *completeLogger) DebugCf(ctx context.Context, format string, v ...interface{}) {
l.debugCf(ctx, format, v...)
}
func (l *completeLogger) Error(v ...interface{}) { l.error(v...) }
func (l *completeLogger) Errorf(format string, v ...interface{}) { l.errorf(format, v...) }
func (l *completeLogger) ErrorC(ctx context.Context, v ...interface{}) { l.errorC(ctx, v...) }
func (l *completeLogger) ErrorCf(ctx context.Context, format string, v ...interface{}) {
l.errorCf(ctx, format, v...)
}
func (l *completeLogger) Fatal(v ...interface{}) { l.fatal(v...) }
func (l *completeLogger) Fatalf(format string, v ...interface{}) { l.fatalf(format, v...) }
func (l *completeLogger) FatalC(ctx context.Context, v ...interface{}) { l.fatalC(ctx, v...) }
func (l *completeLogger) FatalCf(ctx context.Context, format string, v ...interface{}) {
l.fatalCf(ctx, format, v...)
}
func (l *completeLogger) Info(v ...interface{}) { l.info(v...) }
func (l *completeLogger) Infof(format string, v ...interface{}) { l.infof(format, v...) }
func (l *completeLogger) InfoC(ctx context.Context, v ...interface{}) { l.infoC(ctx, v...) }
func (l *completeLogger) InfoCf(ctx context.Context, format string, v ...interface{}) {
l.infoCf(ctx, format, v...)
}
func (l *completeLogger) Print(v ...interface{}) { l.print(v...) }
func (l *completeLogger) Printf(format string, v ...interface{}) { l.printf(format, v...) }
func (l *completeLogger) Warn(v ...interface{}) { l.warn(v...) }
func (l *completeLogger) Warnf(format string, v ...interface{}) { l.warnf(format, v...) }
func (l *completeLogger) WarnC(ctx context.Context, v ...interface{}) { l.warnC(ctx, v...) }
func (l *completeLogger) WarnCf(ctx context.Context, format string, v ...interface{}) {
l.warnCf(ctx, format, v...)
}
// AdaptStdLogger transforms an StdLogger into a CompleteLogger if needed
func AdaptStdLogger(i StdLogger) CompleteLogger {
if v, ok := i.(CompleteLogger); ok {
return v
}
l := newCompleteLogger()
if i == nil {
return l
}
l.fatal = i.Fatal
l.fatalf = i.Fatalf
l.print = i.Print
l.printf = i.Printf
if v, ok := i.(SeverityLogger); ok {
l.debug = v.Debug
l.debugf = v.Debugf
l.error = v.Error
l.errorf = v.Errorf
l.info = v.Info
l.infof = v.Infof
l.warn = v.Warn
l.warnf = v.Warnf
} else {
l.debug = l.print
l.debugf = l.printf
l.error = l.print
l.errorf = l.printf
l.info = l.print
l.infof = l.printf
l.warn = l.print
l.warnf = l.printf
}
if v, ok := i.(SeverityCtxLogger); ok {
l.debugC = v.DebugC
l.debugCf = v.DebugCf
l.errorC = v.ErrorC
l.errorCf = v.ErrorCf
l.fatalC = v.FatalC
l.fatalCf = v.FatalCf
l.infoC = v.InfoC
l.infoCf = v.InfoCf
l.warnC = v.WarnC
l.warnCf = v.WarnCf
} else {
l.debugC = func(ctx context.Context, v ...interface{}) { l.debug(v...) }
l.debugCf = func(ctx context.Context, format string, v ...interface{}) { l.debugf(format, v...) }
l.errorC = func(ctx context.Context, v ...interface{}) { l.error(v...) }
l.errorCf = func(ctx context.Context, format string, v ...interface{}) { l.errorf(format, v...) }
l.fatalC = func(ctx context.Context, v ...interface{}) { l.fatal(v...) }
l.fatalCf = func(ctx context.Context, format string, v ...interface{}) { l.fatalf(format, v...) }
l.infoC = func(ctx context.Context, v ...interface{}) { l.info(v...) }
l.infoCf = func(ctx context.Context, format string, v ...interface{}) { l.infof(format, v...) }
l.warnC = func(ctx context.Context, v ...interface{}) { l.warn(v...) }
l.warnCf = func(ctx context.Context, format string, v ...interface{}) { l.warnf(format, v...) }
}
return l
}

67
vendor/github.com/asticode/go-astikit/map.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
package astikit
import (
"fmt"
"sync"
)
// BiMap represents a bidirectional map
type BiMap struct {
forward map[interface{}]interface{}
inverse map[interface{}]interface{}
m *sync.Mutex
}
// NewBiMap creates a new BiMap
func NewBiMap() *BiMap {
return &BiMap{
forward: make(map[interface{}]interface{}),
inverse: make(map[interface{}]interface{}),
m: &sync.Mutex{},
}
}
func (m *BiMap) get(k interface{}, i map[interface{}]interface{}) (v interface{}, ok bool) {
m.m.Lock()
defer m.m.Unlock()
v, ok = i[k]
return
}
// Get gets the value in the forward map based on the provided key
func (m *BiMap) Get(k interface{}) (interface{}, bool) { return m.get(k, m.forward) }
// GetInverse gets the value in the inverse map based on the provided key
func (m *BiMap) GetInverse(k interface{}) (interface{}, bool) { return m.get(k, m.inverse) }
// MustGet gets the value in the forward map based on the provided key and panics if key is not found
func (m *BiMap) MustGet(k interface{}) interface{} {
v, ok := m.get(k, m.forward)
if !ok {
panic(fmt.Sprintf("astikit: key %+v not found in foward map", k))
}
return v
}
// MustGetInverse gets the value in the inverse map based on the provided key and panics if key is not found
func (m *BiMap) MustGetInverse(k interface{}) interface{} {
v, ok := m.get(k, m.inverse)
if !ok {
panic(fmt.Sprintf("astikit: key %+v not found in inverse map", k))
}
return v
}
func (m *BiMap) set(k, v interface{}, f, i map[interface{}]interface{}) *BiMap {
m.m.Lock()
defer m.m.Unlock()
f[k] = v
i[v] = k
return m
}
// Set sets the value in the forward and inverse map for the provided forward key
func (m *BiMap) Set(k, v interface{}) *BiMap { return m.set(k, v, m.forward, m.inverse) }
// SetInverse sets the value in the forward and inverse map for the provided inverse key
func (m *BiMap) SetInverse(k, v interface{}) *BiMap { return m.set(k, v, m.inverse, m.forward) }

148
vendor/github.com/asticode/go-astikit/os.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
package astikit
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
// MoveFile is a cancellable move of a local file to a local or remote location
func MoveFile(ctx context.Context, dst, src string, f CopyFileFunc) (err error) {
// Copy
if err = CopyFile(ctx, dst, src, f); err != nil {
err = fmt.Errorf("astikit: copying file %s to %s failed: %w", src, dst, err)
return
}
// Delete
if err = os.Remove(src); err != nil {
err = fmt.Errorf("astikit: removing %s failed: %w", src, err)
return
}
return
}
// CopyFileFunc represents a CopyFile func
type CopyFileFunc func(ctx context.Context, dst string, srcStat os.FileInfo, srcFile *os.File) error
// CopyFile is a cancellable copy of a local file to a local or remote location
func CopyFile(ctx context.Context, dst, src string, f CopyFileFunc) (err error) {
// Check context
if err = ctx.Err(); err != nil {
return
}
// Stat src
var srcStat os.FileInfo
if srcStat, err = os.Stat(src); err != nil {
err = fmt.Errorf("astikit: stating %s failed: %w", src, err)
return
}
// Src is a dir
if srcStat.IsDir() {
// Walk through the dir
if err = filepath.Walk(src, func(path string, info os.FileInfo, errWalk error) (err error) {
// Check error
if errWalk != nil {
err = errWalk
return
}
// Do not process root
if src == path {
return
}
// Copy
p := filepath.Join(dst, strings.TrimPrefix(path, filepath.Clean(src)))
if err = CopyFile(ctx, p, path, f); err != nil {
err = fmt.Errorf("astikit: copying %s to %s failed: %w", path, p, err)
return
}
return nil
}); err != nil {
err = fmt.Errorf("astikit: walking through %s failed: %w", src, err)
return
}
return
}
// Open src
var srcFile *os.File
if srcFile, err = os.Open(src); err != nil {
err = fmt.Errorf("astikit: opening %s failed: %w", src, err)
return
}
defer srcFile.Close()
// Custom
if err = f(ctx, dst, srcStat, srcFile); err != nil {
err = fmt.Errorf("astikit: custom failed: %w", err)
return
}
return
}
// LocalCopyFileFunc is the local CopyFileFunc that allows doing cross partition copies
func LocalCopyFileFunc(ctx context.Context, dst string, srcStat os.FileInfo, srcFile *os.File) (err error) {
// Check context
if err = ctx.Err(); err != nil {
return
}
// Create the destination folder
if err = os.MkdirAll(filepath.Dir(dst), DefaultDirMode); err != nil {
err = fmt.Errorf("astikit: mkdirall %s failed: %w", filepath.Dir(dst), err)
return
}
// Create the destination file
var dstFile *os.File
if dstFile, err = os.Create(dst); err != nil {
err = fmt.Errorf("astikit: creating %s failed: %w", dst, err)
return
}
defer dstFile.Close()
// Chmod using os.chmod instead of file.Chmod
if err = os.Chmod(dst, srcStat.Mode()); err != nil {
err = fmt.Errorf("astikit: chmod %s %s failed, %w", dst, srcStat.Mode(), err)
return
}
// Copy the content
if _, err = Copy(ctx, dstFile, srcFile); err != nil {
err = fmt.Errorf("astikit: copying content of %s to %s failed: %w", srcFile.Name(), dstFile.Name(), err)
return
}
return
}
// SignalHandler represents a func that can handle a signal
type SignalHandler func(s os.Signal)
// TermSignalHandler returns a SignalHandler that is executed only on a term signal
func TermSignalHandler(f func()) SignalHandler {
return func(s os.Signal) {
if isTermSignal(s) {
f()
}
}
}
// LoggerSignalHandler returns a SignalHandler that logs the signal
func LoggerSignalHandler(l SeverityLogger, ignoredSignals ...os.Signal) SignalHandler {
ss := make(map[os.Signal]bool)
for _, s := range ignoredSignals {
ss[s] = true
}
return func(s os.Signal) {
if _, ok := ss[s]; ok {
return
}
l.Debugf("astikit: received signal %s", s)
}
}

12
vendor/github.com/asticode/go-astikit/os_js.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build js,wasm
package astikit
import (
"os"
"syscall"
)
func isTermSignal(s os.Signal) bool {
return s == syscall.SIGKILL || s == syscall.SIGINT || s == syscall.SIGQUIT || s == syscall.SIGTERM
}

12
vendor/github.com/asticode/go-astikit/os_others.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build !js !wasm
package astikit
import (
"os"
"syscall"
)
func isTermSignal(s os.Signal) bool {
return s == syscall.SIGABRT || s == syscall.SIGKILL || s == syscall.SIGINT || s == syscall.SIGQUIT || s == syscall.SIGTERM
}

426
vendor/github.com/asticode/go-astikit/pcm.go generated vendored Normal file
View File

@@ -0,0 +1,426 @@
package astikit
import (
"fmt"
"math"
"sync"
"time"
)
// PCMLevel computes the PCM level of samples
// https://dsp.stackexchange.com/questions/2951/loudness-of-pcm-stream
// https://dsp.stackexchange.com/questions/290/getting-loudness-of-a-track-with-rms?noredirect=1&lq=1
func PCMLevel(samples []int) float64 {
// Compute sum of square values
var sum float64
for _, s := range samples {
sum += math.Pow(float64(s), 2)
}
// Square root
return math.Sqrt(sum / float64(len(samples)))
}
func maxPCMSample(bitDepth int) int {
return int(math.Pow(2, float64(bitDepth))/2.0) - 1
}
// PCMNormalize normalizes the PCM samples
func PCMNormalize(samples []int, bitDepth int) (o []int) {
// Get max sample
var m int
for _, s := range samples {
if v := int(math.Abs(float64(s))); v > m {
m = v
}
}
// Get max for bit depth
max := maxPCMSample(bitDepth)
// Loop through samples
for _, s := range samples {
o = append(o, s*max/m)
}
return
}
// ConvertPCMBitDepth converts the PCM bit depth
func ConvertPCMBitDepth(srcSample int, srcBitDepth, dstBitDepth int) (dstSample int, err error) {
// Nothing to do
if srcBitDepth == dstBitDepth {
dstSample = srcSample
return
}
// Convert
if srcBitDepth < dstBitDepth {
dstSample = srcSample << uint(dstBitDepth-srcBitDepth)
} else {
dstSample = srcSample >> uint(srcBitDepth-dstBitDepth)
}
return
}
// PCMSampleFunc is a func that can process a sample
type PCMSampleFunc func(s int) error
// PCMSampleRateConverter is an object capable of converting a PCM's sample rate
type PCMSampleRateConverter struct {
b [][]int
dstSampleRate int
fn PCMSampleFunc
numChannels int
numChannelsProcessed int
numSamplesOutputed int
numSamplesProcessed int
srcSampleRate int
}
// NewPCMSampleRateConverter creates a new PCMSampleRateConverter
func NewPCMSampleRateConverter(srcSampleRate, dstSampleRate, numChannels int, fn PCMSampleFunc) *PCMSampleRateConverter {
return &PCMSampleRateConverter{
b: make([][]int, numChannels),
dstSampleRate: dstSampleRate,
fn: fn,
numChannels: numChannels,
srcSampleRate: srcSampleRate,
}
}
// Reset resets the converter
func (c *PCMSampleRateConverter) Reset() {
c.b = make([][]int, c.numChannels)
c.numChannelsProcessed = 0
c.numSamplesOutputed = 0
c.numSamplesProcessed = 0
}
// Add adds a new sample to the converter
func (c *PCMSampleRateConverter) Add(i int) (err error) {
// Forward sample
if c.srcSampleRate == c.dstSampleRate {
if err = c.fn(i); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
return
}
// Increment num channels processed
c.numChannelsProcessed++
// Reset num channels processed
if c.numChannelsProcessed > c.numChannels {
c.numChannelsProcessed = 1
}
// Only increment num samples processed if all channels have been processed
if c.numChannelsProcessed == c.numChannels {
c.numSamplesProcessed++
}
// Append sample to buffer
c.b[c.numChannelsProcessed-1] = append(c.b[c.numChannelsProcessed-1], i)
// Throw away data
if c.srcSampleRate > c.dstSampleRate {
// Make sure to always keep the first sample but do nothing until we have all channels or target sample has been
// reached
if (c.numSamplesOutputed > 0 && float64(c.numSamplesProcessed) < 1.0+float64(c.numSamplesOutputed)*float64(c.srcSampleRate)/float64(c.dstSampleRate)) || c.numChannelsProcessed < c.numChannels {
return
}
// Loop through channels
for idx, b := range c.b {
// Merge samples
var s int
for _, v := range b {
s += v
}
s /= len(b)
// Reset buffer
c.b[idx] = []int{}
// Custom
if err = c.fn(s); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
}
// Increment num samples outputted
c.numSamplesOutputed++
return
}
// Do nothing until we have all channels
if c.numChannelsProcessed < c.numChannels {
return
}
// Repeat data
for c.numSamplesOutputed == 0 || float64(c.numSamplesProcessed)+1.0 > 1.0+float64(c.numSamplesOutputed)*float64(c.srcSampleRate)/float64(c.dstSampleRate) {
// Loop through channels
for _, b := range c.b {
// Invalid length
if len(b) != 1 {
err = fmt.Errorf("astikit: invalid buffer item length %d", len(b))
return
}
// Custom
if err = c.fn(b[0]); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
}
// Increment num samples outputted
c.numSamplesOutputed++
}
// Reset buffer
c.b = make([][]int, c.numChannels)
return
}
// PCMChannelsConverter is an object of converting PCM's channels
type PCMChannelsConverter struct {
dstNumChannels int
fn PCMSampleFunc
srcNumChannels int
srcSamples int
}
// NewPCMChannelsConverter creates a new PCMChannelsConverter
func NewPCMChannelsConverter(srcNumChannels, dstNumChannels int, fn PCMSampleFunc) *PCMChannelsConverter {
return &PCMChannelsConverter{
dstNumChannels: dstNumChannels,
fn: fn,
srcNumChannels: srcNumChannels,
}
}
// Reset resets the converter
func (c *PCMChannelsConverter) Reset() {
c.srcSamples = 0
}
// Add adds a new sample to the converter
func (c *PCMChannelsConverter) Add(i int) (err error) {
// Forward sample
if c.srcNumChannels == c.dstNumChannels {
if err = c.fn(i); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
return
}
// Reset
if c.srcSamples == c.srcNumChannels {
c.srcSamples = 0
}
// Increment src samples
c.srcSamples++
// Throw away data
if c.srcNumChannels > c.dstNumChannels {
// Throw away sample
if c.srcSamples > c.dstNumChannels {
return
}
// Custom
if err = c.fn(i); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
return
}
// Store
var ss []int
if c.srcSamples < c.srcNumChannels {
ss = []int{i}
} else {
// Repeat data
for idx := c.srcNumChannels; idx <= c.dstNumChannels; idx++ {
ss = append(ss, i)
}
}
// Loop through samples
for _, s := range ss {
// Custom
if err = c.fn(s); err != nil {
err = fmt.Errorf("astikit: handling sample failed: %w", err)
return
}
}
return
}
// PCMSilenceDetector represents a PCM silence detector
type PCMSilenceDetector struct {
analyses []pcmSilenceDetectorAnalysis
buf []int
m *sync.Mutex // Locks buf
minAnalysesPerSilence int
o PCMSilenceDetectorOptions
samplesPerAnalysis int
}
type pcmSilenceDetectorAnalysis struct {
level float64
samples []int
}
// PCMSilenceDetectorOptions represents a PCM silence detector options
type PCMSilenceDetectorOptions struct {
MaxSilenceLevel float64 `toml:"max_silence_level"`
MinSilenceDuration time.Duration `toml:"min_silence_duration"`
SampleRate int `toml:"sample_rate"`
StepDuration time.Duration `toml:"step_duration"`
}
// NewPCMSilenceDetector creates a new silence detector
func NewPCMSilenceDetector(o PCMSilenceDetectorOptions) (d *PCMSilenceDetector) {
// Create
d = &PCMSilenceDetector{
m: &sync.Mutex{},
o: o,
}
// Reset
d.Reset()
// Default option values
if d.o.MinSilenceDuration == 0 {
d.o.MinSilenceDuration = time.Second
}
if d.o.StepDuration == 0 {
d.o.StepDuration = 30 * time.Millisecond
}
// Compute attributes depending on options
d.samplesPerAnalysis = int(math.Floor(float64(d.o.SampleRate) * d.o.StepDuration.Seconds()))
d.minAnalysesPerSilence = int(math.Floor(d.o.MinSilenceDuration.Seconds() / d.o.StepDuration.Seconds()))
return
}
// Reset resets the silence detector
func (d *PCMSilenceDetector) Reset() {
// Lock
d.m.Lock()
defer d.m.Unlock()
// Reset
d.analyses = []pcmSilenceDetectorAnalysis{}
d.buf = []int{}
}
// Add adds samples to the buffer and checks whether there are valid samples between silences
func (d *PCMSilenceDetector) Add(samples []int) (validSamples [][]int) {
// Lock
d.m.Lock()
defer d.m.Unlock()
// Append samples to buffer
d.buf = append(d.buf, samples...)
// Analyze samples by step
for len(d.buf) >= d.samplesPerAnalysis {
// Append analysis
d.analyses = append(d.analyses, pcmSilenceDetectorAnalysis{
level: PCMLevel(d.buf[:d.samplesPerAnalysis]),
samples: append([]int(nil), d.buf[:d.samplesPerAnalysis]...),
})
// Remove samples from buffer
d.buf = d.buf[d.samplesPerAnalysis:]
}
// Loop through analyses
var leadingSilence, inBetween, trailingSilence int
for i := 0; i < len(d.analyses); i++ {
if d.analyses[i].level < d.o.MaxSilenceLevel {
// This is a silence
// This is a leading silence
if inBetween == 0 {
leadingSilence++
// The leading silence is valid
// We can trim its useless part
if leadingSilence > d.minAnalysesPerSilence {
d.analyses = d.analyses[leadingSilence-d.minAnalysesPerSilence:]
i -= leadingSilence - d.minAnalysesPerSilence
leadingSilence = d.minAnalysesPerSilence
}
continue
}
// This is a trailing silence
trailingSilence++
// Trailing silence is invalid
if trailingSilence < d.minAnalysesPerSilence {
continue
}
// Trailing silence is valid
// Loop through analyses
var ss []int
for _, a := range d.analyses[:i+1] {
ss = append(ss, a.samples...)
}
// Append valid samples
validSamples = append(validSamples, ss)
// Remove leading silence and non silence
d.analyses = d.analyses[leadingSilence+inBetween:]
i -= leadingSilence + inBetween
// Reset counts
leadingSilence, inBetween, trailingSilence = trailingSilence, 0, 0
} else {
// This is not a silence
// This is a leading non silence
// We need to remove it
if i == 0 {
d.analyses = d.analyses[1:]
i = -1
continue
}
// This is the first in-between
if inBetween == 0 {
// The leading silence is invalid
// We need to remove it as well as this first non silence
if leadingSilence < d.minAnalysesPerSilence {
d.analyses = d.analyses[i+1:]
i = -1
continue
}
}
// This non-silence was preceded by a silence not big enough to be a valid trailing silence
// We incorporate it in the in-between
if trailingSilence > 0 {
inBetween += trailingSilence
trailingSilence = 0
}
// This is an in-between
inBetween++
continue
}
}
return
}

58
vendor/github.com/asticode/go-astikit/ptr.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package astikit
import "time"
// BoolPtr transforms a bool into a *bool
func BoolPtr(i bool) *bool {
return &i
}
// BytePtr transforms a byte into a *byte
func BytePtr(i byte) *byte {
return &i
}
// DurationPtr transforms a time.Duration into a *time.Duration
func DurationPtr(i time.Duration) *time.Duration {
return &i
}
// Float64Ptr transforms a float64 into a *float64
func Float64Ptr(i float64) *float64 {
return &i
}
// IntPtr transforms an int into an *int
func IntPtr(i int) *int {
return &i
}
// Int64Ptr transforms an int64 into an *int64
func Int64Ptr(i int64) *int64 {
return &i
}
// StrSlicePtr transforms a []string into a *[]string
func StrSlicePtr(i []string) *[]string {
return &i
}
// StrPtr transforms a string into a *string
func StrPtr(i string) *string {
return &i
}
// TimePtr transforms a time.Time into a *time.Time
func TimePtr(i time.Time) *time.Time {
return &i
}
// UInt8Ptr transforms a uint8 into a *uint8
func UInt8Ptr(i uint8) *uint8 {
return &i
}
// UInt32Ptr transforms a uint32 into a *uint32
func UInt32Ptr(i uint32) *uint32 {
return &i
}

36
vendor/github.com/asticode/go-astikit/rand.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package astikit
import (
"math/rand"
"strings"
"time"
)
const (
randLetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
randLetterIdxBits = 6 // 6 bits to represent a letter index
randLetterIdxMask = 1<<randLetterIdxBits - 1 // All 1-bits, as many as letterIdxBits
randLetterIdxMax = 63 / randLetterIdxBits // # of letter indices fitting in 63 bits
)
var randSrc = rand.NewSource(time.Now().UnixNano())
// RandStr generates a random string of length n
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
func RandStr(n int) string {
sb := strings.Builder{}
sb.Grow(n)
// A randSrc.Int63() generates 63 random bits, enough for randLetterIdxMax characters!
for i, cache, remain := n-1, randSrc.Int63(), randLetterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = randSrc.Int63(), randLetterIdxMax
}
if idx := int(cache & randLetterIdxMask); idx < len(randLetterBytes) {
sb.WriteByte(randLetterBytes[idx])
i--
}
cache >>= randLetterIdxBits
remain--
}
return sb.String()
}

13
vendor/github.com/asticode/go-astikit/sort.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package astikit
import "sort"
// SortInt64 sorts a slice of int64s in increasing order.
func SortInt64(a []int64) { sort.Sort(SortInt64Slice(a)) }
// SortInt64Slice attaches the methods of Interface to []int64, sorting in increasing order.
type SortInt64Slice []int64
func (p SortInt64Slice) Len() int { return len(p) }
func (p SortInt64Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p SortInt64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

113
vendor/github.com/asticode/go-astikit/ssh.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
package astikit
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
)
// SSHSession represents an SSH Session
type SSHSession interface {
Run(string) error
Start(string) error
StdinPipe() (io.WriteCloser, error)
Wait() error
}
// SSHSessionFunc represents a func that can return an SSHSession
type SSHSessionFunc func() (s SSHSession, c *Closer, err error)
// SSHCopyFileFunc is the SSH CopyFileFunc that allows doing SSH copies
func SSHCopyFileFunc(fn SSHSessionFunc) CopyFileFunc {
return func(ctx context.Context, dst string, srcStat os.FileInfo, srcFile *os.File) (err error) {
// Check context
if err = ctx.Err(); err != nil {
return
}
// Using local closure allows better readibility for the defer c.Close() since it
// isolates the use of the ssh session
if err = func() (err error) {
// Create ssh session
var s SSHSession
var c *Closer
if s, c, err = fn(); err != nil {
err = fmt.Errorf("astikit: creating ssh session failed: %w", err)
return
}
defer c.Close()
// Create the destination folder
if err = s.Run("mkdir -p " + filepath.Dir(dst)); err != nil {
err = fmt.Errorf("astikit: creating %s failed: %w", filepath.Dir(dst), err)
return
}
return
}(); err != nil {
return
}
// Using local closure allows better readibility for the defer c.Close() since it
// isolates the use of the ssh session
if err = func() (err error) {
// Create ssh session
var s SSHSession
var c *Closer
if s, c, err = fn(); err != nil {
err = fmt.Errorf("astikit: creating ssh session failed: %w", err)
return
}
defer c.Close()
// Create stdin pipe
var stdin io.WriteCloser
if stdin, err = s.StdinPipe(); err != nil {
err = fmt.Errorf("astikit: creating stdin pipe failed: %w", err)
return
}
defer stdin.Close()
// Use "scp" command
if err = s.Start("scp -qt \"" + filepath.Dir(dst) + "\""); err != nil {
err = fmt.Errorf("astikit: scp to %s failed: %w", dst, err)
return
}
// Send metadata
if _, err = fmt.Fprintln(stdin, fmt.Sprintf("C%04o", srcStat.Mode().Perm()), srcStat.Size(), filepath.Base(dst)); err != nil {
err = fmt.Errorf("astikit: sending metadata failed: %w", err)
return
}
// Copy
if _, err = Copy(ctx, stdin, srcFile); err != nil {
err = fmt.Errorf("astikit: copying failed: %w", err)
return
}
// Send close
if _, err = fmt.Fprint(stdin, "\x00"); err != nil {
err = fmt.Errorf("astikit: sending close failed: %w", err)
return
}
// Close stdin
if err = stdin.Close(); err != nil {
err = fmt.Errorf("astikit: closing failed: %w", err)
return
}
// Wait
if err = s.Wait(); err != nil {
err = fmt.Errorf("astikit: waiting failed: %w", err)
return
}
return
}(); err != nil {
return
}
return
}
}

301
vendor/github.com/asticode/go-astikit/stat.go generated vendored Normal file
View File

@@ -0,0 +1,301 @@
package astikit
import (
"context"
"sync"
"sync/atomic"
"time"
)
// Stater is an object that can compute and handle stats
type Stater struct {
cancel context.CancelFunc
ctx context.Context
h StatsHandleFunc
m *sync.Mutex // Locks ss
period time.Duration
running uint32
ss map[*StatMetadata]StatOptions
}
// StatOptions represents stat options
type StatOptions struct {
Handler StatHandler
Metadata *StatMetadata
}
// StatsHandleFunc is a method that can handle stat values
type StatsHandleFunc func(stats []StatValue)
// StatMetadata represents a stat metadata
type StatMetadata struct {
Description string
Label string
Name string
Unit string
}
// StatHandler represents a stat handler
type StatHandler interface {
Start()
Stop()
Value(delta time.Duration) interface{}
}
// StatValue represents a stat value
type StatValue struct {
*StatMetadata
Value interface{}
}
// StaterOptions represents stater options
type StaterOptions struct {
HandleFunc StatsHandleFunc
Period time.Duration
}
// NewStater creates a new stater
func NewStater(o StaterOptions) *Stater {
return &Stater{
h: o.HandleFunc,
m: &sync.Mutex{},
period: o.Period,
ss: make(map[*StatMetadata]StatOptions),
}
}
// Start starts the stater
func (s *Stater) Start(ctx context.Context) {
// Check context
if ctx.Err() != nil {
return
}
// Make sure to start only once
if atomic.CompareAndSwapUint32(&s.running, 0, 1) {
// Update status
defer atomic.StoreUint32(&s.running, 0)
// Reset context
s.ctx, s.cancel = context.WithCancel(ctx)
// Create ticker
t := time.NewTicker(s.period)
defer t.Stop()
// Loop
lastStatAt := now()
for {
select {
case <-t.C:
// Get delta
n := now()
delta := n.Sub(lastStatAt)
lastStatAt = n
// Loop through stats
var stats []StatValue
s.m.Lock()
for _, v := range s.ss {
stats = append(stats, StatValue{
StatMetadata: v.Metadata,
Value: v.Handler.Value(delta),
})
}
s.m.Unlock()
// Handle stats
go s.h(stats)
case <-s.ctx.Done():
return
}
}
}
}
// Stop stops the stater
func (s *Stater) Stop() {
if s.cancel != nil {
s.cancel()
}
}
// AddStats adds stats
func (s *Stater) AddStats(os ...StatOptions) {
s.m.Lock()
defer s.m.Unlock()
for _, o := range os {
s.ss[o.Metadata] = o
}
}
// DelStats deletes stats
func (s *Stater) DelStats(os ...StatOptions) {
s.m.Lock()
defer s.m.Unlock()
for _, o := range os {
delete(s.ss, o.Metadata)
}
}
type durationStat struct {
d time.Duration
fn func(d, delta time.Duration) interface{}
isStarted bool
m *sync.Mutex // Locks isStarted
startedAt time.Time
}
func newDurationStat(fn func(d, delta time.Duration) interface{}) *durationStat {
return &durationStat{
fn: fn,
m: &sync.Mutex{},
}
}
func (s *durationStat) Begin() {
s.m.Lock()
defer s.m.Unlock()
if !s.isStarted {
return
}
s.startedAt = now()
}
func (s *durationStat) End() {
s.m.Lock()
defer s.m.Unlock()
if !s.isStarted {
return
}
s.d += now().Sub(s.startedAt)
s.startedAt = time.Time{}
}
func (s *durationStat) Value(delta time.Duration) (o interface{}) {
// Lock
s.m.Lock()
defer s.m.Unlock()
// Get current values
n := now()
d := s.d
// Recording is still in process
if !s.startedAt.IsZero() {
d += n.Sub(s.startedAt)
s.startedAt = n
}
// Compute stat
o = s.fn(d, delta)
s.d = 0
return
}
func (s *durationStat) Start() {
s.m.Lock()
defer s.m.Unlock()
s.d = 0
s.isStarted = true
}
func (s *durationStat) Stop() {
s.m.Lock()
defer s.m.Unlock()
s.isStarted = false
}
// DurationPercentageStat is an object capable of computing the percentage of time some work is taking per second
type DurationPercentageStat struct {
*durationStat
}
// NewDurationPercentageStat creates a new duration percentage stat
func NewDurationPercentageStat() *DurationPercentageStat {
return &DurationPercentageStat{durationStat: newDurationStat(func(d, delta time.Duration) interface{} {
if delta == 0 {
return 0
}
return float64(d) / float64(delta) * 100
})}
}
type counterStat struct {
c float64
fn func(c, t float64, delta time.Duration) interface{}
isStarted bool
m *sync.Mutex // Locks isStarted
t float64
}
func newCounterStat(fn func(c, t float64, delta time.Duration) interface{}) *counterStat {
return &counterStat{
fn: fn,
m: &sync.Mutex{},
}
}
func (s *counterStat) Add(delta float64) {
s.m.Lock()
defer s.m.Unlock()
if !s.isStarted {
return
}
s.c += delta
s.t++
}
func (s *counterStat) Start() {
s.m.Lock()
defer s.m.Unlock()
s.c = 0
s.isStarted = true
s.t = 0
}
func (s *counterStat) Stop() {
s.m.Lock()
defer s.m.Unlock()
s.isStarted = true
}
func (s *counterStat) Value(delta time.Duration) interface{} {
s.m.Lock()
defer s.m.Unlock()
c := s.c
t := s.t
s.c = 0
s.t = 0
return s.fn(c, t, delta)
}
// CounterAvgStat is an object capable of computing the average value of a counter
type CounterAvgStat struct {
*counterStat
}
// NewCounterAvgStat creates a new counter avg stat
func NewCounterAvgStat() *CounterAvgStat {
return &CounterAvgStat{counterStat: newCounterStat(func(c, t float64, delta time.Duration) interface{} {
if t == 0 {
return 0
}
return c / t
})}
}
// CounterRateStat is an object capable of computing the average value of a counter per second
type CounterRateStat struct {
*counterStat
}
// NewCounterRateStat creates a new counter rate stat
func NewCounterRateStat() *CounterRateStat {
return &CounterRateStat{counterStat: newCounterStat(func(c, t float64, delta time.Duration) interface{} {
if delta.Seconds() == 0 {
return 0
}
return c / delta.Seconds()
})}
}

489
vendor/github.com/asticode/go-astikit/sync.go generated vendored Normal file
View File

@@ -0,0 +1,489 @@
package astikit
import (
"bytes"
"context"
"errors"
"fmt"
"runtime"
"sync"
"sync/atomic"
"time"
)
// Stat names
const (
StatNameWorkRatio = "astikit.work.ratio"
)
// Chan constants
const (
// Calling Add() only blocks if the chan has been started and the ctx
// has not been canceled
ChanAddStrategyBlockWhenStarted = "block.when.started"
// Calling Add() never blocks
ChanAddStrategyNoBlock = "no.block"
ChanOrderFIFO = "fifo"
ChanOrderFILO = "filo"
)
// Chan is an object capable of executing funcs in a specific order while controlling the conditions
// in which adding new funcs is blocking
// Check out ChanOptions for detailed options
type Chan struct {
cancel context.CancelFunc
c *sync.Cond
ctx context.Context
fs []func()
mc *sync.Mutex // Locks ctx
mf *sync.Mutex // Locks fs
o ChanOptions
running uint32
statWorkRatio *DurationPercentageStat
}
// ChanOptions are Chan options
type ChanOptions struct {
// Determines the conditions in which Add() blocks. See constants with pattern ChanAddStrategy*
// Default is ChanAddStrategyNoBlock
AddStrategy string
// Order in which the funcs will be processed. See constants with pattern ChanOrder*
// Default is ChanOrderFIFO
Order string
// By default the funcs not yet processed when the context is cancelled are dropped.
// If "ProcessAll" is true, ALL funcs are processed even after the context is cancelled.
// However, no funcs can be added after the context is cancelled
ProcessAll bool
}
// NewChan creates a new Chan
func NewChan(o ChanOptions) *Chan {
return &Chan{
c: sync.NewCond(&sync.Mutex{}),
mc: &sync.Mutex{},
mf: &sync.Mutex{},
o: o,
}
}
// Start starts the chan by looping through functions in the buffer and
// executing them if any, or waiting for a new one otherwise
func (c *Chan) Start(ctx context.Context) {
// Make sure to start only once
if atomic.CompareAndSwapUint32(&c.running, 0, 1) {
// Update status
defer atomic.StoreUint32(&c.running, 0)
// Create context
c.mc.Lock()
c.ctx, c.cancel = context.WithCancel(ctx)
d := c.ctx.Done()
c.mc.Unlock()
// Handle context
go func() {
// Wait for context to be done
<-d
// Signal
c.c.L.Lock()
c.c.Signal()
c.c.L.Unlock()
}()
// Loop
for {
// Lock cond here in case a func is added between retrieving l and doing the if on it
c.c.L.Lock()
// Get number of funcs in buffer
c.mf.Lock()
l := len(c.fs)
c.mf.Unlock()
// Only return if context has been cancelled and:
// - the user wants to drop funcs that has not yet been processed
// - the buffer is empty otherwise
c.mc.Lock()
if c.ctx.Err() != nil && (!c.o.ProcessAll || l == 0) {
c.mc.Unlock()
c.c.L.Unlock()
return
}
c.mc.Unlock()
// No funcs in buffer
if l == 0 {
c.c.Wait()
c.c.L.Unlock()
continue
}
c.c.L.Unlock()
// Get first func
c.mf.Lock()
fn := c.fs[0]
c.mf.Unlock()
// Execute func
if c.statWorkRatio != nil {
c.statWorkRatio.Begin()
}
fn()
if c.statWorkRatio != nil {
c.statWorkRatio.End()
}
// Remove first func
c.mf.Lock()
c.fs = c.fs[1:]
c.mf.Unlock()
}
}
}
// Stop stops the chan
func (c *Chan) Stop() {
c.mc.Lock()
if c.cancel != nil {
c.cancel()
}
c.mc.Unlock()
}
// Add adds a new item to the chan
func (c *Chan) Add(i func()) {
// Check context
c.mc.Lock()
if c.ctx != nil && c.ctx.Err() != nil {
c.mc.Unlock()
return
}
c.mc.Unlock()
// Wrap the function
var fn func()
var wg *sync.WaitGroup
if c.o.AddStrategy == ChanAddStrategyBlockWhenStarted {
wg = &sync.WaitGroup{}
wg.Add(1)
fn = func() {
defer wg.Done()
i()
}
} else {
fn = i
}
// Add func to buffer
c.mf.Lock()
if c.o.Order == ChanOrderFILO {
c.fs = append([]func(){fn}, c.fs...)
} else {
c.fs = append(c.fs, fn)
}
c.mf.Unlock()
// Signal
c.c.L.Lock()
c.c.Signal()
c.c.L.Unlock()
// Wait
if wg != nil {
wg.Wait()
}
}
// Reset resets the chan
func (c *Chan) Reset() {
c.mf.Lock()
defer c.mf.Unlock()
c.fs = []func(){}
}
// Stats returns the chan stats
func (c *Chan) Stats() []StatOptions {
if c.statWorkRatio == nil {
c.statWorkRatio = NewDurationPercentageStat()
}
return []StatOptions{
{
Handler: c.statWorkRatio,
Metadata: &StatMetadata{
Description: "Percentage of time doing work",
Label: "Work ratio",
Name: StatNameWorkRatio,
Unit: "%",
},
},
}
}
// BufferPool represents a *bytes.Buffer pool
type BufferPool struct {
bp *sync.Pool
}
// NewBufferPool creates a new BufferPool
func NewBufferPool() *BufferPool {
return &BufferPool{bp: &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}}
}
// New creates a new BufferPoolItem
func (p *BufferPool) New() *BufferPoolItem {
return newBufferPoolItem(p.bp.Get().(*bytes.Buffer), p.bp)
}
// BufferPoolItem represents a BufferPool item
type BufferPoolItem struct {
*bytes.Buffer
bp *sync.Pool
}
func newBufferPoolItem(b *bytes.Buffer, bp *sync.Pool) *BufferPoolItem {
return &BufferPoolItem{
Buffer: b,
bp: bp,
}
}
// Close implements the io.Closer interface
func (i *BufferPoolItem) Close() error {
i.Reset()
i.bp.Put(i.Buffer)
return nil
}
// GoroutineLimiter is an object capable of doing several things in parallel while maintaining the
// max number of things running in parallel under a threshold
type GoroutineLimiter struct {
busy int
c *sync.Cond
ctx context.Context
cancel context.CancelFunc
o GoroutineLimiterOptions
}
// GoroutineLimiterOptions represents GoroutineLimiter options
type GoroutineLimiterOptions struct {
Max int
}
// NewGoroutineLimiter creates a new GoroutineLimiter
func NewGoroutineLimiter(o GoroutineLimiterOptions) (l *GoroutineLimiter) {
l = &GoroutineLimiter{
c: sync.NewCond(&sync.Mutex{}),
o: o,
}
if l.o.Max <= 0 {
l.o.Max = 1
}
l.ctx, l.cancel = context.WithCancel(context.Background())
go l.handleCtx()
return
}
// Close closes the limiter properly
func (l *GoroutineLimiter) Close() error {
l.cancel()
return nil
}
func (l *GoroutineLimiter) handleCtx() {
<-l.ctx.Done()
l.c.L.Lock()
l.c.Broadcast()
l.c.L.Unlock()
}
// GoroutineLimiterFunc is a GoroutineLimiter func
type GoroutineLimiterFunc func()
// Do executes custom work in a goroutine
func (l *GoroutineLimiter) Do(fn GoroutineLimiterFunc) (err error) {
// Check context in case the limiter has already been closed
if err = l.ctx.Err(); err != nil {
return
}
// Lock
l.c.L.Lock()
// Wait for a goroutine to be available
for l.busy >= l.o.Max {
l.c.Wait()
}
// Check context in case the limiter has been closed while waiting
if err = l.ctx.Err(); err != nil {
return
}
// Increment
l.busy++
// Unlock
l.c.L.Unlock()
// Execute in a goroutine
go func() {
// Decrement
defer func() {
l.c.L.Lock()
l.busy--
l.c.Signal()
l.c.L.Unlock()
}()
// Execute
fn()
}()
return
}
// Eventer represents an object that can dispatch simple events (name + payload)
type Eventer struct {
c *Chan
hs map[string][]EventerHandler
mh *sync.Mutex
}
// EventerOptions represents Eventer options
type EventerOptions struct {
Chan ChanOptions
}
// EventerHandler represents a function that can handle the payload of an event
type EventerHandler func(payload interface{})
// NewEventer creates a new eventer
func NewEventer(o EventerOptions) *Eventer {
return &Eventer{
c: NewChan(o.Chan),
hs: make(map[string][]EventerHandler),
mh: &sync.Mutex{},
}
}
// On adds an handler for a specific name
func (e *Eventer) On(name string, h EventerHandler) {
// Lock
e.mh.Lock()
defer e.mh.Unlock()
// Add handler
e.hs[name] = append(e.hs[name], h)
}
// Dispatch dispatches a payload for a specific name
func (e *Eventer) Dispatch(name string, payload interface{}) {
// Lock
e.mh.Lock()
defer e.mh.Unlock()
// No handlers
hs, ok := e.hs[name]
if !ok {
return
}
// Loop through handlers
for _, h := range hs {
func(h EventerHandler) {
// Add to chan
e.c.Add(func() {
h(payload)
})
}(h)
}
}
// Start starts the eventer. It is blocking
func (e *Eventer) Start(ctx context.Context) {
e.c.Start(ctx)
}
// Stop stops the eventer
func (e *Eventer) Stop() {
e.c.Stop()
}
// Reset resets the eventer
func (e *Eventer) Reset() {
e.c.Reset()
}
// RWMutex represents a RWMutex capable of logging its actions to ease deadlock debugging
type RWMutex struct {
c string // Last successful caller
l SeverityLogger
m *sync.RWMutex
n string // Name
}
// RWMutexOptions represents RWMutex options
type RWMutexOptions struct {
Logger StdLogger
Name string
}
// NewRWMutex creates a new RWMutex
func NewRWMutex(o RWMutexOptions) *RWMutex {
return &RWMutex{
l: AdaptStdLogger(o.Logger),
m: &sync.RWMutex{},
n: o.Name,
}
}
func (m *RWMutex) caller() (o string) {
if _, file, line, ok := runtime.Caller(2); ok {
o = fmt.Sprintf("%s:%d", file, line)
}
return
}
// Lock write locks the mutex
func (m *RWMutex) Lock() {
c := m.caller()
m.l.Debugf("astikit: requesting lock for %s at %s", m.n, c)
m.m.Lock()
m.l.Debugf("astikit: lock acquired for %s at %s", m.n, c)
m.c = c
}
// Unlock write unlocks the mutex
func (m *RWMutex) Unlock() {
m.m.Unlock()
m.l.Debugf("astikit: unlock executed for %s", m.n)
}
// RLock read locks the mutex
func (m *RWMutex) RLock() {
c := m.caller()
m.l.Debugf("astikit: requesting rlock for %s at %s", m.n, c)
m.m.RLock()
m.l.Debugf("astikit: rlock acquired for %s at %s", m.n, c)
m.c = c
}
// RUnlock read unlocks the mutex
func (m *RWMutex) RUnlock() {
m.m.RUnlock()
m.l.Debugf("astikit: unlock executed for %s", m.n)
}
// IsDeadlocked checks whether the mutex is deadlocked with a given timeout
// and returns the last caller
func (m *RWMutex) IsDeadlocked(timeout time.Duration) (bool, string) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
go func() {
m.m.Lock()
cancel()
m.m.Unlock()
}()
<-ctx.Done()
return errors.Is(ctx.Err(), context.DeadlineExceeded), m.c
}

156
vendor/github.com/asticode/go-astikit/template.go generated vendored Normal file
View File

@@ -0,0 +1,156 @@
package astikit
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"text/template"
)
// Templater represents an object capable of storing and parsing templates
type Templater struct {
layouts []string
m sync.Mutex
templates map[string]*template.Template
}
// NewTemplater creates a new templater
func NewTemplater() *Templater {
return &Templater{templates: make(map[string]*template.Template)}
}
// AddLayoutsFromDir walks through a dir and add files as layouts
func (t *Templater) AddLayoutsFromDir(dirPath, ext string) (err error) {
// Get layouts
if err = filepath.Walk(dirPath, func(path string, info os.FileInfo, e error) (err error) {
// Check input error
if e != nil {
err = fmt.Errorf("astikit: walking layouts has an input error for path %s: %w", path, e)
return
}
// Only process files
if info.IsDir() {
return
}
// Check extension
if ext != "" && filepath.Ext(path) != ext {
return
}
// Read layout
var b []byte
if b, err = ioutil.ReadFile(path); err != nil {
err = fmt.Errorf("astikit: reading %s failed: %w", path, err)
return
}
// Add layout
t.AddLayout(string(b))
return
}); err != nil {
err = fmt.Errorf("astikit: walking layouts in %s failed: %w", dirPath, err)
return
}
return
}
// AddTemplatesFromDir walks through a dir and add files as templates
func (t *Templater) AddTemplatesFromDir(dirPath, ext string) (err error) {
// Loop through templates
if err = filepath.Walk(dirPath, func(path string, info os.FileInfo, e error) (err error) {
// Check input error
if e != nil {
err = fmt.Errorf("astikit: walking templates has an input error for path %s: %w", path, e)
return
}
// Only process files
if info.IsDir() {
return
}
// Check extension
if ext != "" && filepath.Ext(path) != ext {
return
}
// Read file
var b []byte
if b, err = ioutil.ReadFile(path); err != nil {
err = fmt.Errorf("astikit: reading template content of %s failed: %w", path, err)
return
}
// Add template
// We use ToSlash to homogenize Windows path
if err = t.AddTemplate(filepath.ToSlash(strings.TrimPrefix(path, dirPath)), string(b)); err != nil {
err = fmt.Errorf("astikit: adding template failed: %w", err)
return
}
return
}); err != nil {
err = fmt.Errorf("astikit: walking templates in %s failed: %w", dirPath, err)
return
}
return
}
// AddLayout adds a new layout
func (t *Templater) AddLayout(c string) {
t.layouts = append(t.layouts, c)
}
// AddTemplate adds a new template
func (t *Templater) AddTemplate(path, content string) (err error) {
// Parse
var tpl *template.Template
if tpl, err = t.Parse(content); err != nil {
err = fmt.Errorf("astikit: parsing template for path %s failed: %w", path, err)
return
}
// Add template
t.m.Lock()
t.templates[path] = tpl
t.m.Unlock()
return
}
// DelTemplate deletes a template
func (t *Templater) DelTemplate(path string) {
t.m.Lock()
defer t.m.Unlock()
delete(t.templates, path)
}
// Template retrieves a templates
func (t *Templater) Template(path string) (tpl *template.Template, ok bool) {
t.m.Lock()
defer t.m.Unlock()
tpl, ok = t.templates[path]
return
}
// Parse parses the content of a template
func (t *Templater) Parse(content string) (o *template.Template, err error) {
// Parse content
o = template.New("root")
if o, err = o.Parse(content); err != nil {
err = fmt.Errorf("astikit: parsing template content failed: %w", err)
return
}
// Parse layouts
for idx, l := range t.layouts {
if o, err = o.Parse(l); err != nil {
err = fmt.Errorf("astikit: parsing layout #%d failed: %w", idx+1, err)
return
}
}
return
}

58
vendor/github.com/asticode/go-astikit/time.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package astikit
import (
"context"
"strconv"
"time"
)
var now = func() time.Time { return time.Now() }
// Sleep is a cancellable sleep
func Sleep(ctx context.Context, d time.Duration) (err error) {
for {
select {
case <-time.After(d):
return
case <-ctx.Done():
err = ctx.Err()
return
}
}
}
// Timestamp represents a timestamp you can marshal and umarshal
type Timestamp struct {
time.Time
}
// NewTimestamp creates a new timestamp
func NewTimestamp(t time.Time) *Timestamp {
return &Timestamp{Time: t}
}
// UnmarshalJSON implements the JSONUnmarshaler interface
func (t *Timestamp) UnmarshalJSON(text []byte) error {
return t.UnmarshalText(text)
}
// UnmarshalText implements the TextUnmarshaler interface
func (t *Timestamp) UnmarshalText(text []byte) (err error) {
var i int
if i, err = strconv.Atoi(string(text)); err != nil {
return
}
t.Time = time.Unix(int64(i), 0)
return
}
// MarshalJSON implements the JSONMarshaler interface
func (t Timestamp) MarshalJSON() ([]byte, error) {
return t.MarshalText()
}
// MarshalText implements the TextMarshaler interface
func (t Timestamp) MarshalText() (text []byte, err error) {
text = []byte(strconv.Itoa(int(t.UTC().Unix())))
return
}

184
vendor/github.com/asticode/go-astikit/translator.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
package astikit
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
)
// Translator represents an object capable of translating stuff
type Translator struct {
m *sync.RWMutex // Lock p
o TranslatorOptions
p map[string]string
}
// TranslatorOptions represents Translator options
type TranslatorOptions struct {
DefaultLanguage string
}
// NewTranslator creates a new Translator
func NewTranslator(o TranslatorOptions) *Translator {
return &Translator{
m: &sync.RWMutex{},
o: o,
p: make(map[string]string),
}
}
// ParseDir adds translations located in ".json" files in the specified dir
func (t *Translator) ParseDir(dirPath string) (err error) {
// Default dir path
if dirPath == "" {
if dirPath, err = os.Getwd(); err != nil {
err = fmt.Errorf("astikit: getwd failed: %w", err)
return
}
}
// Walk through dir
if err = filepath.Walk(dirPath, func(path string, info os.FileInfo, e error) (err error) {
// Check input error
if e != nil {
err = fmt.Errorf("astikit: walking %s has an input error for path %s: %w", dirPath, path, e)
return
}
// Only process first level files
if info.IsDir() {
if path != dirPath {
err = filepath.SkipDir
}
return
}
// Only process ".json" files
if filepath.Ext(path) != ".json" {
return
}
// Parse file
if err = t.ParseFile(path); err != nil {
err = fmt.Errorf("astikit: parsing %s failed: %w", path, err)
return
}
return
}); err != nil {
err = fmt.Errorf("astikit: walking %s failed: %w", dirPath, err)
return
}
return
}
// ParseFile adds translation located in the provided path
func (t *Translator) ParseFile(path string) (err error) {
// Lock
t.m.Lock()
defer t.m.Unlock()
// Open file
var f *os.File
if f, err = os.Open(path); err != nil {
err = fmt.Errorf("astikit: opening %s failed: %w", path, err)
return
}
defer f.Close()
// Unmarshal
var p map[string]interface{}
if err = json.NewDecoder(f).Decode(&p); err != nil {
err = fmt.Errorf("astikit: unmarshaling %s failed: %w", path, err)
return
}
// Parse
t.parse(p, strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
return
}
func (t *Translator) key(prefix, key string) string {
return prefix + "." + key
}
func (t *Translator) parse(i map[string]interface{}, prefix string) {
for k, v := range i {
p := t.key(prefix, k)
switch a := v.(type) {
case string:
t.p[p] = a
case map[string]interface{}:
t.parse(a, p)
}
}
}
// HTTPMiddleware is the Translator HTTP middleware
func (t *Translator) HTTPMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Store language in context
if l := r.Header.Get("Accept-Language"); l != "" {
*r = *r.WithContext(contextWithTranslatorLanguage(r.Context(), l))
}
// Next handler
h.ServeHTTP(rw, r)
})
}
const contextKeyTranslatorLanguage = "astikit.translator.language"
func contextWithTranslatorLanguage(ctx context.Context, language string) context.Context {
return context.WithValue(ctx, contextKeyTranslatorLanguage, language)
}
func translatorLanguageFromContext(ctx context.Context) string {
v, ok := ctx.Value(contextKeyTranslatorLanguage).(string)
if !ok {
return ""
}
return v
}
func (t *Translator) language(language string) string {
if language == "" {
return t.o.DefaultLanguage
}
return language
}
// LanguageCtx returns the translator language from the context, or the default language if not in the context
func (t *Translator) LanguageCtx(ctx context.Context) string {
return t.language(translatorLanguageFromContext(ctx))
}
// Translate translates a key into a specific language
func (t *Translator) Translate(language, key string) string {
// Lock
t.m.RLock()
defer t.m.RUnlock()
// Get translation
k1 := t.key(t.language(language), key)
v, ok := t.p[k1]
if ok {
return v
}
// Default translation
k2 := t.key(t.o.DefaultLanguage, key)
if v, ok = t.p[k2]; ok {
return v
}
return k1
}
// TranslateCtx translates a key using the language specified in the context
func (t *Translator) TranslateCtx(ctx context.Context, key string) string {
return t.Translate(translatorLanguageFromContext(ctx), key)
}

148
vendor/github.com/asticode/go-astikit/worker.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
package astikit
import (
"context"
"os"
"os/signal"
"sync"
)
// Worker represents an object capable of blocking, handling signals and stopping
type Worker struct {
cancel context.CancelFunc
ctx context.Context
l SeverityLogger
os, ow sync.Once
wg *sync.WaitGroup
}
// WorkerOptions represents worker options
type WorkerOptions struct {
Logger StdLogger
}
// NewWorker builds a new worker
func NewWorker(o WorkerOptions) (w *Worker) {
w = &Worker{
l: AdaptStdLogger(o.Logger),
wg: &sync.WaitGroup{},
}
w.ctx, w.cancel = context.WithCancel(context.Background())
w.wg.Add(1)
w.l.Info("astikit: starting worker...")
return
}
// HandleSignals handles signals
func (w *Worker) HandleSignals(hs ...SignalHandler) {
// Prepend mandatory handler
hs = append([]SignalHandler{TermSignalHandler(w.Stop)}, hs...)
// Notify
ch := make(chan os.Signal, 1)
signal.Notify(ch)
// Execute in a task
w.NewTask().Do(func() {
for {
select {
case s := <-ch:
// Loop through handlers
for _, h := range hs {
h(s)
}
// Return
if isTermSignal(s) {
return
}
case <-w.Context().Done():
return
}
}
})
}
// Stop stops the Worker
func (w *Worker) Stop() {
w.os.Do(func() {
w.l.Info("astikit: stopping worker...")
w.cancel()
w.wg.Done()
})
}
// Wait is a blocking pattern
func (w *Worker) Wait() {
w.ow.Do(func() {
w.l.Info("astikit: worker is now waiting...")
w.wg.Wait()
})
}
// NewTask creates a new task
func (w *Worker) NewTask() *Task {
return newTask(w.wg)
}
// Context returns the worker's context
func (w *Worker) Context() context.Context {
return w.ctx
}
// Logger returns the worker's logger
func (w *Worker) Logger() SeverityLogger {
return w.l
}
// TaskFunc represents a function that can create a new task
type TaskFunc func() *Task
// Task represents a task
type Task struct {
od, ow sync.Once
wg, pwg *sync.WaitGroup
}
func newTask(parentWg *sync.WaitGroup) (t *Task) {
t = &Task{
wg: &sync.WaitGroup{},
pwg: parentWg,
}
t.pwg.Add(1)
return
}
// NewSubTask creates a new sub task
func (t *Task) NewSubTask() *Task {
return newTask(t.wg)
}
// Do executes the task
func (t *Task) Do(f func()) {
go func() {
// Make sure to mark the task as done
defer t.Done()
// Custom
f()
// Wait for first level subtasks to be done
// Wait() can also be called in f() if something needs to be executed just after Wait()
t.Wait()
}()
}
// Done indicates the task is done
func (t *Task) Done() {
t.od.Do(func() {
t.pwg.Done()
})
}
// Wait waits for first level subtasks to be finished
func (t *Task) Wait() {
t.ow.Do(func() {
t.wg.Wait()
})
}