mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Caption support (#2462)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
8
vendor/github.com/asticode/go-astikit/.travis.sh
generated
vendored
Normal file
8
vendor/github.com/asticode/go-astikit/.travis.sh
generated
vendored
Normal 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
15
vendor/github.com/asticode/go-astikit/.travis.yml
generated
vendored
Normal 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
21
vendor/github.com/asticode/go-astikit/LICENSE
generated
vendored
Normal 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
6
vendor/github.com/asticode/go-astikit/README.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[](http://goreportcard.com/report/github.com/asticode/go-astikit)
|
||||
[](https://godoc.org/github.com/asticode/go-astikit)
|
||||
[](https://travis-ci.org/asticode/go-astikit#)
|
||||
[](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
214
vendor/github.com/asticode/go-astikit/archive.go
generated
vendored
Normal 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
8
vendor/github.com/asticode/go-astikit/astikit.go
generated
vendored
Normal 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
297
vendor/github.com/asticode/go-astikit/binary.go
generated
vendored
Normal 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
164
vendor/github.com/asticode/go-astikit/bytes.go
generated
vendored
Normal 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
57
vendor/github.com/asticode/go-astikit/defer.go
generated
vendored
Normal 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
71
vendor/github.com/asticode/go-astikit/errors.go
generated
vendored
Normal 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
104
vendor/github.com/asticode/go-astikit/exec.go
generated
vendored
Normal 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
48
vendor/github.com/asticode/go-astikit/flag.go
generated
vendored
Normal 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
60
vendor/github.com/asticode/go-astikit/float.go
generated
vendored
Normal 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
632
vendor/github.com/asticode/go-astikit/http.go
generated
vendored
Normal 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
121
vendor/github.com/asticode/go-astikit/io.go
generated
vendored
Normal 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
101
vendor/github.com/asticode/go-astikit/limiter.go
generated
vendored
Normal 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
171
vendor/github.com/asticode/go-astikit/logger.go
generated
vendored
Normal 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
67
vendor/github.com/asticode/go-astikit/map.go
generated
vendored
Normal 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
148
vendor/github.com/asticode/go-astikit/os.go
generated
vendored
Normal 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
12
vendor/github.com/asticode/go-astikit/os_js.go
generated
vendored
Normal 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
12
vendor/github.com/asticode/go-astikit/os_others.go
generated
vendored
Normal 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
426
vendor/github.com/asticode/go-astikit/pcm.go
generated
vendored
Normal 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
58
vendor/github.com/asticode/go-astikit/ptr.go
generated
vendored
Normal 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
36
vendor/github.com/asticode/go-astikit/rand.go
generated
vendored
Normal 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
13
vendor/github.com/asticode/go-astikit/sort.go
generated
vendored
Normal 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
113
vendor/github.com/asticode/go-astikit/ssh.go
generated
vendored
Normal 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
301
vendor/github.com/asticode/go-astikit/stat.go
generated
vendored
Normal 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
489
vendor/github.com/asticode/go-astikit/sync.go
generated
vendored
Normal 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
156
vendor/github.com/asticode/go-astikit/template.go
generated
vendored
Normal 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
58
vendor/github.com/asticode/go-astikit/time.go
generated
vendored
Normal 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
184
vendor/github.com/asticode/go-astikit/translator.go
generated
vendored
Normal 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
148
vendor/github.com/asticode/go-astikit/worker.go
generated
vendored
Normal 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()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user