Initial commit, pt. 35

This commit is contained in:
Dmitrii Okunev
2024-07-06 14:32:16 +01:00
parent 69a64a64c2
commit 924c093376
56 changed files with 3709 additions and 758 deletions

5
.gitignore vendored
View File

@@ -1,2 +1,7 @@
*.apk
build
qtbox
qamel-*
rcc*.go
rcc*.cpp
*.qrc

0
.gitmodules vendored Normal file
View File

View File

@@ -22,5 +22,8 @@ streampanel-ios: builddir
streampanel-windows: builddir
CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows go build -ldflags "-H windowsgui" -o build/streampanel.exe ./cmd/streampanel/
streamd-linux-amd64: builddir
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/streamd-linux-amd64 ./cmd/streamd
builddir:
mkdir -p build

View File

@@ -17,6 +17,7 @@ import (
"github.com/spf13/pflag"
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
"github.com/xaionaro-go/streamctl/pkg/streamd"
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
"github.com/xaionaro-go/streamctl/pkg/streamd/server"
uiiface "github.com/xaionaro-go/streamctl/pkg/streamd/ui"
@@ -83,7 +84,7 @@ func main() {
}
defer belt.Flush(ctx)
dataPath, err := xpath.Expand(*configPath)
configPathExpanded, err := xpath.Expand(*configPath)
if err != nil {
l.Fatalf("unable to get the path to the data file: %v", err)
}
@@ -91,6 +92,8 @@ func main() {
var wg sync.WaitGroup
var cancelFunc context.CancelFunc
var _ui uiiface.UI
var streamdGRPC *server.GRPCServer
var streamdGRPCLocker sync.Mutex
restart := func() {
l.Debugf("restart()")
@@ -103,11 +106,25 @@ func main() {
wg.Add(1)
defer wg.Done()
l.Infof("starting a server")
streamdGRPCLocker.Lock()
defer streamdGRPCLocker.Unlock()
var cfg config.Config
err := config.ReadConfigFromPath(ctx, configPathExpanded, &cfg)
if err != nil {
l.Fatal(cfg)
}
ctx, _cancelFunc := context.WithCancel(ctx)
cancelFunc = _cancelFunc
streamD, err := streamd.New(dataPath, _ui, belt.CtxBelt(ctx))
streamD, err := streamd.New(
cfg,
_ui,
func(ctx context.Context, c config.Config) error {
return config.WriteConfigToPath(ctx, configPathExpanded, cfg)
},
belt.CtxBelt(ctx),
)
if err != nil {
l.Fatalf("unable to initialize the streamd instance: %v", err)
}
@@ -127,7 +144,8 @@ func main() {
}()
grpcServer := grpc.NewServer()
streamd_grpc.RegisterStreamDServer(grpcServer, server.NewGRPCServer(streamD))
streamdGRPC = server.NewGRPCServer(streamD)
streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC)
l.Infof("started server at %s", *listenAddr)
err = grpcServer.Serve(listener)
if err != nil {
@@ -137,6 +155,11 @@ func main() {
_ui = ui.NewUI(
ctx,
func(authURL string) {
streamdGRPCLocker.Lock()
defer streamdGRPCLocker.Unlock()
streamdGRPC.OpenOAuthURL(authURL)
},
func(ctx context.Context, s string) {
restart()
},

View File

@@ -15,6 +15,7 @@ import (
)
type UI struct {
OAuthURLOpenFn func(authURL string)
Belt *belt.Belt
RestartFn func(context.Context, string)
}
@@ -23,9 +24,11 @@ var _ ui.UI = (*UI)(nil)
func NewUI(
ctx context.Context,
oauthURLOpener func(authURL string),
restartFn func(context.Context, string),
) *UI {
return &UI{
OAuthURLOpenFn: oauthURLOpener,
Belt: belt.CtxBelt(ctx),
RestartFn: restartFn,
}
@@ -49,18 +52,33 @@ func (*UI) InputGitUserData(
return false, "", nil, nil
}
func (*UI) OAuthHandlerTwitch(
ctx context.Context,
func (ui *UI) oauth2Handler(
_ context.Context,
arg oauthhandler.OAuthHandlerArgument,
) error {
return oauthhandler.OAuth2HandlerViaCLI(ctx, arg)
codeCh, err := oauthhandler.NewCodeReceiver(arg.RedirectURL)
if err != nil {
return err
}
func (*UI) OAuthHandlerYouTube(
ui.OAuthURLOpenFn(arg.AuthURL)
code := <-codeCh
return arg.ExchangeFn(code)
}
func (ui *UI) OAuthHandlerTwitch(
ctx context.Context,
arg oauthhandler.OAuthHandlerArgument,
) error {
return oauthhandler.OAuth2HandlerViaCLI(ctx, arg)
return ui.oauth2Handler(ctx, arg)
}
func (ui *UI) OAuthHandlerYouTube(
ctx context.Context,
arg oauthhandler.OAuthHandlerArgument,
) error {
return ui.oauth2Handler(ctx, arg)
}
func (*UI) InputTwitchUserInfo(

View File

@@ -5,4 +5,4 @@ Website = "https://github.com/xaionaro/streamctl"
Name = "streampanel"
ID = "center.dx.streampanel"
Version = "0.1.0"
Build = 22
Build = 28

View File

@@ -0,0 +1,79 @@
package main
import (
"bytes"
"fmt"
"runtime"
"github.com/DataDog/gostackparse"
xruntime "github.com/facebookincubator/go-belt/pkg/runtime"
errmontypes "github.com/facebookincubator/go-belt/tool/experimental/errmon/types"
"github.com/facebookincubator/go-belt/tool/logger"
)
func getGoroutines() ([]errmontypes.Goroutine, int) {
// TODO: consider pprof.Lookup("goroutine") instead of runtime.Stack
// getting all goroutines
stackBufferSize := 65536 * runtime.NumGoroutine()
if stackBufferSize > 10*1024*1024 {
stackBufferSize = 10 * 1024 * 1024
}
stackBuffer := make([]byte, stackBufferSize)
n := runtime.Stack(stackBuffer, true)
goroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
if len(errs) > 0 { //nolint:staticcheck
// TODO: do something
}
// convert goroutines for the output
goroutinesConverted := make([]errmontypes.Goroutine, 0, len(goroutines))
for _, goroutine := range goroutines {
goroutinesConverted = append(goroutinesConverted, *goroutine)
}
// getting current goroutine ID
n = runtime.Stack(stackBuffer, false)
currentGoroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
if len(errs) > 0 { //nolint:staticcheck
// TODO: do something
}
var currentGoroutineID int
switch len(currentGoroutines) {
case 0:
// TODO: do something
case 1:
currentGoroutineID = currentGoroutines[0].ID
default:
// TODO: do something
}
return goroutinesConverted, currentGoroutineID
}
type ErrorMonitorLoggerHook struct {
ErrorMonitor errmontypes.ErrorMonitor
}
func (h *ErrorMonitorLoggerHook) ProcessLogEntry(entry *logger.Entry) bool {
if entry.Level > logger.LevelWarning {
return true
}
goroutines, currentGoroutineID := getGoroutines()
h.ErrorMonitor.Emitter().Emit(&errmontypes.Event{
Entry: *entry,
ID: "",
ExternalIDs: []any{},
Exception: errmontypes.Exception{
IsPanic: entry.Level <= logger.LevelPanic,
Error: fmt.Errorf("[%s] %s", entry.Level, entry.Message),
StackTrace: xruntime.CallerStackTrace(nil),
},
CurrentGoroutineID: currentGoroutineID,
Goroutines: goroutines,
})
return true
}
func (h *ErrorMonitorLoggerHook) Flush() {}

View File

@@ -2,6 +2,7 @@ package main
import (
"context"
"net"
"net/http"
_ "net/http/pprof"
"os"
@@ -9,10 +10,16 @@ import (
"runtime/pprof"
"github.com/facebookincubator/go-belt"
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/facebookincubator/go-belt/tool/logger/implementation/zap"
"github.com/getsentry/sentry-go"
"github.com/spf13/pflag"
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
"github.com/xaionaro-go/streamctl/pkg/streamd/server"
"github.com/xaionaro-go/streamctl/pkg/streampanel"
"google.golang.org/grpc"
)
const forceNetPProfOnAndroid = true
@@ -20,11 +27,13 @@ const forceNetPProfOnAndroid = true
func main() {
loggerLevel := logger.LevelWarning
pflag.Var(&loggerLevel, "log-level", "Log level")
listenAddr := pflag.String("listen-addr", "", "the address to listen for incoming connections to")
remoteAddr := pflag.String("remote-addr", "", "the address (for example 127.0.0.1:3594) of streamd to connect to, instead of running the stream controllers locally")
configPath := pflag.String("config-path", "~/.streampanel.yaml", "the path to the config file")
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
pflag.Parse()
l := zap.Default().WithLevel(loggerLevel)
@@ -67,21 +76,58 @@ func main() {
runtime.GOMAXPROCS(16)
}
listener, err := net.Listen("tcp", *listenAddr)
if err != nil {
l.Fatalf("failed to listen: %v", err)
}
ctx := context.Background()
go func() {
<-ctx.Done()
listener.Close()
}()
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
Dsn: *sentryDSN,
})
if err != nil {
l.Fatal(err)
}
sentryErrorMonitor := errmonsentry.New(sentryClient)
errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
l.WithHooks(&ErrorMonitorLoggerHook{
ErrorMonitor: sentryErrorMonitor,
})
ctx = logger.CtxWithLogger(ctx, l)
logger.Default = func() logger.Logger {
return l
}
defer belt.Flush(ctx)
var panel *streampanel.Panel
var opts []streampanel.Option
if *remoteAddr != "" {
panel = streampanel.NewRemote(*remoteAddr)
} else {
panel = streampanel.NewBuiltin(*configPath)
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
}
panel, err := streampanel.New(*configPath, opts...)
if err != nil {
l.Fatal(err)
}
err := panel.Loop(ctx)
if *listenAddr != "" {
go func() {
grpcServer := grpc.NewServer()
streamdGRPC := server.NewGRPCServer(panel.StreamD)
streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC)
l.Infof("started server at %s", *listenAddr)
err = grpcServer.Serve(listener)
if err != nil {
l.Fatal(err)
}
}()
}
err = panel.Loop(ctx)
if err != nil {
l.Fatal(err)
}

230
go.mod
View File

@@ -1,6 +1,8 @@
module github.com/xaionaro-go/streamctl
go 1.21
go 1.22.0
toolchain go1.22.3
// The original go-yaml is very slow, using the improved version instead
replace github.com/goccy/go-yaml v1.11.3 => github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b
@@ -17,100 +19,200 @@ require (
)
require (
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
dario.cat/mergo v1.0.0 // indirect
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Jorropo/jsync v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fredbi/uri v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fatih/color v1.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb // indirect
github.com/go-text/render v0.1.0 // indirect
github.com/go-text/typesetting v0.1.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/gorilla/websocket v1.5.2 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mmcloughlin/profile v0.1.1 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yuin/goldmark v1.5.5 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
)
require (
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
fyne.io/fyne/v2 v2.4.5
github.com/DataDog/gostackparse v0.6.0 // indirect
github.com/andreykaipov/goobs v1.4.1
github.com/fatih/color v1.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-git/go-git/v5 v5.12.0
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2 // indirect
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7 // indirect
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc // indirect
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-text/render v0.1.0 // indirect
github.com/go-text/typesetting v0.1.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/gorilla/websocket v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/iguanesolutions/go-systemd/v5 v5.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/spf13/pflag v1.0.5
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c
github.com/ipfs/boxo v0.13.1 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.21.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
github.com/libp2p/go-libp2p-routing-helpers v0.7.2 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-nat v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mmcloughlin/profile v0.1.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.5.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/quic-go v0.42.0 // indirect
github.com/quic-go/webtransport-go v0.6.0 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/rymdport/portal v0.2.2 // indirect
github.com/samber/lo v1.36.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/dig v1.17.1 // indirect
go.uber.org/fx v1.20.1 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.21.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec // indirect
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)
require (
fyne.io/fyne/v2 v2.4.6-0.20240630191754-79694263c06a
github.com/DataDog/gostackparse v0.6.0
github.com/andreykaipov/goobs v1.4.1
github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91
github.com/getsentry/sentry-go v0.28.1
github.com/go-git/go-git/v5 v5.12.0
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/hyprspace/hyprspace v0.10.1
github.com/immune-gmbh/attestation-sdk v0.0.0-20230711173209-f44e4502aeca
github.com/libp2p/go-libp2p v0.33.2
github.com/libp2p/go-libp2p-kad-dht v0.25.2
github.com/multiformats/go-multiaddr v0.12.3
github.com/prometheus/client_golang v1.18.0
github.com/spf13/pflag v1.0.5
github.com/xaionaro-go/datacounter v1.0.4
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c
github.com/yl2chen/cidranger v1.0.2
golang.org/x/crypto v0.23.0
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002
)

513
go.sum
View File

@@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
@@ -42,15 +44,24 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/fyne/v2 v2.4.5 h1:W6jpAEmLoBbKyBB+EXqI7GMJ7kLgHQWCa0wZHUV2VfQ=
fyne.io/fyne/v2 v2.4.5/go.mod h1:SlOgbca0y80cRObu/JOhxIJdIgtoW7aCyqUVlTMgs0Y=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
fyne.io/fyne/v2 v2.4.6-0.20240630191754-79694263c06a h1:3qFUyQMqANRdbmuBXt/9YCObynWOMKYUcngIq243QjA=
fyne.io/fyne/v2 v2.4.6-0.20240630191754-79694263c06a/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/gostackparse v0.6.0 h1:egCGQviIabPwsyoWpGvIBGrEnNWez35aEO7OJ1vBI4o=
github.com/DataDog/gostackparse v0.6.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU=
github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
@@ -58,6 +69,7 @@ github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0k
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/andreykaipov/goobs v1.4.1 h1:IpvSMVFzwsrN2d+h8pwMMHIAYiR4sVS2jSTYKNvNmA4=
github.com/andreykaipov/goobs v1.4.1/go.mod h1:rjGZl9Y/O2axzrGwq9kL0l+ykKtRUYcNgPVh23YVwgc=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -66,17 +78,27 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
@@ -84,8 +106,16 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
@@ -93,6 +123,21 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 h1:jAUM3D1KIrJmwx60DKB+a/qqM69yHnu6otDGVa2t0vs=
github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91/go.mod h1:8rK6Kbo1Jd6sK22b24aPVgAm3jlNy1q1ft+lBALdIqA=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@@ -109,22 +154,38 @@ github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38/go.mod h
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg=
github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a h1:ybgRdYvAHTn93HW79bLiBiJwVL4jVeyGQRZMgImoeWs=
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
@@ -139,9 +200,8 @@ github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVin
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb h1:S9I8pIVT5JHKDvmI1vQ0qs5fqxzUfhcZm/YbUC/8k1k=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -155,12 +215,14 @@ github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335 h1:N17hl+3/Zqxg3SM+33Q
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335/go.mod h1:rmcKNA11zmis1auYtl0UY64vE/4+QKeS07w/htllpSE=
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc h1:VNz633GRJx2/hL0SpBNoNlLid4xtyi7LSJP1kHpD2Fo=
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc/go.mod h1:Pz/V4pxeXP0hjBlXIrm2ehR0GJ0l4Bon3fsOl6TmoJs=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs=
github.com/go-text/render v0.1.0/go.mod h1:jqEuNMenrmj6QRnkdpeaP0oKGFLDNhDkVKwGjsWWYU4=
github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw=
@@ -169,9 +231,13 @@ github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3 h1:levTn
github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@@ -181,6 +247,7 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -210,6 +277,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -224,7 +293,11 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -239,6 +312,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
@@ -247,6 +322,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
@@ -255,15 +332,19 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw=
github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30=
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -278,47 +359,151 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/hyprspace/hyprspace v0.10.1 h1:YAFX9Fp9GsA1bAH3Bg3ajZwvvO7eV3eHmezJmX6oyQg=
github.com/hyprspace/hyprspace v0.10.1/go.mod h1:e2eSbG9E+siBN767gYkcF7QrfZ6HfLdyiAO0eSo079g=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/iguanesolutions/go-systemd/v5 v5.1.1 h1:Hs0Z16knPGCBFnKECrICPh+RQ89Sgy0xyzcalrHMKdw=
github.com/iguanesolutions/go-systemd/v5 v5.1.1/go.mod h1:Quv57scs6S7T0rC6qyLfW20KU/P4p9hrbLPF+ILYrXY=
github.com/immune-gmbh/attestation-sdk v0.0.0-20230711173209-f44e4502aeca h1:d7LQ/Sqt4qKZsCCf0pQ2ifknhxlkzEgU9vQlJtVAWyg=
github.com/immune-gmbh/attestation-sdk v0.0.0-20230711173209-f44e4502aeca/go.mod h1:ApkH6Puh8Xw6QKGcF9xfn2onhhggIvWF+uVpiT/CEtc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/boxo v0.13.1 h1:nQ5oQzcMZR3oL41REJDcTbrvDvuZh3J9ckc9+ILeRQI=
github.com/ipfs/boxo v0.13.1/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8=
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=
github.com/libp2p/go-libp2p v0.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40=
github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ=
github.com/libp2p/go-libp2p-kad-dht v0.25.2/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo=
github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0=
github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0=
github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=
github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk=
github.com/libp2p/go-libp2p-routing-helpers v0.7.2 h1:xJMFyhQ3Iuqnk9Q2dYE1eUTzsah7NLw3Qs2zjUV78T0=
github.com/libp2p/go-libp2p-routing-helpers v0.7.2/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ=
github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -332,47 +517,153 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mmcloughlin/profile v0.1.1 h1:jhDmAqPyebOsVDOCICJoINoLb/AnLBaUw58nFzxWS2w=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.12.3 h1:hVBXvPRcKG0w80VinQ23P5t7czWgg65BmIvQKjDydU8=
github.com/multiformats/go-multiaddr v0.12.3/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE=
github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA=
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicklaw5/helix/v2 v2.26.0 h1:Qkc/R0eCDdWtUmnczk2g03+mObPUfc49Kz2Bt4B5d0g=
github.com/nicklaw5/helix/v2 v2.26.0/go.mod h1:zZcKsyyBWDli34x3QleYsVMiiNGMXPAEU5NjsiZDtvY=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=
github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/rymdport/portal v0.2.2 h1:P2Q/4k673zxdFAsbD8EESZ7psfuO6/4jNu6EDrDICkM=
github.com/rymdport/portal v0.2.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs=
github.com/smartystreets/assertions v1.13.0/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
@@ -389,6 +680,8 @@ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+g
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -401,12 +694,30 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
github.com/xaionaro-go/datacounter v1.0.4 h1:+QMZLmu73R5WGkQfUPwlXF/JFN+Weo4iuDZkiL2wVm8=
github.com/xaionaro-go/datacounter v1.0.4/go.mod h1:Sf9vBevuV6w5iE6K3qJ9pWVKcyS60clWBUSQLjt5++c=
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c h1:WeiZrQbFImtlpYCHdxWpjm033Zm0n1ihx3bD/9b6rYI=
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c/go.mod h1:fnHPnf0CsRZNVxlSs0ZdUiYW49mLF5QFycfmoT+LuO4=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b h1:eoc4aMdU40lOUIlpKTRXfSMcaE8Z6EFJuFw5ptB83lg=
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -416,11 +727,12 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -438,31 +750,50 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=
go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -474,12 +805,13 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -495,8 +827,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -508,15 +840,19 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -551,10 +887,13 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -568,6 +907,7 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -581,13 +921,17 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -602,8 +946,10 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -612,6 +958,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -621,6 +969,7 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -634,20 +983,20 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -661,13 +1010,18 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -683,6 +1037,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -722,13 +1078,23 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -754,6 +1120,8 @@ google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1Avk
google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -763,6 +1131,10 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -807,6 +1179,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec h1:C3cpGJVV1aqtO+b3L4LV6wQsB7sYplbahYZxrjkZd3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -841,17 +1216,19 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002 h1:V7Da7qt0MkY3noVANIMVBk28nOnijADeOR3i5Hcvpj4=
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -861,8 +1238,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -870,6 +1249,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -0,0 +1 @@
Imported 3D assets and components imported from bundles will be created in this folder.

View File

@@ -0,0 +1,122 @@
import QmlProject
Project {
mainFile: "QStreamContent/App.qml"
mainUiFile: "QStreamContent/Control.ui.qml"
/* Include .qml, .js, and image files from current directory and subdirectories */
QmlFiles {
directory: "QStream"
}
QmlFiles {
directory: "QStreamContent"
}
QmlFiles {
directory: "Generated"
}
JavaScriptFiles {
directory: "QStream"
}
JavaScriptFiles {
directory: "QStream"
}
ImageFiles {
directory: "QStreamContent"
}
ImageFiles {
directory: "Generated"
}
Files {
filter: "*.conf"
files: ["qtquickcontrols2.conf"]
}
Files {
filter: "qmldir"
directory: "."
}
Files {
filter: "*.ttf;*.otf"
}
Files {
filter: "*.wav;*.mp3"
}
Files {
filter: "*.mp4"
}
Files {
filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag"
}
Files {
filter: "*.qsb"
}
Files {
filter: "*.json"
}
Files {
filter: "*.mesh"
directory: "Generated"
}
Files {
filter: "*.qad"
directory: "Generated"
}
Environment {
QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
QT_AUTO_SCREEN_SCALE_FACTOR: "1"
QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
QT_LOGGING_RULES: "qt.qml.connections=false"
QT_ENABLE_HIGHDPI_SCALING: "0"
/* Useful for debugging
QSG_VISUALIZE=batches
QSG_VISUALIZE=clip
QSG_VISUALIZE=changes
QSG_VISUALIZE=overdraw
*/
}
qt6Project: true
/* List of plugin directories passed to QML runtime */
importPaths: [ "." ]
/* Required for deployment */
targetDirectory: "/opt/QStream"
qdsVersion: "4.5"
quickVersion: "6.7"
/* If any modules the project imports require widgets (e.g. QtCharts), widgetApp must be true */
widgetApp: true
/* args: Specifies command line arguments for qsb tool to generate shaders.
files: Specifies target files for qsb tool. If path is included, it must be relative to this file.
Wildcard '*' can be used in the file name part of the path.
e.g. files: [ "QStreamContent/shaders/*.vert", "*.frag" ] */
ShaderTool {
args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12"
files: [ "QStreamContent/shaders/*" ]
}
multilanguageSupport: true
supportedLanguages: ["en"]
primaryLanguage: "en"
}

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtDesignStudio 4.5.1, 2024-07-02T22:36:24. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{ec5bdba3-1159-44c9-a0db-a0d3d900c9c2}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 6.7.1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 6.7.1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{63f87550-2541-4163-9631-08b7fea781da}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">-1</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">0</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">QML Runtime</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmlProjectManager.QmlRunConfiguration.Qml</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="QmlProjectManager.QmlRunConfiguration.LastUsedLanguage">en</value>
<value type="QString" key="QmlProjectManager.QmlRunConfiguration.MainScript">CurrentFile</value>
<value type="bool" key="QmlProjectManager.QmlRunConfiguration.UseMultiLanguage">true</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 13.0.2, 2024-07-03T01:53:48. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{e9757cbe-2f51-4736-a8e1-c1053c85fe17}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">12</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 6.7.2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 6.7.2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt6.672.linux_gcc_64_kit</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">-1</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">0</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">QML Runtime</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmlProjectManager.QmlRunConfiguration.Qml</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="QmlProjectManager.QmlRunConfiguration.LastUsedLanguage">en</value>
<value type="QString" key="QmlProjectManager.QmlRunConfiguration.MainScript">CurrentFile</value>
<value type="bool" key="QmlProjectManager.QmlRunConfiguration.UseMultiLanguage">true</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1,21 @@
pragma Singleton
import QtQuick
QtObject {
readonly property int width: 1080
readonly property int height: 1920
property string relativeFontDirectory: "fonts"
/* Edit this comment to add your custom font */
readonly property font font: Qt.font({
family: Qt.application.font.family,
pixelSize: Qt.application.font.pixelSize
})
readonly property font largeFont: Qt.font({
family: Qt.application.font.family,
pixelSize: Qt.application.font.pixelSize * 1.6
})
readonly property color backgroundColor: "#EAEAEA"
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick 6.7
import Qt.labs.folderlistmodel 6.7
QtObject {
id: loader
property url fontDirectory: Qt.resolvedUrl("../../QStreamContent/" + relativeFontDirectory)
property string relativeFontDirectory: "fonts"
function loadFont(url) {
var fontLoader = Qt.createQmlObject('import QtQuick 2.15; FontLoader { source: "' + url + '"; }',
loader,
"dynamicFontLoader");
}
property FolderListModel folderModel: FolderListModel {
id: folderModel
folder: loader.fontDirectory
nameFilters: [ "*.ttf", "*.otf" ]
showDirs: false
onStatusChanged: {
if (folderModel.status == FolderListModel.Ready) {
var i
for (i = 0; i < count; i++) {
loadFont(folderModel.get(i, "fileURL"))
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick 6.7
ListModel {
id: eventListModel
ListElement {
eventId: "enterPressed"
eventDescription: "Emitted when pressing the enter button"
shortcut: "Return"
parameters: "Enter"
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick 6.7
import QtQuick.Studio.EventSimulator 1.0
import QtQuick.Studio.EventSystem 1.0
QtObject {
id: simulator
property bool active: true
property Timer __timer: Timer {
id: timer
interval: 100
onTriggered: {
EventSimulator.show()
}
}
Component.onCompleted: {
EventSystem.init(Qt.resolvedUrl("EventListModel.qml"))
if (simulator.active)
timer.start()
}
}

View File

@@ -0,0 +1,13 @@
MetaInfo {
Type {
name: "QStream.EventListSimulator"
icon: ":/qtquickplugin/images/item-icon16.png"
Hints {
visibleInNavigator: true
canBeDroppedInNavigator: true
canBeDroppedInFormEditor: false
canBeDroppedInView3D: false
}
}
}

View File

@@ -0,0 +1,5 @@
module QStream
singleton Constants 1.0 Constants.qml
EventListSimulator 1.0 EventListSimulator.qml
EventListModel 1.0 EventListModel.qml
DirectoryFontLoader 1.0 DirectoryFontLoader.qml

View File

@@ -0,0 +1,54 @@
import QtQuick 6.7
import QtQuick.Controls 2.15
import QStream
Window {
width: Constants.width
height: Constants.height
visible: true
TabBar {
anchors.left: parent.left
anchors.right: parent.right
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("Control")
}
TabButton {
text: qsTr("Monitor")
}
TabButton {
text: qsTr("Settings")
}
}
SwipeView {
id: swipeView
x: 0
y: 40
anchors.top: tabBar.bottom
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.left: parent.left
currentIndex: tabBar.currentIndex
Controls {
id: control
}
Settings {
id: settings
}
Monitor {
id: monitor
}
}
}

View File

@@ -0,0 +1,29 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle
{
SystemPalette {
id: palette
colorGroup: SystemPalette.Active
}
property int lBorderWidth : 1
property int rBorderWidth : 1
property int tBorderWidth : 1
property int bBorderWidth : 1
z : -1
property string borderColor : palette.midlight
color: "transparent"
anchors.fill: parent
Rectangle {
width:parent.width-20
height:parent.height-20
border.color: "#ff0000"
border.width: 5
}
}

View File

@@ -0,0 +1,170 @@
/*
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
*/
import QtQuick
import QtQuick.Controls
import QStream
Column {
id: root
Row {
id: top_buttons
width: 200
height: 50
anchors.left: parent.left
anchors.right: parent.right
Text {
id: label_profile
text: qsTr("Profile:")
anchors.top: parent.top
anchors.bottom: parent.bottom
verticalAlignment: Text.AlignVCenter
padding: 10
font.pointSize: 16
}
Button {
id: button_profile_create
width: button_profile_create.height
height: 100
text: qsTr("+")
anchors.top: parent.top
anchors.bottom: parent.bottom
icon.source: "../3rdparty/font-awesome/svgs/solid/plus.svg"
display: AbstractButton.IconOnly
}
Button {
id: button_profile_copy
width: button_profile_copy.height
text: qsTr("Copy")
anchors.top: parent.top
anchors.bottom: parent.bottom
display: AbstractButton.IconOnly
icon.source: "../3rdparty/font-awesome/svgs/solid/copy.svg"
}
Button {
id: button_profile_edit
width: button_profile_edit.height
text: qsTr("Edit")
anchors.top: parent.top
anchors.bottom: parent.bottom
display: AbstractButton.IconOnly
icon.source: "../3rdparty/font-awesome/svgs/solid/pencil.svg"
}
Button {
id: button_profile_delete
width: button_profile_delete.height
text: qsTr("-")
anchors.top: parent.top
anchors.bottom: parent.bottom
display: AbstractButton.IconOnly
icon.source: "../3rdparty/font-awesome/svgs/solid/trash-can.svg"
}
}
ListViewStyled {
id: listView_profiles
width: parent.width
height: parent.height - top_buttons.height - textField_title.height
- scrollView_description.height - controls.height
model: ListModel {
ListElement {
name: "Red"
}
ListElement {
name: "Green"
}
ListElement {
name: "Blue"
}
ListElement {
name: "White"
}
}
}
TextField {
id: textField_title
width: parent.width
placeholderText: "Stream title"
}
ScrollView {
id: scrollView_description
anchors.left: parent.left
anchors.right: parent.right
height: Math.min(contentHeight, 100)
contentWidth: width
contentHeight: textArea_description.implicitHeight
TextArea {
id: textArea_description
wrapMode: Text.WordWrap
placeholderText: qsTr("Stream description")
}
}
Row {
id: controls
width: 200
height: top_buttons.height
anchors.left: parent.left
anchors.right: parent.right
CheckBox {
id: checkbox_twitch
text: qsTr("Twtich")
anchors.top: parent.top
anchors.bottom: parent.bottom
checked: true
}
CheckBox {
id: checkbox_youtube
text: qsTr("YouTube")
anchors.top: parent.top
anchors.bottom: parent.bottom
checked: true
}
DelayButton {
id: button_setupStream
width: (controls.width - checkbox_twitch.width - checkbox_youtube.width) / 2
text: qsTr("Setup stream")
anchors.top: parent.top
anchors.bottom: parent.bottom
delay: 1000
}
DelayButton {
id: button_startStream
width: button_setupStream.width
text: qsTr("Start stream")
anchors.top: parent.top
anchors.bottom: parent.bottom
display: AbstractButton.IconOnly
icon.height: 24
icon.width: 24
icon.source: "../3rdparty/font-awesome/svgs/solid/circle-play.svg"
delay: 5000
palette {
dark: '#0f0'
}
}
}
Item {
id: __materialLibrary__
}
}

View File

@@ -0,0 +1,63 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
ListView {
id: listView
header: TextField {
width: listView.width
placeholderText: "Filter"
}
model: ListModel {
ListElement {
name: "Red"
}
ListElement {
name: "Green"
}
ListElement {
name: "Blue"
}
ListElement {
name: "White"
}
}
delegate: Rectangle {
id: rectangle
visible: !headerItem.text || _text.text.toLowerCase().includes(headerItem.text.toLowerCase())
width: parent.width
height: visible ? 30 : 0
color: listView.currentIndex
== index ? listView.palette.highlight : (index % 2
== 0 ? listView.palette.base : listView.palette.alternateBase)
border {
color: listView.palette.alternateBase
width: 2
}
Row {
spacing: 5
x: 10
width: parent.width - 10
height: parent.height
Text {
id: _text
width: parent.width
height: parent.height
text: name
verticalAlignment: Text.AlignVCenter
color: listView.currentIndex
== index ? listView.palette.highlightedText : listView.palette.text
font.bold: listView.currentIndex == index
}
}
MouseArea {
width: parent.width
height: parent.height
onClicked: listView.currentIndex = index
}
}
}

View File

@@ -0,0 +1,16 @@
/*
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
*/
import QtQuick
import QtQuick.Controls
import QStream
Column {
id: root
width: Constants.width
height: Constants.height
}

View File

@@ -0,0 +1,15 @@
/*
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
Column {
id: root
width: Constants.width
height: Constants.height
}

View File

@@ -0,0 +1 @@
Fonts in this folder are loaded automatically.

View File

@@ -0,0 +1,41 @@
package main
/*
import (
"os"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/qml"
"github.com/therecipe/qt/quickcontrols2"
)
func main() {
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)
gui.NewQGuiApplication(len(os.Args), os.Args)
quickcontrols2.QQuickStyle_SetStyle("Default")
engine := qml.NewQQmlApplicationEngine(nil)
engine.Load(core.QUrl_FromLocalFile("./QStreamContent/App.qml"))
gui.QGuiApplication_Exec()
}
*/
/*
import (
"os"
"github.com/go-qamel/qamel"
)
func main() {
app := qamel.NewApplication(len(os.Args), os.Args)
app.SetApplicationDisplayName("test")
view := qamel.NewViewerWithSource("QStreamContent/App.qml")
view.SetResizeMode(qamel.SizeRootObjectToView)
view.SetHeight(400)
view.SetWidth(600)
view.Show()
app.Exec()
}
*/
func main() {}

View File

@@ -0,0 +1,8 @@
[Controls]
Style=Basic
[Material]
Theme=Dark
[Universal]
Theme=Dark

View File

@@ -0,0 +1,44 @@
package config
import (
"fmt"
"github.com/hyprspace/hyprspace/config"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
)
func New() (*config.Config, error) {
privKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)
if err != nil {
return nil, fmt.Errorf("unable to generate a key pair: %w", err)
}
var addrs []multiaddr.Multiaddr
for _, addrString := range []string{
"/ip4/0.0.0.0/tcp/8001",
"/ip4/0.0.0.0/udp/8001/quic-v1",
"/ip6/::/tcp/8001",
"/ip6/::/udp/8001/quic-v1",
} {
addr, err := multiaddr.NewMultiaddr(addrString)
if err != nil {
return nil, fmt.Errorf("unable to parse address '%s': %w", addrString, err)
}
addrs = append(addrs, addr)
}
cfg := &config.Config{
PrivateKey: privKey,
ListenAddresses: addrs,
}
// it's done, but let's validate:
_, err = peer.IDFromPrivateKey(privKey)
if err != nil {
return nil, fmt.Errorf("unable to generate a peer ID from the private key: %w", err)
}
return cfg, nil
}

422
pkg/hyprspace/up.go Normal file
View File

@@ -0,0 +1,422 @@
// copied from https://github.com/hyprspace/hyprspace/blob/master/cli/up.go
// LICENSE: Apache-2.0
//
// Modified by Dmitrii Okunev, LICENSE of the changes: CC-0, or WTFPL-2.0 or Apache-2.0 (chose what you like)
package hyprspace
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io/fs"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"sync"
"syscall"
"time"
"github.com/hyprspace/hyprspace/config"
hsdns "github.com/hyprspace/hyprspace/dns"
"github.com/hyprspace/hyprspace/p2p"
hsrpc "github.com/hyprspace/hyprspace/rpc"
"github.com/hyprspace/hyprspace/svc"
"github.com/hyprspace/hyprspace/tun"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/yl2chen/cidranger"
)
type MuxStream struct {
Stream *network.Stream
Lock *sync.Mutex
}
var (
cfg *config.Config
node host.Host
// iface is the tun device used to pass packets between
// Hyprspace and the user's machine.
tunDev *tun.TUN
// activeStreams is a map of active streams to a peer
activeStreams map[peer.ID]MuxStream
// context
ctx context.Context
// context cancel function
ctxCancel func()
)
// UpRun handles the execution of the up command.
func UpRun(
interfaceName string,
_cfg string,
) {
ifName := interfaceName
if ifName == "" {
ifName = "hyprspace"
}
// Parse Global Config Flag for Custom Config Path
configPath := _cfg
if configPath == "" {
configPath = "/etc/hyprspace/" + ifName + ".json"
}
// Read in configuration from file.
cfg2, err := config.Read(configPath)
checkErr(err)
cfg2.Interface = ifName
cfg = cfg2
fmt.Println("[+] Creating TUN Device")
// Create new TUN device
tunDev, err = tun.New(
cfg.Interface,
tun.Address(cfg.BuiltinAddr4.String()+"/32"),
tun.Address(cfg.BuiltinAddr6.String()+"/128"),
tun.MTU(1420),
)
if err != nil {
checkErr(err)
}
allRoutes4, err := cfg.PeerLookup.ByRoute.CoveredNetworks(*cidranger.AllIPv4)
if err != nil {
checkErr(err)
}
allRoutes6, err := cfg.PeerLookup.ByRoute.CoveredNetworks(*cidranger.AllIPv6)
if err != nil {
checkErr(err)
}
var routeOpts []tun.Option
for _, r := range allRoutes4 {
routeOpts = append(routeOpts, tun.Route(r.Network()))
}
for _, r := range allRoutes6 {
routeOpts = append(routeOpts, tun.Route(r.Network()))
}
// Setup System Context
ctx, ctxCancel = context.WithCancel(context.Background())
fmt.Println("[+] Creating LibP2P Node")
// Create P2P Node
host, dht, err := p2p.CreateNode(
ctx,
cfg.PrivateKey,
cfg.ListenAddresses,
streamHandler,
p2p.NewClosedCircuitRelayFilter(cfg.Peers),
p2p.NewRecursionGater(cfg),
cfg.Peers,
)
checkErr(err)
host.SetStreamHandler(p2p.PeXProtocol, p2p.NewPeXStreamHandler(host, cfg))
node = host
for _, p := range cfg.Peers {
host.ConnManager().Protect(p.ID, "/hyprspace/peer")
}
fmt.Println("[+] Setting Up Node Discovery via DHT")
// Setup P2P Discovery
go p2p.Discover(ctx, host, dht, cfg.Peers)
// Configure path for lock
lockPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface+".lock")
// PeX
go p2p.PeXService(ctx, host, cfg)
// Route metrics and latency
go p2p.RouteMetricsService(ctx, host, cfg)
// Register the application to listen for signals
go signalHandler(ctx, host, lockPath, dht)
// Log about various events
go eventLogger(ctx, host)
// RPC server
go hsrpc.RpcServer(ctx, multiaddr.StringCast(fmt.Sprintf("/unix/run/hyprspace-rpc.%s.sock", cfg.Interface)), host, *cfg, *tunDev)
// Magic DNS server
go hsdns.MagicDnsServer(ctx, *cfg, node)
// metrics endpoint
metricsPort, ok := os.LookupEnv("HYPRSPACE_METRICS_PORT")
if ok {
metricsTuple := fmt.Sprintf("127.0.0.1:%s", metricsPort)
http.Handle("/metrics", promhttp.Handler())
go func() {
http.ListenAndServe(metricsTuple, nil)
}()
fmt.Printf("[+] Listening for metrics scrape requests on http://%s/metrics\n", metricsTuple)
}
serviceNet := svc.NewServiceNetwork(host, cfg, tunDev)
for name, addr := range cfg.Services {
proxy, err := svc.ProxyTo(addr)
checkErr(err)
serviceNet.Register(
name,
proxy,
)
}
var svcNetIds [][4]byte
for _, p := range cfg.Peers {
svcNetIds = append(svcNetIds, config.MkNetID(p.ID))
}
svcNetIds = append(svcNetIds, config.MkNetID(host.ID()))
for _, netId := range svcNetIds {
addr := make([]byte, 16)
copy(addr, serviceNet.NetworkRange.IP)
copy(addr[10:], netId[:])
mask1, mask0 := serviceNet.NetworkRange.Mask.Size()
routeOpts = append(routeOpts, tun.Route(net.IPNet{
IP: addr,
Mask: net.CIDRMask(mask1+32, mask0),
}))
}
// Write lock to filesystem to indicate an existing running daemon.
err = os.WriteFile(lockPath, []byte(fmt.Sprint(os.Getpid())), os.ModePerm)
checkErr(err)
// Bring Up TUN Device
err = tunDev.Up()
if err != nil {
checkErr(errors.New("unable to bring up tun device"))
}
checkErr(tunDev.Apply(routeOpts...))
fmt.Println("[+] Network setup complete")
// + ----------------------------------------+
// | Listen For New Packets on TUN Interface |
// + ----------------------------------------+
// Initialize active streams map and packet byte array.
activeStreams = make(map[peer.ID]MuxStream)
for {
var packet = make([]byte, 1420)
// Read in a packet from the tun device.
plen, err := tunDev.Iface.Read(packet)
if errors.Is(err, fs.ErrClosed) {
fmt.Println("[-] Interface closed")
<-ctx.Done()
time.Sleep(1 * time.Second)
return
} else if err != nil {
fmt.Println(err)
continue
}
var dstIP net.IP
proto := packet[0] & 0xf0
if proto == 0x40 {
dstIP = net.IP(packet[16:20])
if cfg.BuiltinAddr4.Equal(dstIP) {
continue
}
} else if proto == 0x60 {
dstIP = net.IP(packet[24:40])
if cfg.BuiltinAddr6.Equal(dstIP) {
continue
} else if serviceNet.NetworkRange.Contains(dstIP) {
// Are you TCP because your protocol is 6, or is your protocol 6 because you are TCP?
if packet[6] == 0x06 {
port := uint16(packet[42])*256 + uint16(packet[43])
if serviceNet.EnsureListener([16]byte(packet[24:40]), port) {
count, err := (*serviceNet.Tun).Write([][]byte{packet}, 0)
if count == 0 {
fmt.Printf("[!] To service network: %s\n", err)
}
}
}
continue
}
} else {
continue
}
var dst peer.ID
// Check route table for destination address.
route, found := cfg.FindRouteForIP(dstIP)
if found {
dst = route.Target.ID
go sendPacket(dst, packet, plen)
}
}
}
func sendPacket(dst peer.ID, packet []byte, plen int) {
// Check if we already have an open connection to the destination peer.
ms, ok := activeStreams[dst]
if ok {
if func() bool {
ms.Lock.Lock()
defer ms.Lock.Unlock()
// Write out the packet's length to the libp2p stream to ensure
// we know the full size of the packet at the other end.
err := binary.Write(*ms.Stream, binary.LittleEndian, uint16(plen))
if err == nil {
// Write the packet out to the libp2p stream.
// If everything succeeds continue on to the next packet.
_, err = (*ms.Stream).Write(packet[:plen])
if err == nil {
(*ms.Stream).SetWriteDeadline(time.Now().Add(25 * time.Second))
return true
}
}
// If we encounter an error when writing to a stream we should
// close that stream and delete it from the active stream map.
(*ms.Stream).Close()
delete(activeStreams, dst)
return false
}() {
return
}
}
stream, err := node.NewStream(ctx, dst, p2p.Protocol)
if err != nil {
fmt.Println("[!] Failed to open stream to " + dst.String() + ": " + err.Error())
go p2p.Rediscover()
return
}
stream.SetWriteDeadline(time.Now().Add(25 * time.Second))
// Write packet length
err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err != nil {
stream.Close()
return
}
// Write the packet
_, err = stream.Write(packet[:plen])
if err != nil {
stream.Close()
return
}
// If all succeeds when writing the packet to the stream
// we should reuse this stream by adding it active streams map.
activeStreams[dst] = MuxStream{
Stream: &stream,
Lock: &sync.Mutex{},
}
}
func signalHandler(ctx context.Context, host host.Host, lockPath string, dht *dht.IpfsDHT) {
exitCh := make(chan os.Signal, 1)
rebootstrapCh := make(chan os.Signal, 1)
signal.Notify(exitCh, syscall.SIGINT, syscall.SIGTERM)
signal.Notify(rebootstrapCh, syscall.SIGUSR1)
for {
select {
case <-ctx.Done():
return
case <-rebootstrapCh:
fmt.Println("[-] Re-bootstrapping on SIGUSR1")
host.ConnManager().TrimOpenConns(context.Background())
<-dht.ForceRefresh()
p2p.Rediscover()
case <-exitCh:
// Shut the node down
err := host.Close()
checkErr(err)
// Remove daemon lock from file system.
err = os.Remove(lockPath)
checkErr(err)
fmt.Println("Received signal, shutting down...")
tunDev.Iface.Close()
tunDev.Down()
ctxCancel()
}
}
}
func eventLogger(ctx context.Context, host host.Host) {
subCon, err := host.EventBus().Subscribe(new(event.EvtPeerConnectednessChanged))
checkErr(err)
for {
select {
case <-ctx.Done():
return
case ev := <-subCon.Out():
evt := ev.(event.EvtPeerConnectednessChanged)
for _, vpnPeer := range cfg.Peers {
if vpnPeer.ID == evt.Peer {
if evt.Connectedness == network.Connected {
for _, c := range host.Network().ConnsToPeer(evt.Peer) {
fmt.Printf("[+] Connected to %s/p2p/%s\n", c.RemoteMultiaddr().String(), evt.Peer.String())
}
} else if evt.Connectedness == network.NotConnected {
fmt.Printf("[!] Disconnected from %s\n", evt.Peer.String())
}
break
}
}
}
}
}
func streamHandler(stream network.Stream) {
// If the remote node ID isn't in the list of known nodes don't respond.
if _, ok := config.FindPeer(cfg.Peers, stream.Conn().RemotePeer()); !ok {
stream.Reset()
return
}
var packet = make([]byte, 1420)
var packetSize = make([]byte, 2)
for {
// Read the incoming packet's size as a binary value.
_, err := stream.Read(packetSize)
if err != nil {
stream.Close()
return
}
// Decode the incoming packet's size from binary.
size := binary.LittleEndian.Uint16(packetSize)
// Read in the packet until completion.
var plen uint16 = 0
for plen < size {
tmp, err := stream.Read(packet[plen:size])
plen += uint16(tmp)
if err != nil {
stream.Close()
return
}
}
stream.SetWriteDeadline(time.Now().Add(25 * time.Second))
tunDev.Iface.Write(packet[:size])
}
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}

7
pkg/hyprspace/vpn.go Normal file
View File

@@ -0,0 +1,7 @@
package hyprspace
type VPN struct{}
func New() *VPN {
return &VPN{}
}

View File

@@ -83,6 +83,7 @@ type PlatformConfig[T any, S StreamProfile] struct {
Enable *bool
Config T
StreamProfiles StreamProfiles[S]
Custom map[string]any
}
type ProfileName string
@@ -91,6 +92,35 @@ func (cfg PlatformConfig[T, S]) GetStreamProfile(name ProfileName) (S, bool) {
return cfg.StreamProfiles.Get(name)
}
func (cfg *PlatformConfig[T, S]) SetCustomString(key string, value any) bool {
if cfg == nil {
return false
}
if cfg.Custom == nil {
cfg.Custom = map[string]any{}
}
cfg.Custom[key] = value
return true
}
func (cfg *PlatformConfig[T, S]) GetCustomString(key string) (string, bool) {
if cfg == nil {
return "", false
}
if cfg.Custom == nil {
return "", false
}
v, ok := cfg.Custom[key]
if !ok {
return "", false
}
s, ok := v.(string)
if !ok {
return "", false
}
return s, true
}
type AbstractPlatformConfig = PlatformConfig[any, AbstractStreamProfile]
type RawMessage json.RawMessage
@@ -162,6 +192,7 @@ func (cfg *Config) UnmarshalYAML(b []byte) error {
(*cfg)[k] = &PlatformConfig[any, AbstractStreamProfile]{
Config: &RawMessage{},
StreamProfiles: make(StreamProfiles[AbstractStreamProfile]),
Custom: map[string]any{},
}
vOrig = (*cfg)[k]
} else {
@@ -223,6 +254,7 @@ func ToAbstractPlatformConfig[T any, S StreamProfile](
Enable: platCfg.Enable,
Config: platCfg.Config,
StreamProfiles: ToAbstractStreamProfiles[S](platCfg.StreamProfiles),
Custom: platCfg.Custom,
}
}
@@ -234,6 +266,7 @@ func ConvertPlatformConfig[T any, S StreamProfile](
Enable: platCfg.Enable,
Config: GetPlatformSpecificConfig[T](ctx, platCfg.Config),
StreamProfiles: GetStreamProfiles[S](platCfg.StreamProfiles),
Custom: platCfg.Custom,
}
}
@@ -291,6 +324,7 @@ func InitConfig[T any, S StreamProfile](cfg Config, id PlatformName, platCfg Pla
}
cfg[id] = &PlatformConfig[any, AbstractStreamProfile]{
Config: &platCfg.Config,
Custom: map[string]any{},
}
}

View File

@@ -202,7 +202,7 @@ func (obs *OBS) GetStreamStatus(
var startedAt *time.Time
if streamStatus.OutputActive {
startedAt = ptr(time.Now().Add(time.Duration(streamStatus.OutputDuration * float64(time.Second))))
startedAt = ptr(time.Now().Add(-time.Duration(streamStatus.OutputDuration * float64(time.Millisecond))))
}
return &streamcontrol.StreamStatus{

View File

@@ -289,7 +289,7 @@ func getClient(
options := &helix.Options{
ClientID: cfg.Config.ClientID,
ClientSecret: cfg.Config.ClientSecret,
RedirectURI: "http://localhost:8091/",
RedirectURI: "http://0.0.0.0:8091/", // TODO: make this secure and also random
}
client, err := helix.NewClient(options)
if err != nil {

View File

@@ -4,12 +4,14 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"sort"
"strconv"
"sync"
"time"
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
@@ -27,8 +29,11 @@ import (
const copyThumbnail = false
type YouTube struct {
locker sync.Mutex
Config Config
YouTubeService *youtube.Service
CancelFunc context.CancelFunc
SaveConfigFunc func(Config) error
}
var _ streamcontrol.StreamController[StreamProfile] = (*YouTube)(nil)
@@ -36,7 +41,7 @@ var _ streamcontrol.StreamController[StreamProfile] = (*YouTube)(nil)
func New(
ctx context.Context,
cfg Config,
safeCfgFn func(Config) error,
saveCfgFn func(Config) error,
) (*YouTube, error) {
if cfg.Config.ClientID == "" || cfg.Config.ClientSecret == "" {
return nil, fmt.Errorf("'clientid' or/and 'clientsecret' is/are not set; go to https://console.cloud.google.com/apis/credentials and create an app if it not created, yet")
@@ -44,73 +49,22 @@ func New(
ctx, cancelFn := context.WithCancel(ctx)
isNewToken := false
getNewToken := func() error {
t, err := getToken(ctx, cfg)
if err != nil {
return fmt.Errorf("unable to get an access token: %w", err)
}
cfg.Config.Token = t
err = safeCfgFn(cfg)
errmon.ObserveErrorCtx(ctx, err)
isNewToken = true
return nil
}
if cfg.Config.Token == nil {
err := getNewToken()
if err != nil {
cancelFn()
return nil, err
}
}
tokenSource := getAuthCfg(cfg).TokenSource(ctx, cfg.Config.Token)
checkToken := func() error {
logger.Debugf(ctx, "checking if the token changed")
token, err := tokenSource.Token()
if err != nil {
logger.Errorf(ctx, "unable to get a token: %v", err)
return err
}
if token.AccessToken == cfg.Config.Token.AccessToken {
logger.Debugf(ctx, "the token have not change")
return err
}
logger.Debugf(ctx, "the token have changed")
cfg.Config.Token = token
return safeCfgFn(cfg)
}
if !isNewToken {
if err := checkToken(); err != nil {
logger.Errorf(ctx, "unable to get a token: %v", err)
err := getNewToken()
if err != nil {
cancelFn()
return nil, err
}
tokenSource = getAuthCfg(cfg).TokenSource(ctx, cfg.Config.Token)
}
}
if err := checkToken(); err != nil {
cancelFn()
return nil, fmt.Errorf("the token is invalid: %w", err)
}
youtubeService, err := youtube.NewService(ctx, option.WithTokenSource(tokenSource))
if err != nil {
cancelFn()
return nil, err
}
yt := &YouTube{
YouTubeService: youtubeService,
Config: cfg,
SaveConfigFunc: saveCfgFn,
CancelFunc: cancelFn,
}
err := yt.init(ctx)
if err != nil {
return nil, fmt.Errorf("initialization failed: %w", err)
}
err = yt.Ping(ctx)
if err != nil {
return nil, fmt.Errorf("connection verification failed: %w", err)
}
go func() {
ticker := time.NewTicker(time.Minute)
for {
@@ -118,7 +72,7 @@ func New(
case <-ctx.Done():
return
case <-ticker.C:
err := checkToken()
err := yt.checkToken(ctx)
errmon.ObserveErrorCtx(ctx, err)
}
}
@@ -127,12 +81,106 @@ func New(
return yt, nil
}
func (yt *YouTube) checkToken(ctx context.Context) (_err error) {
logger.Debugf(ctx, "YouTube.checkToken")
defer func() { logger.Debugf(ctx, "/YouTube.checkToken: %v", _err) }()
yt.locker.Lock()
defer yt.locker.Unlock()
return yt.checkTokenNoLock(ctx)
}
func (yt *YouTube) checkTokenNoLock(ctx context.Context) (_err error) {
logger.Debugf(ctx, "YouTube.checkTokenNoLock")
defer func() { logger.Debugf(ctx, "/YouTube.checkTokenNoLock: %v", _err) }()
tokenSource := getAuthCfg(yt.Config).TokenSource(ctx, yt.Config.Config.Token)
logger.Debugf(ctx, "checking if the token changed")
token, err := tokenSource.Token()
if err != nil {
logger.Errorf(ctx, "unable to get a token: %v", err)
return err
}
if token.AccessToken == yt.Config.Config.Token.AccessToken {
logger.Debugf(ctx, "the token have not change")
return err
}
logger.Debugf(ctx, "the token have changed")
yt.Config.Config.Token = token
return yt.SaveConfigFunc(yt.Config)
}
func (yt *YouTube) getNewToken(ctx context.Context) (_ret *oauth2.Token, _err error) {
logger.Debugf(ctx, "YouTube.getNewToken")
defer func() { logger.Debugf(ctx, "/YouTube.getNewToken: %v", _err) }()
t, err := getToken(ctx, yt.Config)
if err != nil {
return nil, fmt.Errorf("unable to get an access token: %w", err)
}
yt.Config.Config.Token = t
err = yt.SaveConfigFunc(yt.Config)
errmon.ObserveErrorCtx(ctx, err)
return t, nil
}
func (yt *YouTube) init(ctx context.Context) (_err error) {
logger.Debugf(ctx, "YouTube.init")
defer func() { logger.Debugf(ctx, "/YouTube.init: %v", _err) }()
yt.locker.Lock()
defer yt.locker.Unlock()
logger.Debugf(ctx, "YouTube.init: Lock()-ed")
defer func() { logger.Debugf(ctx, "/YouTube.init: UnLock()-ed") }()
isNewToken := false
if yt.Config.Config.Token == nil {
_, err := yt.getNewToken(ctx)
if err != nil {
yt.CancelFunc()
return err
}
isNewToken = true
}
tokenSource := getAuthCfg(yt.Config).TokenSource(ctx, yt.Config.Config.Token)
if !isNewToken {
if err := yt.checkTokenNoLock(ctx); err != nil {
logger.Errorf(ctx, "unable to get a token: %v", err)
_, err := yt.getNewToken(ctx)
if err != nil {
yt.CancelFunc()
return err
}
isNewToken = true
tokenSource = getAuthCfg(yt.Config).TokenSource(ctx, yt.Config.Config.Token)
}
}
if err := yt.checkTokenNoLock(ctx); err != nil {
yt.CancelFunc()
return fmt.Errorf("the token is invalid: %w", err)
}
youtubeService, err := youtube.NewService(ctx, option.WithTokenSource(tokenSource))
if err != nil {
yt.CancelFunc()
return err
}
yt.YouTubeService = youtubeService // TODO: make this atomic
return nil
}
func getAuthCfg(cfg Config) *oauth2.Config {
return &oauth2.Config{
ClientID: cfg.Config.ClientID,
ClientSecret: cfg.Config.ClientSecret,
Endpoint: google.Endpoint,
RedirectURL: "http://localhost:8090",
RedirectURL: "http://0.0.0.0:8090", // TODO: make this secure and also random
Scopes: []string{
"https://www.googleapis.com/auth/youtube",
},
@@ -168,6 +216,20 @@ func getToken(ctx context.Context, cfg Config) (*oauth2.Token, error) {
return tok, nil
}
func (yt *YouTube) Ping(ctx context.Context) error {
counter := 0
for {
_, err := yt.YouTubeService.I18nLanguages.List(nil).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.I18nLanguages result: %v", err)
if err != nil {
if yt.fixError(ctx, err, &counter) {
continue
}
}
return err
}
}
func (yt *YouTube) Close() error {
yt.CancelFunc()
return nil
@@ -178,13 +240,23 @@ func (yt *YouTube) IterateUpcomingBroadcasts(
callback func(broadcast *youtube.LiveBroadcast) error,
parts ...string,
) error {
broadcasts, err := yt.YouTubeService.LiveBroadcasts.
var broadcasts *youtube.LiveBroadcastListResponse
counter := 0
for {
var err error
broadcasts, err = yt.YouTubeService.LiveBroadcasts.
List(append([]string{"id"}, parts...)).
BroadcastStatus("upcoming").
Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
if yt.fixError(ctx, err, &counter) {
continue
}
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
break
}
for _, broadcast := range broadcasts.Items {
if err := callback(broadcast); err != nil {
@@ -199,14 +271,23 @@ func (yt *YouTube) IterateActiveBroadcasts(
callback func(broadcast *youtube.LiveBroadcast) error,
parts ...string,
) error {
broadcasts, err := yt.YouTubeService.LiveBroadcasts.
var broadcasts *youtube.LiveBroadcastListResponse
counter := 0
for {
var err error
broadcasts, err = yt.YouTubeService.LiveBroadcasts.
List(append([]string{"id"}, parts...)).
BroadcastStatus("active").
Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
if yt.fixError(ctx, err, &counter) {
continue
}
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
break
}
for _, broadcast := range broadcasts.Items {
if err := callback(broadcast); err != nil {
return fmt.Errorf("got an error with broadcast %v: %w", broadcast.Id, err)
@@ -225,6 +306,7 @@ func (yt *YouTube) updateActiveBroadcasts(
return fmt.Errorf("unable to update broadcast %v: %w", broadcast.Id, err)
}
_, err := yt.YouTubeService.LiveBroadcasts.Update(parts, broadcast).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
return fmt.Errorf("unable to update broadcast %v: %w", broadcast.Id, err)
}
@@ -283,6 +365,7 @@ func (yt *YouTube) InsertAdsCuePoint(
DurationSecs: int64(duration.Seconds()),
WalltimeMs: uint64(ts.UnixMilli()),
}).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
return err
})
}
@@ -292,7 +375,9 @@ func (yt *YouTube) DeleteActiveBroadcasts(
) error {
return yt.IterateActiveBroadcasts(ctx, func(broadcast *youtube.LiveBroadcast) error {
logger.Debugf(ctx, "deleting broadcast %v", broadcast.Id)
return yt.YouTubeService.LiveBroadcasts.Delete(broadcast.Id).Context(ctx).Do()
err := yt.YouTubeService.LiveBroadcasts.Delete(broadcast.Id).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
return err
})
}
@@ -347,6 +432,9 @@ func (yt *YouTube) StartStream(
profile StreamProfile,
customArgs ...any,
) error {
if err := yt.Ping(ctx); err != nil {
return fmt.Errorf("connection to YouTube is broken: %w", err)
}
var templateBroadcastIDs []string
for _, templateBroadcastIDCandidate := range customArgs {
_templateBroadcastIDs, ok := templateBroadcastIDCandidate.(FlagBroadcastTemplateIDs)
@@ -371,7 +459,9 @@ func (yt *YouTube) StartStream(
return nil
}
logger.Debugf(ctx, "deleting broadcast %v", broadcast.Id)
return yt.YouTubeService.LiveBroadcasts.Delete(broadcast.Id).Context(ctx).Do()
err := yt.YouTubeService.LiveBroadcasts.Delete(broadcast.Id).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
return err
})
if err != nil {
logger.Error(ctx, "unable to delete other upcoming streams: %w", err)
@@ -386,6 +476,7 @@ func (yt *YouTube) StartStream(
List(liveBroadcastParts).
Id(templateBroadcastIDs...).
Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
@@ -399,6 +490,7 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "getting video info of %v", templateBroadcastIDs)
response, err := yt.YouTubeService.Videos.List(videoParts).Id(templateBroadcastIDs...).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.Video result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
@@ -409,6 +501,7 @@ func (yt *YouTube) StartStream(
}
playlistsResponse, err := yt.YouTubeService.Playlists.List(playlistParts).MaxResults(1000).Mine(true).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.Playlists result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of playlists: %w", err)
}
@@ -419,6 +512,7 @@ func (yt *YouTube) StartStream(
for _, playlist := range playlistsResponse.Items {
playlistItemsResponse, err := yt.YouTubeService.PlaylistItems.List(playlistItemParts).MaxResults(1000).PlaylistId(playlist.Id).VideoId(templateBroadcastID).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.PlaylistItems result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of playlist items: %w", err)
}
@@ -440,6 +534,7 @@ func (yt *YouTube) StartStream(
var highestStreamNum uint64
if profile.AutoNumerate {
resp, err := yt.YouTubeService.LiveBroadcasts.List(liveBroadcastParts).Context(ctx).Mine(true).MaxResults(100).Fields().Do(googleapi.QueryParameter("order", "date"))
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
return fmt.Errorf("unable to request previous streams to figure out the next stream number for auto-numeration: %w", err)
}
@@ -499,6 +594,7 @@ func (yt *YouTube) StartStream(
[]string{"snippet", "contentDetails", "monetizationDetails", "status"},
broadcast,
).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
return fmt.Errorf("unable to create a broadcast: %w", err)
}
@@ -516,6 +612,7 @@ func (yt *YouTube) StartStream(
logger.Tracef(ctx, "updating video data to %#+v", broadcast)
}
_, err = yt.YouTubeService.Videos.Update(videoParts, video).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.Update result: %v", err)
if err != nil {
return fmt.Errorf("unable to update video data: %w", err)
}
@@ -543,6 +640,7 @@ func (yt *YouTube) StartStream(
}
_, err = yt.YouTubeService.PlaylistItems.Insert(playlistItemParts, newPlaylistItem).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.PlaylistItems result: %v", err)
if err != nil {
return fmt.Errorf("unable to add video to playlist %#+v: %w", playlistID, err)
}
@@ -562,6 +660,7 @@ func (yt *YouTube) StartStream(
}
logger.Debugf(ctx, "setting the thumbnail")
_, err = yt.YouTubeService.Thumbnails.Set(newBroadcast.Id).Media(bytes.NewReader(thumbnail)).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.Thumbnails result: %v", err)
if err != nil {
return fmt.Errorf("unable to set the thumbnail: %w", err)
}
@@ -593,13 +692,18 @@ func (yt *YouTube) EndStream(
}
const timeLayout = "2006-01-02T15:04:05-0700"
const timeLayoutFallback = time.RFC3339
func (yt *YouTube) GetStreamStatus(
ctx context.Context,
) (_ret *streamcontrol.StreamStatus, _err error) {
logger.Tracef(ctx, "GetStreamStatus")
defer func() {
logger.Tracef(ctx, "GetStreamStatus: err:%v; ret:%#+v", _err, _ret)
logger.Tracef(ctx, "/GetStreamStatus: err:%v; ret:%#+v", _err, _ret)
}()
if err := yt.Ping(ctx); err != nil {
return nil, fmt.Errorf("connection to YouTube is broken: %w", err)
}
var activeBroadcasts []*youtube.LiveBroadcast
var startedAt *time.Time
isActive := false
@@ -607,10 +711,14 @@ func (yt *YouTube) GetStreamStatus(
ts := broadcast.Snippet.ActualStartTime
_startedAt, err := time.Parse(timeLayout, ts)
if err != nil {
return fmt.Errorf("unable to parse '%s' with layout '%s': %w", ts, timeLayout, err)
_startedAt, err = time.Parse(timeLayoutFallback, ts)
if err != nil {
return fmt.Errorf("unable to parse '%s' with layouts '%s' and '%s': %w", ts, timeLayout, timeLayoutFallback, err)
}
}
startedAt = &_startedAt
activeBroadcasts = append(activeBroadcasts, broadcast)
isActive = true
return nil
}, liveBroadcastParts...)
if err != nil {
@@ -681,31 +789,73 @@ func (yt *YouTube) Flush(
func (yt *YouTube) ListStreams(
ctx context.Context,
) ([]*youtube.LiveStream, error) {
counter := 0
for {
response, err := yt.YouTubeService.
LiveStreams.
List([]string{"id", "snippet", "cdn", "status"}).
Mine(true).
MaxResults(20).
Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveStreams result: %v", err)
if err != nil {
if yt.fixError(ctx, err, &counter) {
continue
}
return nil, fmt.Errorf("unable to query the list of streams: %w", err)
}
return response.Items, nil
}
}
type LiveBroadcast = youtube.LiveBroadcast
func (yt *YouTube) ListBroadcasts(
ctx context.Context,
) ([]*youtube.LiveBroadcast, error) {
counter := 0
for {
response, err := yt.YouTubeService.
LiveBroadcasts.
List([]string{"id", "snippet", "contentDetails", "monetizationDetails", "status"}).
Mine(true).
MaxResults(1000).
Context(ctx).Do()
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
if yt.fixError(ctx, err, &counter) {
continue
}
return nil, fmt.Errorf("unable to query the list of broadcasts: %w", err)
}
return response.Items, nil
}
}
func (yt *YouTube) fixError(ctx context.Context, err error, counterPtr *int) bool {
if *counterPtr > 2 {
return false
}
*counterPtr++
gErr := &googleapi.Error{}
if !errors.As(err, &gErr) {
return false
}
if gErr.Code == 401 {
_, tErr := yt.getNewToken(ctx)
if tErr != nil {
logger.Errorf(ctx, "unable to get a new token: %w", err)
return false
}
iErr := yt.init(ctx)
if iErr != nil {
logger.Errorf(ctx, "unable to re-initialize the YouTube client: %w", err)
return false
}
return true
}
return false
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"time"
"github.com/facebookincubator/go-belt/tool/logger"
@@ -104,7 +105,7 @@ func (c *Client) GetConfig(ctx context.Context) (*config.Config, error) {
}
var result config.Config
err = config.ReadConfig(ctx, []byte(reply.Config), &result)
_, err = result.Read([]byte(reply.Config))
if err != nil {
return nil, fmt.Errorf("unable to unserialize the received config: %w", err)
}
@@ -113,7 +114,7 @@ func (c *Client) GetConfig(ctx context.Context) (*config.Config, error) {
func (c *Client) SetConfig(ctx context.Context, cfg *config.Config) error {
var buf bytes.Buffer
err := config.WriteConfig(ctx, &buf, *cfg)
_, err := cfg.WriteTo(&buf)
if err != nil {
return fmt.Errorf("unable to serialize the config: %w", err)
}
@@ -431,6 +432,40 @@ func (c *Client) UpdateStream(
return nil
}
func (c *Client) SubscriberToOAuthURLs(
ctx context.Context,
) (chan string, error) {
client, conn, err := c.grpcClient()
if err != nil {
return nil, err
}
result := make(chan string)
subClient, err := client.SubscribeToOAuthRequests(ctx, &streamd_grpc.SubscribeToOAuthRequestsRequest{})
if err != nil {
return nil, fmt.Errorf("unable to subscribe to oauth URLs: %w", err)
}
subClient.CloseSend()
go func() {
defer conn.Close()
defer func() {
close(result)
}()
for {
res, err := subClient.Recv()
if err == io.EOF {
return
}
result <- res.GetAuthURL()
}
}()
return result, nil
}
func ptr[T any](in T) *T {
return &in
}

View File

@@ -1,15 +1,11 @@
package config
import (
"bytes"
"context"
"fmt"
"io"
"os"
"github.com/facebookincubator/go-belt/tool/logger"
goyaml "github.com/go-yaml/yaml"
"github.com/goccy/go-yaml"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
@@ -29,17 +25,15 @@ type GitRepoConfig struct {
LatestSyncCommit string `yaml:"latest_sync_commit,omitempty"` // TODO: deprecate this field, it's just a non-needed mechanism (better to check against git history).
}
type Config struct {
type config struct {
CachePath *string `yaml:"cache_path"`
Commands struct {
OnStartStream string `yaml:"on_start_stream"`
OnStopStream string `yaml:"on_stop_stream"`
}
GitRepo GitRepoConfig
Backends streamcontrol.Config
ProfileMetadata map[streamcontrol.ProfileName]ProfileMetadata
}
type Config config
func NewConfig() Config {
cfg := streamcontrol.Config{}
obs.InitConfig(cfg)
@@ -72,103 +66,8 @@ func ReadConfigFromPath(
return fmt.Errorf("unable to read file '%s': %w", cfgPath, err)
}
return ReadConfig(ctx, b, cfg)
}
func ReadConfig(
ctx context.Context,
b []byte,
cfg *Config,
) error {
err := yaml.Unmarshal(b, cfg)
if err != nil {
return fmt.Errorf("unable to unserialize data: %w: <%s>", err, b)
}
if cfg.Backends == nil {
cfg.Backends = streamcontrol.Config{}
}
if cfg.Backends[obs.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[obs.StreamProfile](ctx, cfg.Backends[obs.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of OBS: %w: <%s>", err, b)
}
logger.Debugf(ctx, "final stream profiles of OBS: %#+v", cfg.Backends[obs.ID].StreamProfiles)
}
if cfg.Backends[twitch.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](ctx, cfg.Backends[twitch.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of twitch: %w: <%s>", err, b)
}
logger.Debugf(ctx, "final stream profiles of twitch: %#+v", cfg.Backends[twitch.ID].StreamProfiles)
}
if cfg.Backends[youtube.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](ctx, cfg.Backends[youtube.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of youtube: %w: <%s>", err, b)
}
logger.Debugf(ctx, "final stream profiles of youtube: %#+v", cfg.Backends[youtube.ID].StreamProfiles)
}
return nil
}
func WriteConfigToPath(
ctx context.Context,
cfgPath string,
cfg Config,
) error {
pathNew := cfgPath + ".new"
f, err := os.OpenFile(pathNew, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0750)
if err != nil {
return fmt.Errorf("unable to open the data file '%s': %w", pathNew, err)
}
err = WriteConfig(ctx, f, cfg)
f.Close()
if err != nil {
return fmt.Errorf("unable to write data to file '%s': %w", pathNew, err)
}
err = os.Rename(pathNew, cfgPath)
if err != nil {
return fmt.Errorf("cannot move '%s' to '%s': %w", pathNew, cfgPath, err)
}
logger.Infof(ctx, "wrote to '%s' config %#+v", cfgPath, cfg)
return nil
}
func WriteConfig(
_ context.Context,
w io.Writer,
cfg Config,
) error {
b, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("unable to serialize data %#+v: %w", cfg, err)
}
// have to use another YAML encoder to avoid the random-indent bug,
// but also have to use the initial encoder to correctly map
// out structures to YAML; so using both sequentially :(
m := map[string]any{}
err = goyaml.Unmarshal(b, &m)
if err != nil {
return fmt.Errorf("unable to unserialize data %#+v: %w", cfg, err)
}
b, err = goyaml.Marshal(m)
if err != nil {
return fmt.Errorf("unable to re-serialize data %#+v: %w", cfg, err)
}
_, err = io.Copy(w, bytes.NewBuffer(b))
if err != nil {
return fmt.Errorf("unable to write data %#+v: %w", cfg, err)
}
return nil
_, err = cfg.Read(b)
return err
}
func ReadOrCreateConfigFile(
@@ -196,3 +95,26 @@ func ReadOrCreateConfigFile(
return nil, fmt.Errorf("unable to access file '%s': %w", dataPath, err)
}
}
func WriteConfigToPath(
ctx context.Context,
cfgPath string,
cfg Config,
) error {
pathNew := cfgPath + ".new"
f, err := os.OpenFile(pathNew, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0750)
if err != nil {
return fmt.Errorf("unable to open the data file '%s': %w", pathNew, err)
}
_, err = cfg.WriteTo(f)
f.Close()
if err != nil {
return fmt.Errorf("unable to write data to file '%s': %w", pathNew, err)
}
err = os.Rename(pathNew, cfgPath)
if err != nil {
return fmt.Errorf("cannot move '%s' to '%s': %w", pathNew, cfgPath, err)
}
logger.Infof(ctx, "wrote to '%s' config %#+v", cfgPath, cfg)
return nil
}

View File

@@ -0,0 +1,69 @@
package config
import (
"context"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
)
var _ io.Reader = (*Config)(nil)
var _ io.ReaderFrom = (*Config)(nil)
var _ yaml.BytesUnmarshaler = (*Config)(nil)
func (cfg *Config) Read(
b []byte,
) (int, error) {
return len(b), cfg.UnmarshalYAML(b)
}
func (cfg *Config) UnmarshalYAML(b []byte) error {
err := yaml.Unmarshal(b, (*config)(cfg))
if err != nil {
return fmt.Errorf("unable to unserialize data: %w", err)
}
if cfg.Backends == nil {
cfg.Backends = streamcontrol.Config{}
}
if cfg.Backends[obs.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[obs.StreamProfile](context.Background(), cfg.Backends[obs.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of OBS: %w", err)
}
}
if cfg.Backends[twitch.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](context.Background(), cfg.Backends[twitch.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of twitch: %w", err)
}
}
if cfg.Backends[youtube.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](context.Background(), cfg.Backends[youtube.ID].StreamProfiles)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of youtube: %w", err)
}
}
return nil
}
func (cfg *Config) ReadFrom(
r io.Reader,
) (int64, error) {
b, err := io.ReadAll(r)
if err != nil {
return int64(len(b)), fmt.Errorf("unable to read: %w", err)
}
n, err := cfg.Read(b)
return int64(n), err
}

View File

@@ -0,0 +1,57 @@
package config
import (
"bytes"
"fmt"
"io"
goyaml "github.com/go-yaml/yaml"
"github.com/goccy/go-yaml"
"github.com/xaionaro-go/datacounter"
)
var _ io.Writer = (*Config)(nil)
var _ io.WriterTo = (*Config)(nil)
var _ yaml.BytesMarshaler = (*Config)(nil)
func (cfg Config) Write(b []byte) (int, error) {
n, err := cfg.WriteTo(bytes.NewBuffer(b))
return int(n), err
}
func (cfg Config) WriteTo(
w io.Writer,
) (int64, error) {
b, err := cfg.MarshalYAML()
if err != nil {
return 0, err
}
counter := datacounter.NewWriterCounter(w)
io.Copy(counter, bytes.NewReader(b))
return int64(counter.Count()), nil
}
func (cfg Config) MarshalYAML() ([]byte, error) {
b, err := yaml.Marshal((config)(cfg))
if err != nil {
return nil, fmt.Errorf("unable to serialize data %#+v: %w", cfg, err)
}
// have to use another YAML encoder to avoid the random-indent bug,
// but also have to use the initial encoder to correctly map
// out structures to YAML; so using both sequentially :(
m := map[string]any{}
err = goyaml.Unmarshal(b, &m)
if err != nil {
return nil, fmt.Errorf("unable to unserialize data %#+v: %w", cfg, err)
}
b, err = goyaml.Marshal(m)
if err != nil {
return nil, fmt.Errorf("unable to re-serialize data %#+v: %w", cfg, err)
}
return b, nil
}

View File

@@ -31,7 +31,7 @@ func (d *StreamD) sendConfigViaGIT(
var newBytes bytes.Buffer
latestSyncCommit := d.Config.GitRepo.LatestSyncCommit
d.Config.GitRepo.LatestSyncCommit = ""
err := config.WriteConfig(ctx, &newBytes, d.Config)
_, err := d.Config.WriteTo(&newBytes)
d.Config.GitRepo.LatestSyncCommit = latestSyncCommit
if err != nil {
return fmt.Errorf("unable to serialize the panel data: %w", err)
@@ -47,7 +47,7 @@ func (d *StreamD) sendConfigViaGIT(
}
if !hash.IsZero() {
d.setLastKnownGitCommitHash(hash)
if err := d.saveDataToConfigFile(ctx); err != nil {
if err := d.SaveConfigFunc(ctx, d.Config); err != nil {
return fmt.Errorf("unable to store the new commit hash in the config file: %w", err)
}
}
@@ -68,7 +68,7 @@ func (d *StreamD) gitSync(ctx context.Context) {
d.getLastKnownGitCommitHash(),
func(ctx context.Context, commitHash plumbing.Hash, b []byte) {
panelData := config.NewConfig()
err := config.ReadConfig(ctx, b, &panelData)
_, err := panelData.Read(b)
if err != nil {
d.UI.DisplayError(fmt.Errorf("unable to read panel data: %w", err))
return

View File

@@ -618,6 +618,7 @@ type GetStreamStatusRequest struct {
unknownFields protoimpl.UnknownFields
PlatID string `protobuf:"bytes,1,opt,name=platID,proto3" json:"platID,omitempty"`
NoCache bool `protobuf:"varint,2,opt,name=noCache,proto3" json:"noCache,omitempty"`
}
func (x *GetStreamStatusRequest) Reset() {
@@ -659,6 +660,13 @@ func (x *GetStreamStatusRequest) GetPlatID() string {
return ""
}
func (x *GetStreamStatusRequest) GetNoCache() bool {
if x != nil {
return x.NoCache
}
return false
}
type GetStreamStatusReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1601,6 +1609,91 @@ func (*OBSOLETE_GitReloginReply) Descriptor() ([]byte, []int) {
return file_streamd_proto_rawDescGZIP(), []int{35}
}
type SubscribeToOAuthRequestsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribeToOAuthRequestsRequest) Reset() {
*x = SubscribeToOAuthRequestsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_streamd_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeToOAuthRequestsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeToOAuthRequestsRequest) ProtoMessage() {}
func (x *SubscribeToOAuthRequestsRequest) ProtoReflect() protoreflect.Message {
mi := &file_streamd_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeToOAuthRequestsRequest.ProtoReflect.Descriptor instead.
func (*SubscribeToOAuthRequestsRequest) Descriptor() ([]byte, []int) {
return file_streamd_proto_rawDescGZIP(), []int{36}
}
type OAuthRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AuthURL string `protobuf:"bytes,1,opt,name=authURL,proto3" json:"authURL,omitempty"`
}
func (x *OAuthRequest) Reset() {
*x = OAuthRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_streamd_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OAuthRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OAuthRequest) ProtoMessage() {}
func (x *OAuthRequest) ProtoReflect() protoreflect.Message {
mi := &file_streamd_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OAuthRequest.ProtoReflect.Descriptor instead.
func (*OAuthRequest) Descriptor() ([]byte, []int) {
return file_streamd_proto_rawDescGZIP(), []int{37}
}
func (x *OAuthRequest) GetAuthURL() string {
if x != nil {
return x.AuthURL
}
return ""
}
var File_streamd_proto protoreflect.FileDescriptor
var file_streamd_proto_rawDesc = []byte{
@@ -1633,150 +1726,162 @@ var file_streamd_proto_rawDesc = []byte{
0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61,
0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49,
0x44, 0x22, 0x10, 0x0a, 0x0e, 0x45, 0x6e, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x70, 0x6c, 0x79, 0x22, 0x4a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a,
0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
0x6c, 0x61, 0x74, 0x49, 0x44, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a,
0x0a, 0x08, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x08, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x74,
0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00,
0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1e,
0x0a, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0d,
0x0a, 0x0b, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x22, 0x2f, 0x0a,
0x15, 0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x6f, 0x43, 0x61, 0x63, 0x68, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e, 0x6f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x22,
0x85, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x41, 0x63,
0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x41, 0x63,
0x74, 0x69, 0x76, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f,
0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72,
0x74, 0x65, 0x64, 0x41, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x74, 0x61,
0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x22, 0x2f, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x42, 0x61,
0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x22, 0x4f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42,
0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12,
0x24, 0x0a, 0x0d, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x10, 0x0a, 0x0e, 0x52, 0x65, 0x73,
0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x52,
0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x3f, 0x0a, 0x0f, 0x53,
0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16,
0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x0f, 0x0a, 0x0d,
0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x51, 0x0a,
0x15, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x22, 0x4f,
0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f,
0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69,
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73,
0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
0x10, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x3f, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05,
0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74,
0x6c, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x51, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c,
0x61, 0x74, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x4a, 0x0a,
0x16, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49,
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x65, 0x74,
0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x7f, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74,
0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44,
0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x2d, 0x0a, 0x2b, 0x45, 0x58, 0x50, 0x45, 0x52,
0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2b, 0x0a, 0x29, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49,
0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x1d, 0x0a, 0x1b, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f,
0x46, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x1b, 0x0a, 0x19, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46,
0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x1c, 0x0a, 0x1a, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47,
0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x40, 0x0a,
0x18, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47, 0x69, 0x74,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x73, 0x49,
0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0d, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x22,
0x1c, 0x0a, 0x1a, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52,
0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1a, 0x0a,
0x18, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c,
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x99, 0x09, 0x0a, 0x07, 0x53, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x44, 0x12, 0x31, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x12, 0x11, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x11, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x0a, 0x53,
0x61, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x2e, 0x53, 0x61, 0x76, 0x65,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e,
0x53, 0x61, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x12, 0x34, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12,
0x12, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65,
0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x43,
0x61, 0x63, 0x68, 0x65, 0x12, 0x11, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x61,
0x63, 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0b, 0x53, 0x74,
0x61, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x13, 0x2e, 0x53, 0x74, 0x61, 0x72,
0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11,
0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x45, 0x6e, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x12, 0x11, 0x2e, 0x45, 0x6e, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x45, 0x6e, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52,
0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x2e, 0x47, 0x65, 0x74, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0e, 0x47,
0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e,
0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65,
0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2b, 0x0a,
0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x0f, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61,
0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x52, 0x65, 0x73, 0x74,
0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x53, 0x65,
0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x10, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69,
0x74, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0e, 0x53, 0x65,
0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x53,
0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0f,
0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12,
0x17, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x70,
0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x12, 0x14, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x82, 0x01,
0x0a, 0x24, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52,
0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d,
0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e,
0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x52, 0x0a, 0x14, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46,
0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x2e, 0x4f, 0x42, 0x53,
0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c,
0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x10, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45,
0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x2e, 0x4f, 0x42, 0x53,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x20,
0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x4a, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x41, 0x70,
0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x7f, 0x0a, 0x13, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x74, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69,
0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65,
0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x13, 0x0a, 0x11,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x2d, 0x0a, 0x2b, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41,
0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0x2b, 0x0a, 0x29, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c,
0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x0a,
0x1b, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1b, 0x0a, 0x19,
0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x42, 0x53,
0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45,
0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x13, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45,
0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x4f, 0x42,
0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x40, 0x0a, 0x18, 0x4f, 0x42, 0x53, 0x4f, 0x4c,
0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c,
0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x49, 0x6e,
0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x42, 0x53,
0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x4f, 0x42, 0x53, 0x4f, 0x4c,
0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x6f, 0x2f, 0x73, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x64, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x70, 0x6c, 0x79, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x54, 0x6f, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0c, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x75, 0x74, 0x68, 0x55, 0x52,
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x75, 0x74, 0x68, 0x55, 0x52, 0x4c,
0x32, 0xea, 0x09, 0x0a, 0x07, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x12, 0x31, 0x0a, 0x09,
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x11, 0x2e, 0x47, 0x65, 0x74, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x47,
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12,
0x31, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x11, 0x2e, 0x53,
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x0f, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x34, 0x0a, 0x0a, 0x53, 0x61, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x12, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x65,
0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x12, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, 0x61,
0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x52, 0x65, 0x73,
0x65, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x31,
0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x11, 0x2e, 0x49, 0x6e,
0x69, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f,
0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x12, 0x37, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x12, 0x13, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x45, 0x6e,
0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x11, 0x2e, 0x45, 0x6e, 0x64, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x45, 0x6e, 0x64,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x43, 0x0a,
0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x17, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x40, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e,
0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x47,
0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12,
0x0f, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x0d, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x12, 0x2e, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x10, 0x2e,
0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x0e, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x12, 0x40, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x53, 0x65,
0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c,
0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x15, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x14, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x00, 0x12, 0x82, 0x01, 0x0a, 0x24, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d,
0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e,
0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69,
0x6e, 0x69, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x45, 0x58,
0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x52, 0x65, 0x69, 0x6e, 0x69,
0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x14, 0x4f, 0x42, 0x53,
0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x12, 0x1c, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74,
0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1a, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x65, 0x74, 0x63, 0x68,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a,
0x10, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x12, 0x1b, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74,
0x47, 0x69, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x65, 0x74, 0x47, 0x69, 0x74,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x13, 0x4f,
0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67,
0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69,
0x74, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x19, 0x2e, 0x4f, 0x42, 0x53, 0x4f, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x47, 0x69, 0x74, 0x52, 0x65,
0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x18,
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x6f, 0x4f, 0x41, 0x75, 0x74, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x20, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x62, 0x65, 0x54, 0x6f, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x4f, 0x41, 0x75,
0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x00, 0x30, 0x01, 0x42, 0x11, 0x5a,
0x0f, 0x67, 0x6f, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x64, 0x5f, 0x67, 0x72, 0x70, 0x63,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1791,7 +1896,7 @@ func file_streamd_proto_rawDescGZIP() []byte {
return file_streamd_proto_rawDescData
}
var file_streamd_proto_msgTypes = make([]protoimpl.MessageInfo, 36)
var file_streamd_proto_msgTypes = make([]protoimpl.MessageInfo, 38)
var file_streamd_proto_goTypes = []interface{}{
(*GetConfigRequest)(nil), // 0: GetConfigRequest
(*GetConfigReply)(nil), // 1: GetConfigReply
@@ -1829,6 +1934,8 @@ var file_streamd_proto_goTypes = []interface{}{
(*OBSOLETE_GetGitInfoReply)(nil), // 33: OBSOLETE_GetGitInfoReply
(*OBSOLETE_GitReloginRequest)(nil), // 34: OBSOLETE_GitReloginRequest
(*OBSOLETE_GitReloginReply)(nil), // 35: OBSOLETE_GitReloginReply
(*SubscribeToOAuthRequestsRequest)(nil), // 36: SubscribeToOAuthRequestsRequest
(*OAuthRequest)(nil), // 37: OAuthRequest
}
var file_streamd_proto_depIdxs = []int32{
0, // 0: StreamD.GetConfig:input_type -> GetConfigRequest
@@ -1849,26 +1956,28 @@ var file_streamd_proto_depIdxs = []int32{
30, // 15: StreamD.OBSOLETE_FetchConfig:input_type -> OBSOLETE_FetchConfigRequest
32, // 16: StreamD.OBSOLETE_GitInfo:input_type -> OBSOLETE_GetGitInfoRequest
34, // 17: StreamD.OBSOLETE_GitRelogin:input_type -> OBSOLETE_GitReloginRequest
1, // 18: StreamD.GetConfig:output_type -> GetConfigReply
3, // 19: StreamD.SetConfig:output_type -> SetConfigReply
5, // 20: StreamD.SaveConfig:output_type -> SaveConfigReply
7, // 21: StreamD.ResetCache:output_type -> ResetCacheReply
9, // 22: StreamD.InitCache:output_type -> InitCacheReply
11, // 23: StreamD.StartStream:output_type -> StartStreamReply
13, // 24: StreamD.EndStream:output_type -> EndStreamReply
15, // 25: StreamD.GetStreamStatus:output_type -> GetStreamStatusReply
17, // 26: StreamD.GetBackendInfo:output_type -> GetBackendInfoReply
19, // 27: StreamD.Restart:output_type -> RestartReply
21, // 28: StreamD.SetTitle:output_type -> SetTitleReply
23, // 29: StreamD.SetDescription:output_type -> SetDescriptionReply
25, // 30: StreamD.SetApplyProfile:output_type -> SetApplyProfileReply
27, // 31: StreamD.UpdateStream:output_type -> UpdateStreamReply
29, // 32: StreamD.EXPERIMENTAL_ReinitStreamControllers:output_type -> EXPERIMENTAL_ReinitStreamControllersReply
31, // 33: StreamD.OBSOLETE_FetchConfig:output_type -> OBSOLETE_FetchConfigReply
33, // 34: StreamD.OBSOLETE_GitInfo:output_type -> OBSOLETE_GetGitInfoReply
35, // 35: StreamD.OBSOLETE_GitRelogin:output_type -> OBSOLETE_GitReloginReply
18, // [18:36] is the sub-list for method output_type
0, // [0:18] is the sub-list for method input_type
36, // 18: StreamD.SubscribeToOAuthRequests:input_type -> SubscribeToOAuthRequestsRequest
1, // 19: StreamD.GetConfig:output_type -> GetConfigReply
3, // 20: StreamD.SetConfig:output_type -> SetConfigReply
5, // 21: StreamD.SaveConfig:output_type -> SaveConfigReply
7, // 22: StreamD.ResetCache:output_type -> ResetCacheReply
9, // 23: StreamD.InitCache:output_type -> InitCacheReply
11, // 24: StreamD.StartStream:output_type -> StartStreamReply
13, // 25: StreamD.EndStream:output_type -> EndStreamReply
15, // 26: StreamD.GetStreamStatus:output_type -> GetStreamStatusReply
17, // 27: StreamD.GetBackendInfo:output_type -> GetBackendInfoReply
19, // 28: StreamD.Restart:output_type -> RestartReply
21, // 29: StreamD.SetTitle:output_type -> SetTitleReply
23, // 30: StreamD.SetDescription:output_type -> SetDescriptionReply
25, // 31: StreamD.SetApplyProfile:output_type -> SetApplyProfileReply
27, // 32: StreamD.UpdateStream:output_type -> UpdateStreamReply
29, // 33: StreamD.EXPERIMENTAL_ReinitStreamControllers:output_type -> EXPERIMENTAL_ReinitStreamControllersReply
31, // 34: StreamD.OBSOLETE_FetchConfig:output_type -> OBSOLETE_FetchConfigReply
33, // 35: StreamD.OBSOLETE_GitInfo:output_type -> OBSOLETE_GetGitInfoReply
35, // 36: StreamD.OBSOLETE_GitRelogin:output_type -> OBSOLETE_GitReloginReply
37, // 37: StreamD.SubscribeToOAuthRequests:output_type -> OAuthRequest
19, // [19:38] is the sub-list for method output_type
0, // [0:19] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -2312,6 +2421,30 @@ func file_streamd_proto_init() {
return nil
}
}
file_streamd_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeToOAuthRequestsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_streamd_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OAuthRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_streamd_proto_msgTypes[15].OneofWrappers = []interface{}{}
type x struct{}
@@ -2320,7 +2453,7 @@ func file_streamd_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_streamd_proto_rawDesc,
NumEnums: 0,
NumMessages: 36,
NumMessages: 38,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -40,6 +40,7 @@ type StreamDClient interface {
OBSOLETE_FetchConfig(ctx context.Context, in *OBSOLETE_FetchConfigRequest, opts ...grpc.CallOption) (*OBSOLETE_FetchConfigReply, error)
OBSOLETE_GitInfo(ctx context.Context, in *OBSOLETE_GetGitInfoRequest, opts ...grpc.CallOption) (*OBSOLETE_GetGitInfoReply, error)
OBSOLETE_GitRelogin(ctx context.Context, in *OBSOLETE_GitReloginRequest, opts ...grpc.CallOption) (*OBSOLETE_GitReloginReply, error)
SubscribeToOAuthRequests(ctx context.Context, in *SubscribeToOAuthRequestsRequest, opts ...grpc.CallOption) (StreamD_SubscribeToOAuthRequestsClient, error)
}
type streamDClient struct {
@@ -212,6 +213,38 @@ func (c *streamDClient) OBSOLETE_GitRelogin(ctx context.Context, in *OBSOLETE_Gi
return out, nil
}
func (c *streamDClient) SubscribeToOAuthRequests(ctx context.Context, in *SubscribeToOAuthRequestsRequest, opts ...grpc.CallOption) (StreamD_SubscribeToOAuthRequestsClient, error) {
stream, err := c.cc.NewStream(ctx, &StreamD_ServiceDesc.Streams[0], "/StreamD/SubscribeToOAuthRequests", opts...)
if err != nil {
return nil, err
}
x := &streamDSubscribeToOAuthRequestsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type StreamD_SubscribeToOAuthRequestsClient interface {
Recv() (*OAuthRequest, error)
grpc.ClientStream
}
type streamDSubscribeToOAuthRequestsClient struct {
grpc.ClientStream
}
func (x *streamDSubscribeToOAuthRequestsClient) Recv() (*OAuthRequest, error) {
m := new(OAuthRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// StreamDServer is the server API for StreamD service.
// All implementations must embed UnimplementedStreamDServer
// for forward compatibility
@@ -234,6 +267,7 @@ type StreamDServer interface {
OBSOLETE_FetchConfig(context.Context, *OBSOLETE_FetchConfigRequest) (*OBSOLETE_FetchConfigReply, error)
OBSOLETE_GitInfo(context.Context, *OBSOLETE_GetGitInfoRequest) (*OBSOLETE_GetGitInfoReply, error)
OBSOLETE_GitRelogin(context.Context, *OBSOLETE_GitReloginRequest) (*OBSOLETE_GitReloginReply, error)
SubscribeToOAuthRequests(*SubscribeToOAuthRequestsRequest, StreamD_SubscribeToOAuthRequestsServer) error
mustEmbedUnimplementedStreamDServer()
}
@@ -295,6 +329,9 @@ func (UnimplementedStreamDServer) OBSOLETE_GitInfo(context.Context, *OBSOLETE_Ge
func (UnimplementedStreamDServer) OBSOLETE_GitRelogin(context.Context, *OBSOLETE_GitReloginRequest) (*OBSOLETE_GitReloginReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method OBSOLETE_GitRelogin not implemented")
}
func (UnimplementedStreamDServer) SubscribeToOAuthRequests(*SubscribeToOAuthRequestsRequest, StreamD_SubscribeToOAuthRequestsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeToOAuthRequests not implemented")
}
func (UnimplementedStreamDServer) mustEmbedUnimplementedStreamDServer() {}
// UnsafeStreamDServer may be embedded to opt out of forward compatibility for this service.
@@ -632,6 +669,27 @@ func _StreamD_OBSOLETE_GitRelogin_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _StreamD_SubscribeToOAuthRequests_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeToOAuthRequestsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StreamDServer).SubscribeToOAuthRequests(m, &streamDSubscribeToOAuthRequestsServer{stream})
}
type StreamD_SubscribeToOAuthRequestsServer interface {
Send(*OAuthRequest) error
grpc.ServerStream
}
type streamDSubscribeToOAuthRequestsServer struct {
grpc.ServerStream
}
func (x *streamDSubscribeToOAuthRequestsServer) Send(m *OAuthRequest) error {
return x.ServerStream.SendMsg(m)
}
// StreamD_ServiceDesc is the grpc.ServiceDesc for StreamD service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -712,6 +770,12 @@ var StreamD_ServiceDesc = grpc.ServiceDesc{
Handler: _StreamD_OBSOLETE_GitRelogin_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeToOAuthRequests",
Handler: _StreamD_SubscribeToOAuthRequests_Handler,
ServerStreams: true,
},
},
Metadata: "streamd.proto",
}

View File

@@ -22,6 +22,8 @@ service StreamD {
rpc OBSOLETE_FetchConfig(OBSOLETE_FetchConfigRequest) returns (OBSOLETE_FetchConfigReply) {}
rpc OBSOLETE_GitInfo(OBSOLETE_GetGitInfoRequest) returns (OBSOLETE_GetGitInfoReply) {}
rpc OBSOLETE_GitRelogin(OBSOLETE_GitReloginRequest) returns (OBSOLETE_GitReloginReply) {}
rpc SubscribeToOAuthRequests(SubscribeToOAuthRequestsRequest) returns (stream OAuthRequest) {};
}
message GetConfigRequest {}
@@ -51,6 +53,7 @@ message EndStreamRequest {
message EndStreamReply {}
message GetStreamStatusRequest {
string platID = 1;
bool noCache = 2;
}
message GetStreamStatusReply {
bool isActive = 1;
@@ -103,3 +106,8 @@ message OBSOLETE_GetGitInfoReply {
message OBSOLETE_GitReloginRequest {}
message OBSOLETE_GitReloginReply {}
message SubscribeToOAuthRequestsRequest{}
message OAuthRequest{
string authURL = 1;
}

View File

@@ -5,9 +5,16 @@ import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/dustin/go-broadcast"
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/goccy/go-yaml"
"github.com/hashicorp/go-multierror"
"github.com/immune-gmbh/attestation-sdk/pkg/lockmap"
"github.com/immune-gmbh/attestation-sdk/pkg/objhash"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
@@ -20,6 +27,11 @@ import (
type GRPCServer struct {
streamd_grpc.StreamDServer
StreamD api.StreamD
Cache map[objhash.ObjHash]any
CacheMetaLock sync.Mutex
CacheLockMap *lockmap.LockMap
OAuthURLBroadcaster broadcast.Broadcaster
YouTubeStreamStartedAt time.Time
}
var _ streamd_grpc.StreamDServer = (*GRPCServer)(nil)
@@ -27,19 +39,140 @@ var _ streamd_grpc.StreamDServer = (*GRPCServer)(nil)
func NewGRPCServer(streamd api.StreamD) *GRPCServer {
return &GRPCServer{
StreamD: streamd,
Cache: map[objhash.ObjHash]any{},
CacheLockMap: lockmap.NewLockMap(),
OAuthURLBroadcaster: broadcast.NewBroadcaster(10),
}
}
const timeFormat = time.RFC3339
func memoize[REQ any, REPLY any, T func(context.Context, *REQ) (*REPLY, error)](
grpc *GRPCServer,
fn T,
ctx context.Context,
req *REQ,
cacheDuration time.Duration,
) (_ret *REPLY, _err error) {
logger.Debugf(ctx, "memoize %T", (*REQ)(nil))
defer logger.Debugf(ctx, "/memoize %T", (*REQ)(nil))
key, err := objhash.Build(fmt.Sprintf("%T", req), req)
errmon.ObserveErrorCtx(ctx, err)
if err != nil {
return fn(ctx, req)
}
logger.Debugf(ctx, "cache key %X", key[:])
grpc.CacheMetaLock.Lock()
logger.Tracef(ctx, "grpc.CacheMetaLock.Lock()-ed")
cache := grpc.Cache
h := grpc.CacheLockMap.Lock(key)
logger.Tracef(ctx, "grpc.CacheLockMap.Lock(%X)-ed", key[:])
type cacheItem struct {
Reply *REPLY
Error error
SavedAt time.Time
}
if h.UserData != nil {
if v, ok := h.UserData.(cacheItem); ok {
grpc.CacheMetaLock.Unlock()
logger.Tracef(ctx, "grpc.CacheMetaLock.Unlock()-ed")
h.Unlock()
logger.Tracef(ctx, "grpc.CacheLockMap.Unlock(%X)-ed", key[:])
logger.Debugf(ctx, "re-using the value")
return v.Reply, v.Error
} else {
logger.Errorf(ctx, "cache-failure: expected type %T, but got %T", (*cacheItem)(nil), h.UserData)
}
}
//Jul 01 16:47:27 dx-landing start.sh[15530]: {"level":"error","ts":1719852447.1707313,"caller":"server/grpc.go:91","msg":"cache-failure: expected type
//server.cacheItem[github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetStreamStatusRequest,github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetStreamStatusReply,func(context.Context, *github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetStreamStatusRequest) (*github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetStreamStatusReply, error)], but got
//server.cacheItem[github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetBackendInfoRequest,github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetBackendInfoReply,func(context.Context, *github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetBackendInfoRequest) (*github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc.GetBackendInfoReply, error)]"}
cachedResult, ok := cache[key]
grpc.CacheMetaLock.Unlock()
logger.Tracef(ctx, "grpc.CacheMetaLock.Unlock()-ed")
if ok {
if v, ok := cachedResult.(cacheItem); ok {
cutoffTS := time.Now().Add(-cacheDuration)
if cacheDuration > 0 && !v.SavedAt.Before(cutoffTS) {
h.UserData = v
h.Unlock()
logger.Tracef(ctx, "grpc.CacheLockMap.Unlock(%X)-ed", key[:])
logger.Debugf(ctx, "using the cached value")
return v.Reply, v.Error
}
logger.Debugf(ctx, "the cached value expired: %s < %s", v.SavedAt.Format(timeFormat), cutoffTS.Format(timeFormat))
delete(cache, key)
} else {
logger.Errorf(ctx, "cache-failure: expected type %T, but got %T", (*cacheItem)(nil), cachedResult)
}
}
var ts time.Time
defer func() {
cacheItem := cacheItem{
Reply: _ret,
Error: _err,
SavedAt: ts,
}
h.UserData = cacheItem
h.Unlock()
logger.Tracef(ctx, "grpc.CacheLockMap.Unlock(%X)-ed", key[:])
grpc.CacheMetaLock.Lock()
logger.Tracef(ctx, "grpc.CacheMetaLock.Lock()-ed")
defer logger.Tracef(ctx, "grpc.CacheMetaLock.Unlock()-ed")
defer grpc.CacheMetaLock.Unlock()
cache[key] = cacheItem
}()
logger.Tracef(ctx, "no cache")
ts = time.Now()
return fn(ctx, req)
}
func (grpc *GRPCServer) Close() error {
err := &multierror.Error{}
err = multierror.Append(err, grpc.OAuthURLBroadcaster.Close())
return err.ErrorOrNil()
}
func (grpc *GRPCServer) invalidateCache(ctx context.Context) {
logger.Debugf(ctx, "invalidateCache()")
defer logger.Debugf(ctx, "/invalidateCache()")
grpc.CacheMetaLock.Lock()
defer grpc.CacheMetaLock.Unlock()
grpc.CacheLockMap = lockmap.NewLockMap()
grpc.Cache = map[objhash.ObjHash]any{}
}
func (grpc *GRPCServer) GetConfig(
ctx context.Context,
req *streamd_grpc.GetConfigRequest,
) (*streamd_grpc.GetConfigReply, error) {
return memoize(grpc, grpc.getConfig, ctx, req, time.Second)
}
func (grpc *GRPCServer) getConfig(
ctx context.Context,
req *streamd_grpc.GetConfigRequest,
) (*streamd_grpc.GetConfigReply, error) {
cfg, err := grpc.StreamD.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get the config: %w", err)
}
var buf bytes.Buffer
err = config.WriteConfig(ctx, &buf, *cfg)
_, err = cfg.WriteTo(&buf)
if err != nil {
return nil, fmt.Errorf("unable to serialize the config: %w", err)
}
@@ -52,10 +185,11 @@ func (grpc *GRPCServer) SetConfig(
ctx context.Context,
req *streamd_grpc.SetConfigRequest,
) (*streamd_grpc.SetConfigReply, error) {
logger.Debugf(ctx, "received SetConfig: %s", req.Config)
var result config.Config
err := config.ReadConfig(ctx, []byte(req.Config), &result)
_, err := result.Read([]byte(req.Config))
if err != nil {
return nil, fmt.Errorf("unable to unserialize the config: %w", err)
}
@@ -65,6 +199,7 @@ func (grpc *GRPCServer) SetConfig(
return nil, fmt.Errorf("unable to set the config: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.SetConfigReply{}, nil
}
@@ -76,6 +211,7 @@ func (grpc *GRPCServer) SaveConfig(
if err != nil {
return nil, fmt.Errorf("unable to save the config: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.SaveConfigReply{}, nil
}
@@ -87,6 +223,7 @@ func (grpc *GRPCServer) ResetCache(
if err != nil {
return nil, fmt.Errorf("unable to reset the cache: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.ResetCacheReply{}, nil
}
@@ -98,6 +235,7 @@ func (grpc *GRPCServer) InitCache(
if err != nil {
return nil, fmt.Errorf("unable to init the cache: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.InitCacheReply{}, nil
}
@@ -138,6 +276,10 @@ func (grpc *GRPCServer) StartStream(
return nil, fmt.Errorf("unable to start the stream: %w", err)
}
grpc.CacheMetaLock.Lock()
grpc.YouTubeStreamStartedAt = time.Now()
grpc.CacheMetaLock.Unlock()
grpc.invalidateCache(ctx)
return &streamd_grpc.StartStreamReply{}, nil
}
@@ -149,12 +291,20 @@ func (grpc *GRPCServer) EndStream(
if err != nil {
return nil, fmt.Errorf("unable to end the stream: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.EndStreamReply{}, nil
}
func (grpc *GRPCServer) GetBackendInfo(
ctx context.Context,
req *streamd_grpc.GetBackendInfoRequest,
) (*streamd_grpc.GetBackendInfoReply, error) {
return memoize(grpc, grpc.getBackendInfo, ctx, req, time.Second)
}
func (grpc *GRPCServer) getBackendInfo(
ctx context.Context,
req *streamd_grpc.GetBackendInfoRequest,
) (*streamd_grpc.GetBackendInfoReply, error) {
platID := streamcontrol.PlatformName(req.GetPlatID())
isEnabled, err := grpc.StreamD.IsBackendEnabled(ctx, platID)
@@ -184,6 +334,7 @@ func (grpc *GRPCServer) Restart(
if err != nil {
return nil, fmt.Errorf("unable to restart: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.RestartReply{}, nil
}
@@ -195,12 +346,20 @@ func (grpc *GRPCServer) OBSOLETE_FetchConfig(
if err != nil {
return nil, fmt.Errorf("unable to fetch the config: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.OBSOLETE_FetchConfigReply{}, nil
}
func (grpc *GRPCServer) OBSOLETE_GitInfo(
ctx context.Context,
req *streamd_grpc.OBSOLETE_GetGitInfoRequest,
) (*streamd_grpc.OBSOLETE_GetGitInfoReply, error) {
return memoize(grpc, grpc._OBSOLETE_GitInfo, ctx, req, time.Minute)
}
func (grpc *GRPCServer) _OBSOLETE_GitInfo(
ctx context.Context,
req *streamd_grpc.OBSOLETE_GetGitInfoRequest,
) (*streamd_grpc.OBSOLETE_GetGitInfoReply, error) {
isEnabled, err := grpc.StreamD.OBSOLETE_IsGITInitialized(ctx)
if err != nil {
@@ -219,6 +378,7 @@ func (grpc *GRPCServer) OBSOLETE_GitRelogin(
if err != nil {
return nil, fmt.Errorf("unable to relogin: %w", err)
}
grpc.invalidateCache(ctx)
return &streamd_grpc.OBSOLETE_GitReloginReply{}, nil
}
@@ -226,6 +386,37 @@ func (grpc *GRPCServer) GetStreamStatus(
ctx context.Context,
req *streamd_grpc.GetStreamStatusRequest,
) (*streamd_grpc.GetStreamStatusReply, error) {
logger.Tracef(ctx, "GetStreamStatus()")
defer logger.Tracef(ctx, "/GetStreamStatus()")
cacheDuration := time.Minute
switch streamcontrol.PlatformName(req.PlatID) {
case obs.ID:
cacheDuration = 10 * time.Second
case youtube.ID:
if grpc.YouTubeStreamStartedAt.After(time.Now().Add(-time.Minute)) {
cacheDuration = 15 * time.Second
} else {
cacheDuration = 10 * time.Minute
}
case twitch.ID:
cacheDuration = time.Minute
}
if req.NoCache {
cacheDuration = 0
}
return memoize(grpc, grpc.getStreamStatus, ctx, req, cacheDuration)
}
func (grpc *GRPCServer) getStreamStatus(
ctx context.Context,
req *streamd_grpc.GetStreamStatusRequest,
) (*streamd_grpc.GetStreamStatusReply, error) {
logger.Tracef(ctx, "getStreamStatus()")
defer logger.Tracef(ctx, "/getStreamStatus()")
platID := streamcontrol.PlatformName(req.GetPlatID())
streamStatus, err := grpc.StreamD.GetStreamStatus(ctx, platID)
if err != nil {
@@ -252,3 +443,34 @@ func (grpc *GRPCServer) GetStreamStatus(
CustomData: customData,
}, nil
}
func (grpc *GRPCServer) SubscribeToOAuthRequests(
req *streamd_grpc.SubscribeToOAuthRequestsRequest,
sender streamd_grpc.StreamD_SubscribeToOAuthRequestsServer,
) error {
logger.Tracef(sender.Context(), "SubscribeToOAuthRequests()")
defer logger.Tracef(sender.Context(), "/SubscribeToOAuthRequests()")
oauthURLChan := make(chan any)
grpc.OAuthURLBroadcaster.Register(oauthURLChan)
defer grpc.OAuthURLBroadcaster.Unregister(oauthURLChan)
for {
select {
case _oauthURL := <-oauthURLChan:
oauthURL := _oauthURL.(string)
err := sender.Send(&streamd_grpc.OAuthRequest{
AuthURL: oauthURL,
})
if err != nil {
return fmt.Errorf("unable to send the OAuth URL: %w", err)
}
case <-sender.Context().Done():
return sender.Context().Err()
}
}
}
func (grpc *GRPCServer) OpenOAuthURL(authURL string) {
grpc.OAuthURLBroadcaster.Submit(authURL)
}

View File

@@ -126,6 +126,7 @@ func newTwitch(
Enable: platCfg.Enable,
Config: platCfg.Config,
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(platCfg.StreamProfiles),
Custom: platCfg.Custom,
})
if err != nil {
logger.Error(ctx, err)
@@ -147,6 +148,7 @@ func newTwitch(
Enable: c.Enable,
Config: c.Config,
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles),
Custom: c.Custom,
})
},
)
@@ -191,6 +193,7 @@ func newYouTube(
Enable: platCfg.Enable,
Config: platCfg.Config,
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(platCfg.StreamProfiles),
Custom: platCfg.Custom,
})
if err != nil {
logger.Error(ctx, err)
@@ -212,6 +215,7 @@ func newYouTube(
Enable: c.Enable,
Config: c.Config,
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles),
Custom: platCfg.Custom,
})
},
)
@@ -270,7 +274,7 @@ func (d *StreamD) initYouTubeBackend(ctx context.Context) error {
d.UI.OAuthHandlerYouTube,
)
if err != nil {
return err
return fmt.Errorf("unable to initialize the backend 'YouTube': %w", err)
}
d.StreamControllers.YouTube = youTube
return nil

View File

@@ -27,10 +27,12 @@ type StreamControllers struct {
YouTube *youtube.YouTube
}
type SaveConfigFunc func(context.Context, config.Config) error
type StreamD struct {
UI ui.UI
ConfigPath string
SaveConfigFunc SaveConfigFunc
ConfigLock sync.Mutex
Config config.Config
@@ -48,23 +50,19 @@ type StreamD struct {
var _ api.StreamD = (*StreamD)(nil)
func New(configPath string, ui ui.UI, b *belt.Belt) (*StreamD, error) {
func New(config config.Config, ui ui.UI, saveCfgFunc SaveConfigFunc, b *belt.Belt) (*StreamD, error) {
ctx := belt.CtxWithBelt(context.Background(), b)
cfg, err := config.ReadOrCreateConfigFile(ctx, configPath)
if err != nil {
return nil, fmt.Errorf("unable to read or create config '%s': %w", configPath, err)
}
d := &StreamD{
UI: ui,
ConfigPath: configPath,
Config: *cfg,
SaveConfigFunc: saveCfgFunc,
Config: config,
Cache: &cache.Cache{},
}
err = d.readCache(ctx)
err := d.readCache(ctx)
if err != nil {
logger.FromBelt(b).Errorf("unable to read cache from '%s': %v", d.ConfigPath, err)
logger.FromBelt(b).Errorf("unable to read cache: %v", err)
}
return d, nil
@@ -98,7 +96,7 @@ func (d *StreamD) readCache(ctx context.Context) error {
if d.Config.CachePath == nil {
d.Config.CachePath = config.NewConfig().CachePath
logger.Tracef(ctx, "setting the CachePath to default value '%s'", d.ConfigPath)
logger.Tracef(ctx, "setting the CachePath to default value '%s'", *d.Config.CachePath)
}
if *d.Config.CachePath == "" {
@@ -125,7 +123,7 @@ func (d *StreamD) writeCache(ctx context.Context) error {
if d.Config.CachePath == nil {
d.Config.CachePath = config.NewConfig().CachePath
logger.Tracef(ctx, "setting the CachePath to default value '%s'", d.ConfigPath)
logger.Tracef(ctx, "setting the CachePath to default value '%s'", *d.Config.CachePath)
}
if *d.Config.CachePath == "" {
@@ -186,7 +184,7 @@ func (d *StreamD) InitCache(ctx context.Context) error {
if changedCache {
err := d.writeCache(ctx)
if err != nil {
logger.Errorf(ctx, "unable to write cache into '%s': %w", d.ConfigPath, err)
logger.Errorf(ctx, "unable to write cache into '%s': %w", *d.Config.CachePath, err)
}
}
return nil
@@ -288,7 +286,7 @@ func (d *StreamD) normalizeYoutubeData() {
}
func (d *StreamD) SaveConfig(ctx context.Context) error {
err := d.saveDataToConfigFile(ctx)
err := d.SaveConfigFunc(ctx, d.Config)
if err != nil {
return err
}
@@ -305,24 +303,6 @@ func (d *StreamD) SaveConfig(ctx context.Context) error {
return nil
}
func (d *StreamD) getExpandedDataPath() (string, error) {
return xpath.Expand(d.ConfigPath)
}
func (d *StreamD) saveDataToConfigFile(ctx context.Context) error {
dataPath, err := d.getExpandedDataPath()
if err != nil {
return fmt.Errorf("unable to get the path to the config file: %w", err)
}
err = config.WriteConfigToPath(ctx, dataPath, d.Config)
if err != nil {
return fmt.Errorf("unable to save the config: %w", err)
}
return nil
}
func (d *StreamD) ResetCache(ctx context.Context) error {
d.Cache.Twitch = cache.Twitch{}
d.Cache.Youtube = cache.YouTube{}
@@ -362,7 +342,8 @@ func (d *StreamD) StartStream(
title string, description string,
profile streamcontrol.AbstractStreamProfile,
customArgs ...any,
) error {
) (_err error) {
defer func() { logger.Debugf(ctx, "/StartStream(%s): %v", platID, _err) }()
switch platID {
case obs.ID:
profile, err := streamcontrol.GetStreamProfile[obs.StreamProfile](ctx, profile)

View File

@@ -0,0 +1,57 @@
package config
import (
"context"
"fmt"
"os"
"github.com/facebookincubator/go-belt/tool/logger"
streamd "github.com/xaionaro-go/streamctl/pkg/streamd/config"
)
type Config struct {
RemoteStreamDAddr string `yaml:"streamd_remote"`
BuiltinStreamD streamd.Config `yaml:"streamd_builtin"`
}
func DefaultConfig() Config {
return Config{
BuiltinStreamD: streamd.NewSampleConfig(),
}
}
func ReadConfigFromPath[CFG Config](
cfgPath string,
cfg *Config,
) error {
b, err := os.ReadFile(cfgPath)
if err != nil {
return fmt.Errorf("unable to read file '%s': %w", cfgPath, err)
}
_, err = cfg.Read(b)
return err
}
func WriteConfigToPath(
ctx context.Context,
cfgPath string,
cfg Config,
) error {
pathNew := cfgPath + ".new"
f, err := os.OpenFile(pathNew, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0750)
if err != nil {
return fmt.Errorf("unable to open the data file '%s': %w", pathNew, err)
}
_, err = cfg.WriteTo(f)
f.Close()
if err != nil {
return fmt.Errorf("unable to write data to file '%s': %w", pathNew, err)
}
err = os.Rename(pathNew, cfgPath)
if err != nil {
return fmt.Errorf("cannot move '%s' to '%s': %w", pathNew, cfgPath, err)
}
logger.Infof(ctx, "wrote to '%s' config %#+v", cfgPath, cfg)
return nil
}

View File

@@ -0,0 +1,4 @@
package config
const CustomConfigKeyOnStreamStart = "on_start_stream"
const CustomConfigKeyOnStreamStop = "on_stop_stream"

View File

@@ -0,0 +1,20 @@
package config
type Option interface {
Apply(cfg *Config)
}
type Options []Option
func (options Options) ApplyOverrides(cfg Config) Config {
for _, option := range options {
option.Apply(&cfg)
}
return cfg
}
type OptionRemoteStreamDAddr string
func (o OptionRemoteStreamDAddr) Apply(cfg *Config) {
cfg.RemoteStreamDAddr = string(o)
}

View File

@@ -0,0 +1,29 @@
package config
import (
"fmt"
"io"
"github.com/goccy/go-yaml"
)
var _ io.Reader = (*Config)(nil)
var _ io.ReaderFrom = (*Config)(nil)
func (cfg *Config) Read(
b []byte,
) (int, error) {
return len(b), yaml.Unmarshal(b, cfg)
}
func (cfg *Config) ReadFrom(
r io.Reader,
) (int64, error) {
b, err := io.ReadAll(r)
if err != nil {
return int64(len(b)), fmt.Errorf("unable to read: %w", err)
}
n, err := cfg.Read(b)
return int64(n), err
}

View File

@@ -0,0 +1,30 @@
package config
import (
"bytes"
"io"
"github.com/goccy/go-yaml"
"github.com/xaionaro-go/datacounter"
)
var _ io.Writer = (*Config)(nil)
var _ io.WriterTo = (*Config)(nil)
func (cfg Config) Write(b []byte) (int, error) {
n, err := cfg.WriteTo(bytes.NewBuffer(b))
return int(n), err
}
func (cfg Config) WriteTo(
w io.Writer,
) (int64, error) {
b, err := yaml.Marshal(cfg)
if err != nil {
return 0, err
}
counter := datacounter.NewWriterCounter(w)
io.Copy(counter, bytes.NewReader(b))
return int64(counter.Count()), nil
}

View File

@@ -1,22 +1,8 @@
package streampanel
type configT struct {
}
import "github.com/xaionaro-go/streamctl/pkg/streampanel/config"
func defaultConfig() configT {
return configT{}
}
type Option interface {
apply(cfg *configT)
}
type Options []Option
func (options Options) Config() configT {
cfg := defaultConfig()
for _, option := range options {
option.apply(&cfg)
}
return cfg
}
type Config = config.Config
type Option = config.Option
type Options = config.Options
type OptionRemoteStreamDAddr = config.OptionRemoteStreamDAddr

View File

@@ -34,21 +34,24 @@ import (
"github.com/xaionaro-go/streamctl/pkg/streamd"
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
"github.com/xaionaro-go/streamctl/pkg/streamd/client"
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
streamdconfig "github.com/xaionaro-go/streamctl/pkg/streamd/config"
"github.com/xaionaro-go/streamctl/pkg/streampanel/config"
"github.com/xaionaro-go/streamctl/pkg/xpath"
)
const appName = "StreamPanel"
type Profile struct {
config.ProfileMetadata
streamdconfig.ProfileMetadata
Name streamcontrol.ProfileName
PerPlatform map[streamcontrol.PlatformName]streamcontrol.AbstractStreamProfile
}
type Panel struct {
StreamD StreamD
StreamD api.StreamD
app fyne.App
config configT
config Config
streamMutex sync.Mutex
updateTimerHandler *updateTimerHandler
profilesOrder []streamcontrol.ProfileName
@@ -69,31 +72,36 @@ type Panel struct {
twitchCheck *widget.Check
configPath string
configCache *config.Config
remoteAddr string
configCache *streamdconfig.Config
setStatusFunc func(string)
displayErrorLocker sync.Mutex
displayErrorWindow fyne.Window
waitWindowLocker sync.Mutex
waitWindow fyne.Window
}
func NewBuiltin(
func New(
configPath string,
opts ...Option,
) *Panel {
return &Panel{
configPath: configPath,
config: Options(opts).Config(),
}
) (*Panel, error) {
configPath, err := getExpandedConfigPath(configPath)
if err != nil {
return nil, fmt.Errorf("unable to get the path to the config file: %w", err)
}
func NewRemote(
remoteAddr string,
opts ...Option,
) *Panel {
return &Panel{
remoteAddr: remoteAddr,
config: Options(opts).Config(),
var cfg Config
err = config.ReadConfigFromPath(configPath, &cfg)
if err != nil {
return nil, fmt.Errorf("unable to read the config from path '%s': %w", configPath, err)
}
return &Panel{
configPath: configPath,
config: Options(opts).ApplyOverrides(cfg),
}, nil
}
func (p *Panel) SetStatus(msg string) {
@@ -111,22 +119,21 @@ func (p *Panel) Loop(ctx context.Context) error {
p.defaultContext = ctx
logger.Debug(ctx, "config", p.config)
switch {
case p.configPath != "":
if p.config.RemoteStreamDAddr != "" {
if err := p.initRemoteStreamD(ctx); err != nil {
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.config.RemoteStreamDAddr, err)
}
} else {
if err := p.initBuiltinStreamD(ctx); err != nil {
return fmt.Errorf("unable to initialize the builtin stream controller '%s': %w", p.configPath, err)
}
case p.remoteAddr != "":
if err := p.initRemoteStreamD(ctx); err != nil {
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.remoteAddr, err)
}
}
p.app = fyneapp.New()
go func() {
var loadingWindow fyne.Window
if p.remoteAddr == "" {
if p.config.RemoteStreamDAddr == "" {
loadingWindow = p.newLoadingWindow(ctx)
resizeWindow(loadingWindow, fyne.NewSize(600, 600))
loadingWindowText := widget.NewRichTextFromMarkdown("")
@@ -136,11 +143,37 @@ func (p *Panel) Loop(ctx context.Context) error {
loadingWindowText.ParseMarkdown(fmt.Sprintf("# %s", msg))
}
}
if streamD, ok := p.StreamD.(*client.Client); ok && false {
oauthURLChan, err := streamD.SubscriberToOAuthURLs(ctx)
if err != nil {
p.DisplayError(fmt.Errorf("unable to subscribe to OAuth requests of streamd: %w", err))
}
go func() {
for {
select {
case <-ctx.Done():
return
case authURL, ok := <-oauthURLChan:
if !ok {
return
}
if authURL == "" {
logger.Errorf(ctx, "received an empty authURL")
time.Sleep(1 * time.Second)
continue
}
oauthhandler.LaunchBrowser(authURL)
time.Sleep(1 * time.Second) // throttling the execution to avoid hanging the OS
}
}
}()
}
err := p.StreamD.Run(ctx)
if err != nil {
p.DisplayError(fmt.Errorf("unable to initialize the streaming controllers: %w", err))
os.Exit(1)
}
p.setStatusFunc = nil
@@ -150,7 +183,7 @@ func (p *Panel) Loop(ctx context.Context) error {
p.DisplayError(err)
}
if p.remoteAddr == "" {
if p.config.RemoteStreamDAddr == "" {
logger.Tracef(ctx, "hiding the loading window")
loadingWindow.Hide()
}
@@ -166,23 +199,33 @@ func (p *Panel) newLoadingWindow(ctx context.Context) fyne.Window {
logger.FromCtx(ctx).Debugf("newLoadingWindow")
defer logger.FromCtx(ctx).Debugf("endof newLoadingWindow")
w := p.app.NewWindow("Loading...")
w := p.app.NewWindow(appName + ": Loading...")
w.Show()
return w
}
func (p *Panel) getExpandedDataPath() (string, error) {
return xpath.Expand(p.configPath)
func getExpandedConfigPath(configPath string) (string, error) {
return xpath.Expand(configPath)
}
func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
dataPath, err := p.getExpandedDataPath()
var err error
p.StreamD, err = streamd.New(
p.config.BuiltinStreamD,
p,
func(ctx context.Context, cfg streamdconfig.Config) error {
p.config.BuiltinStreamD = cfg
err = config.WriteConfigToPath(ctx, p.configPath, p.config)
if err != nil {
return fmt.Errorf("unable to get the path to the data file: %w", err)
return fmt.Errorf("unable to save the config: %w", err)
}
p.StreamD, err = streamd.New(dataPath, p, belt.CtxBelt(ctx))
return nil
},
belt.CtxBelt(ctx),
)
if err != nil {
return fmt.Errorf("unable to initialize the streamd instance: %w", err)
}
@@ -191,7 +234,7 @@ func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
}
func (p *Panel) initRemoteStreamD(context.Context) error {
p.StreamD = client.New(p.remoteAddr)
p.StreamD = client.New(p.config.RemoteStreamDAddr)
return nil
}
@@ -209,7 +252,7 @@ func (p *Panel) InputOBSConnectInfo(
ctx context.Context,
cfg *streamcontrol.PlatformConfig[obs.PlatformSpecificConfig, obs.StreamProfile],
) (bool, error) {
w := p.app.NewWindow("Input Twitch user info")
w := p.app.NewWindow(appName + ": Input Twitch user info")
resizeWindow(w, fyne.NewSize(600, 200))
hostField := widget.NewEntry()
@@ -330,7 +373,7 @@ func (p *Panel) openBrowser(authURL string) error {
waitCh := make(chan struct{})
w := p.app.NewWindow("Browser selection window")
w := p.app.NewWindow(appName + ": Browser selection window")
promptText := widget.NewRichTextWithText("It is required to confirm access in Twitch/YouTube using browser. Select a browser for that (or leave the field empty for auto-selection):")
promptText.Wrapping = fyne.TextWrapWord
browserField := widget.NewEntry()
@@ -369,7 +412,7 @@ func (p *Panel) InputTwitchUserInfo(
ctx context.Context,
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
) (bool, error) {
w := p.app.NewWindow("Input Twitch user info")
w := p.app.NewWindow(appName + ": Input Twitch user info")
resizeWindow(w, fyne.NewSize(600, 200))
channelField := widget.NewEntry()
@@ -430,7 +473,7 @@ func (p *Panel) InputYouTubeUserInfo(
ctx context.Context,
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
) (bool, error) {
w := p.app.NewWindow("Input YouTube user info")
w := p.app.NewWindow(appName + ": Input YouTube user info")
resizeWindow(w, fyne.NewSize(600, 200))
clientIDField := widget.NewEntry()
@@ -538,7 +581,7 @@ func (p *Panel) profileDelete(ctx context.Context, profileName streamcontrol.Pro
return nil
}
func getProfile(cfg *config.Config, profileName streamcontrol.ProfileName) Profile {
func getProfile(cfg *streamdconfig.Config, profileName streamcontrol.ProfileName) Profile {
prof := Profile{
ProfileMetadata: cfg.ProfileMetadata[profileName],
Name: profileName,
@@ -747,20 +790,23 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
return fmt.Errorf("unable to get info if GIT is initialized: %w", err)
}
w := p.app.NewWindow("Settings")
w := p.app.NewWindow(appName + ": Settings")
resizeWindow(w, fyne.NewSize(400, 900))
cmdStartStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyOnStreamStart)
cmdStopStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyOnStreamStop)
startStreamCommandEntry := widget.NewEntry()
startStreamCommandEntry.SetText(cfg.Commands.OnStartStream)
startStreamCommandEntry.SetText(cmdStartStream)
stopStreamCommandEntry := widget.NewEntry()
stopStreamCommandEntry.SetText(cfg.Commands.OnStopStream)
stopStreamCommandEntry.SetText(cmdStopStream)
cancelButton := widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func() {
w.Close()
})
saveButton := widget.NewButtonWithIcon("Save", theme.DocumentSaveIcon(), func() {
cfg.Commands.OnStartStream = startStreamCommandEntry.Text
cfg.Commands.OnStopStream = stopStreamCommandEntry.Text
cfg.Backends[obs.ID].SetCustomString(config.CustomConfigKeyOnStreamStart, startStreamCommandEntry.Text)
cfg.Backends[obs.ID].SetCustomString(config.CustomConfigKeyOnStreamStop, stopStreamCommandEntry.Text)
if err := p.StreamD.SaveConfig(ctx); err != nil {
p.DisplayError(err)
@@ -931,18 +977,24 @@ func (p *Panel) openMenuWindow(ctx context.Context) {
}
func resizeWindow(w fyne.Window, newSize fyne.Size) {
w.Resize(newSize)
}
func setupStreamString() string {
switch runtime.GOOS {
case "android":
w.SetFullScreen(true)
w.SetPadded(false)
w.Canvas().SetOnTypedRune(func(r rune) {
go func() {
time.Sleep(200 * time.Millisecond)
w.Resize(w.Canvas().Size())
}()
})
return "Set!"
default:
w.Resize(newSize)
return "Setup stream"
}
}
func startStreamString() string {
switch runtime.GOOS {
case "android":
return "Go!"
default:
return "Start stream"
}
}
@@ -961,6 +1013,12 @@ func (p *Panel) getUpdatedStatus_startStopStreamButton(ctx context.Context) {
return
}
if !obsIsEnabled {
if p.updateTimerHandler != nil {
p.updateTimerHandler.Close()
p.updateTimerHandler = nil
}
p.startStopButton.SetText(startStreamString())
p.startStopButton.Icon = theme.MediaRecordIcon()
p.startStopButton.Importance = widget.SuccessImportance
p.startStopButton.Disable()
return
@@ -974,10 +1032,27 @@ func (p *Panel) getUpdatedStatus_startStopStreamButton(ctx context.Context) {
logger.Tracef(ctx, "obsStreamStatus == %#+v", obsStreamStatus)
if obsStreamStatus.IsActive {
p.startStopButton.Icon = theme.MediaStopIcon()
p.startStopButton.Importance = widget.DangerImportance
p.startStopButton.Enable()
if p.updateTimerHandler == nil {
if obsStreamStatus.StartedAt == nil {
p.startStopButton.SetText("Stop stream")
} else {
p.startStopButton.SetText("...")
logger.Debugf(ctx, "stream was already started at %s", obsStreamStatus.StartedAt.Format(time.RFC3339))
p.updateTimerHandler = newUpdateTimerHandler(p.startStopButton, *obsStreamStatus.StartedAt)
}
}
return
}
if p.updateTimerHandler != nil {
p.updateTimerHandler.Close()
p.updateTimerHandler = nil
}
p.startStopButton.SetText(startStreamString())
p.startStopButton.Icon = theme.MediaRecordIcon()
p.startStopButton.Importance = widget.SuccessImportance
ytIsEnabled, err := p.StreamD.IsBackendEnabled(ctx, youtube.ID)
@@ -986,28 +1061,28 @@ func (p *Panel) getUpdatedStatus_startStopStreamButton(ctx context.Context) {
return
}
if !ytIsEnabled {
if !ytIsEnabled || !p.youtubeCheck.Checked {
p.startStopButton.Enable()
return
}
ytStreamStatus, err := p.StreamD.GetStreamStatus(ctx, youtube.ID)
if err != nil {
logger.Errorf(ctx, "unable to get stream status from OBS: %w", err)
logger.Errorf(ctx, "unable to get stream status from YouTube: %w", err)
return
}
logger.Tracef(ctx, "ytStreamStatus == %#+v", ytStreamStatus)
if d, ok := ytStreamStatus.CustomData.(youtube.StreamStatusCustomData); ok {
logger.Tracef(ctx, "len(d.UpcomingBroadcasts) == %d; len(d.Streams) == %d", len(d.UpcomingBroadcasts), len(d.Streams))
if len(d.UpcomingBroadcasts) != 0 || len(d.Streams) != 0 {
if len(d.UpcomingBroadcasts) != 0 {
p.startStopButton.Enable()
}
}
}
func (p *Panel) initMainWindow(ctx context.Context) {
w := p.app.NewWindow("StreamPanel")
w := p.app.NewWindow(appName)
w.SetMaster()
resizeWindow(w, fyne.NewSize(400, 600))
@@ -1066,12 +1141,12 @@ func (p *Panel) initMainWindow(ctx context.Context) {
profileFilter,
)
p.setupStreamButton = widget.NewButtonWithIcon("Setup stream", theme.SettingsIcon(), func() {
p.setupStreamButton = widget.NewButtonWithIcon(setupStreamString(), theme.SettingsIcon(), func() {
p.onSetupStreamButton(ctx)
})
p.setupStreamButton.Disable()
p.startStopButton = widget.NewButtonWithIcon("Start stream", theme.MediaRecordIcon(), func() {
p.startStopButton = widget.NewButtonWithIcon(startStreamString(), theme.MediaRecordIcon(), func() {
p.onStartStopButton(ctx)
})
p.startStopButton.Importance = widget.SuccessImportance
@@ -1137,13 +1212,6 @@ func (p *Panel) initMainWindow(ctx context.Context) {
),
)
switch runtime.GOOS {
case "android":
l := widget.NewMultiLineEntry()
l.SetMinRowsVisible(10)
bottomPanel.Add(l)
}
w.SetContent(container.NewBorder(
topPanel,
bottomPanel,
@@ -1156,10 +1224,11 @@ func (p *Panel) initMainWindow(ctx context.Context) {
p.mainWindow = w
p.profilesListWidget = profilesList
if _, ok := p.StreamD.(*client.Client); ok {
go func() {
p.getUpdatedStatus(ctx)
t := time.NewTicker(5 * time.Second)
t := time.NewTicker(1 * time.Second)
for {
select {
case <-ctx.Done():
@@ -1172,6 +1241,7 @@ func (p *Panel) initMainWindow(ctx context.Context) {
}
}()
}
}
func (p *Panel) getSelectedProfile() Profile {
return getProfile(p.configCache, *p.selectedProfileName)
@@ -1308,7 +1378,7 @@ func (p *Panel) startStream(ctx context.Context) {
if p.updateTimerHandler != nil {
p.updateTimerHandler.Stop()
}
p.updateTimerHandler = newUpdateTimerHandler(p.startStopButton)
p.updateTimerHandler = newUpdateTimerHandler(p.startStopButton, time.Now())
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, obs.ID)
if err != nil {
@@ -1333,7 +1403,9 @@ func (p *Panel) startStream(ctx context.Context) {
p.DisplayError(fmt.Errorf("unable to start the stream on YouTube: %w", err))
}
p.execCommand(ctx, p.configCache.Commands.OnStartStream)
if onStreamStart, ok := p.configCache.Backends[obs.ID].GetCustomString(config.CustomConfigKeyOnStreamStart); ok {
p.execCommand(ctx, onStreamStart)
}
p.startStopButton.Refresh()
}
@@ -1387,8 +1459,12 @@ func (p *Panel) stopStream(ctx context.Context) {
}
p.startStopButton.SetText("OnStopStream command...")
p.execCommand(ctx, p.configCache.Commands.OnStopStream)
p.startStopButton.SetText("Start stream")
if onStreamStop, ok := p.configCache.Backends[obs.ID].GetCustomString(config.CustomConfigKeyOnStreamStop); ok {
p.execCommand(ctx, onStreamStop)
}
p.startStopButton.SetText(startStreamString())
p.startStopButton.Icon = theme.MediaRecordIcon()
p.startStopButton.Importance = widget.SuccessImportance
@@ -1396,7 +1472,7 @@ func (p *Panel) stopStream(ctx context.Context) {
}
func (p *Panel) onSetupStreamButton(ctx context.Context) {
p.setupStream(ctx)
p.waitForResponse(func() { p.setupStream(ctx) })
}
func (p *Panel) onStartStopButton(ctx context.Context) {
@@ -1412,7 +1488,7 @@ func (p *Panel) onStartStopButton(ctx context.Context) {
"Are you ready to start the stream?",
func(b bool) {
if b {
p.startStream(ctx)
p.waitForResponse(func() { p.startStream(ctx) })
}
},
p.mainWindow,
@@ -1431,7 +1507,9 @@ func cleanYoutubeRecordingName(in string) string {
func (p *Panel) editProfileWindow(ctx context.Context) fyne.Window {
oldProfile := getProfile(p.configCache, *p.selectedProfileName)
return p.profileWindow(
var w fyne.Window
p.waitForResponse(func() {
w = p.profileWindow(
ctx,
fmt.Sprintf("Edit the profile '%s'", oldProfile.Name),
oldProfile,
@@ -1448,11 +1526,15 @@ func (p *Panel) editProfileWindow(ctx context.Context) fyne.Window {
return nil
},
)
})
return w
}
func (p *Panel) cloneProfileWindow(ctx context.Context) fyne.Window {
oldProfile := getProfile(p.configCache, *p.selectedProfileName)
return p.profileWindow(
var w fyne.Window
p.waitForResponse(func() {
w = p.profileWindow(
ctx,
"Create a profile",
oldProfile,
@@ -1467,10 +1549,12 @@ func (p *Panel) cloneProfileWindow(ctx context.Context) fyne.Window {
return nil
},
)
})
return w
}
func (p *Panel) deleteProfileWindow(ctx context.Context) fyne.Window {
w := p.app.NewWindow("Delete the profile?")
w := p.app.NewWindow(appName + ": Delete the profile?")
yesButton := widget.NewButton("YES", func() {
err := p.profileDelete(ctx, *p.selectedProfileName)
@@ -1499,7 +1583,9 @@ func (p *Panel) deleteProfileWindow(ctx context.Context) fyne.Window {
}
func (p *Panel) newProfileWindow(ctx context.Context) fyne.Window {
return p.profileWindow(
var w fyne.Window
p.waitForResponse(func() {
w = p.profileWindow(
ctx,
"Create a profile",
Profile{},
@@ -1522,6 +1608,8 @@ func (p *Panel) newProfileWindow(ctx context.Context) fyne.Window {
return nil
},
)
})
return w
}
func ptr[T any](in T) *T {
@@ -1917,7 +2005,7 @@ func (p *Panel) profileWindow(
profile := Profile{
Name: streamcontrol.ProfileName(profileName.Text),
PerPlatform: map[streamcontrol.PlatformName]streamcontrol.AbstractStreamProfile{},
ProfileMetadata: config.ProfileMetadata{
ProfileMetadata: streamdconfig.ProfileMetadata{
DefaultStreamTitle: defaultStreamTitle.Text,
DefaultStreamDescription: defaultStreamDescription.Text,
MaxOrder: 0,
@@ -1942,7 +2030,10 @@ func (p *Panel) profileWindow(
youtubeProfile.Tags = _tags
profile.PerPlatform[youtube.ID] = youtubeProfile
}
err := commitFn(ctx, profile)
var err error
p.waitForResponse(func() {
err = commitFn(ctx, profile)
})
if err != nil {
p.DisplayError(err)
return
@@ -1969,9 +2060,9 @@ func (p *Panel) profileWindow(
func (p *Panel) DisplayError(err error) {
logger.Debugf(p.defaultContext, "DisplayError('%v')", err)
w := p.app.NewWindow("Got an error: " + err.Error())
defer logger.Debugf(p.defaultContext, "/DisplayError('%v')", err)
errorMessage := fmt.Sprintf("Error: %v\n\nstack trace:\n%s", err, debug.Stack())
resizeWindow(w, fyne.NewSize(400, 300))
textWidget := widget.NewMultiLineEntry()
textWidget.SetText(errorMessage)
textWidget.Wrapping = fyne.TextWrapWord
@@ -1979,6 +2070,51 @@ func (p *Panel) DisplayError(err error) {
Bold: true,
Monospace: true,
}
p.displayErrorLocker.Lock()
defer p.displayErrorLocker.Unlock()
if p.displayErrorWindow != nil {
p.displayErrorWindow.SetContent(container.NewVSplit(p.displayErrorWindow.Content(), textWidget))
return
}
w := p.app.NewWindow(appName + ": Got an error: " + err.Error())
resizeWindow(w, fyne.NewSize(400, 300))
w.SetContent(textWidget)
w.SetOnClosed(func() {
p.displayErrorLocker.Lock()
defer p.displayErrorLocker.Unlock()
p.displayErrorWindow = nil
})
w.Show()
}
func (p *Panel) waitForResponse(callback func()) {
p.showWaitWindow()
defer p.hideWaitWindow()
callback()
}
func (p *Panel) showWaitWindow() {
p.waitWindowLocker.Lock()
defer p.waitWindowLocker.Unlock()
if p.waitWindow != nil {
return
}
waitWindow := p.app.NewWindow(appName + ": Please wait...")
textWidget := widget.NewRichTextFromMarkdown("A long operation is in process, please wait...")
waitWindow.SetContent(textWidget)
waitWindow.Show()
p.waitWindow = waitWindow
}
func (p *Panel) hideWaitWindow() {
p.waitWindowLocker.Lock()
defer p.waitWindowLocker.Unlock()
p.waitWindow.Close()
p.waitWindow = nil
}

View File

@@ -1,19 +0,0 @@
package streampanel
import (
"github.com/facebookincubator/go-belt"
"github.com/xaionaro-go/streamctl/pkg/streamd"
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
"github.com/xaionaro-go/streamctl/pkg/streamd/client"
"github.com/xaionaro-go/streamctl/pkg/streamd/ui"
)
type StreamD = api.StreamD
func NewBuiltinStreamD(configPath string, ui ui.UI, b *belt.Belt) (*streamd.StreamD, error) {
return streamd.New(configPath, ui, b)
}
func NewRemoteStreamD(target string, ui ui.UI, _ *belt.Belt) *client.Client {
return client.New(target)
}

View File

@@ -14,15 +14,15 @@ type updateTimerHandler struct {
startTS time.Time
}
func newUpdateTimerHandler(startStopButton *widget.Button) *updateTimerHandler {
func newUpdateTimerHandler(startStopButton *widget.Button, startedAt time.Time) *updateTimerHandler {
ctx, cancelFn := context.WithCancel(context.Background())
h := &updateTimerHandler{
ctx: ctx,
cancelFn: cancelFn,
startStopButton: startStopButton,
startTS: time.Now(),
startTS: startedAt,
}
h.startStopButton.Text = "0s"
h.startStopButton.Text = "..."
go h.loop()
return h
}