diff --git a/.gitignore b/.gitignore index a1db7a8..e5644ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ *.apk build +qtbox +qamel-* +rcc*.go +rcc*.cpp +*.qrc diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index 3cc54ea..ee50c3d 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/cmd/streamd/main.go b/cmd/streamd/main.go index 8c52f6d..feddc8a 100644 --- a/cmd/streamd/main.go +++ b/cmd/streamd/main.go @@ -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() }, diff --git a/cmd/streamd/ui/ui.go b/cmd/streamd/ui/ui.go index 74655d2..3789c1f 100644 --- a/cmd/streamd/ui/ui.go +++ b/cmd/streamd/ui/ui.go @@ -15,19 +15,22 @@ import ( ) type UI struct { - Belt *belt.Belt - RestartFn func(context.Context, string) + OAuthURLOpenFn func(authURL string) + Belt *belt.Belt + RestartFn func(context.Context, string) } var _ ui.UI = (*UI)(nil) func NewUI( ctx context.Context, + oauthURLOpener func(authURL string), restartFn func(context.Context, string), ) *UI { return &UI{ - Belt: belt.CtxBelt(ctx), - RestartFn: restartFn, + 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 + } + + ui.OAuthURLOpenFn(arg.AuthURL) + + code := <-codeCh + return arg.ExchangeFn(code) } -func (*UI) OAuthHandlerYouTube( +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( diff --git a/cmd/streampanel/FyneApp.toml b/cmd/streampanel/FyneApp.toml index bc9c35a..e75bcee 100644 --- a/cmd/streampanel/FyneApp.toml +++ b/cmd/streampanel/FyneApp.toml @@ -5,4 +5,4 @@ Website = "https://github.com/xaionaro/streamctl" Name = "streampanel" ID = "center.dx.streampanel" Version = "0.1.0" - Build = 22 + Build = 28 diff --git a/cmd/streampanel/error_monitor_hook.go b/cmd/streampanel/error_monitor_hook.go new file mode 100644 index 0000000..733e4d5 --- /dev/null +++ b/cmd/streampanel/error_monitor_hook.go @@ -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() {} diff --git a/cmd/streampanel/main.go b/cmd/streampanel/main.go index 9432d6d..14f6a46 100644 --- a/cmd/streampanel/main.go +++ b/cmd/streampanel/main.go @@ -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) } diff --git a/go.mod b/go.mod index 0d55976..60b1507 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 2155b44..3f16986 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/experimental/qstreampanel/qml/Generated/Generated.txt b/pkg/experimental/qstreampanel/qml/Generated/Generated.txt new file mode 100644 index 0000000..84c843f --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/Generated/Generated.txt @@ -0,0 +1 @@ +Imported 3D assets and components imported from bundles will be created in this folder. diff --git a/pkg/experimental/qstreampanel/qml/QStream.qmlproject b/pkg/experimental/qstreampanel/qml/QStream.qmlproject new file mode 100644 index 0000000..77ee1b0 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream.qmlproject @@ -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" + +} diff --git a/pkg/experimental/qstreampanel/qml/QStream.qmlproject.qtds b/pkg/experimental/qstreampanel/qml/QStream.qmlproject.qtds new file mode 100644 index 0000000..71ff839 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream.qmlproject.qtds @@ -0,0 +1,116 @@ + + + + + + EnvironmentId + {ec5bdba3-1159-44c9-a0db-a0d3d900c9c2} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.7.1 + Desktop Qt 6.7.1 + {63f87550-2541-4163-9631-08b7fea781da} + -1 + 0 + 0 + 0 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + 0 + + false + QML Runtime + QmlProjectManager.QmlRunConfiguration.Qml + + false + en + CurrentFile + true + true + true + :1 + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/pkg/experimental/qstreampanel/qml/QStream.qmlproject.user b/pkg/experimental/qstreampanel/qml/QStream.qmlproject.user new file mode 100644 index 0000000..ae605ee --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream.qmlproject.user @@ -0,0 +1,148 @@ + + + + + + EnvironmentId + {e9757cbe-2f51-4736-a8e1-c1053c85fe17} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 12 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.7.2 + Desktop Qt 6.7.2 + qt.qt6.672.linux_gcc_64_kit + -1 + 0 + 0 + 0 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 0 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + QML Runtime + QmlProjectManager.QmlRunConfiguration.Qml + + false + en + CurrentFile + true + true + true + :1 + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/pkg/experimental/qstreampanel/qml/QStream/Constants.qml b/pkg/experimental/qstreampanel/qml/QStream/Constants.qml new file mode 100644 index 0000000..1d71f38 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/Constants.qml @@ -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" +} diff --git a/pkg/experimental/qstreampanel/qml/QStream/DirectoryFontLoader.qml b/pkg/experimental/qstreampanel/qml/QStream/DirectoryFontLoader.qml new file mode 100644 index 0000000..10a5764 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/DirectoryFontLoader.qml @@ -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")) + } + } + } + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStream/EventListModel.qml b/pkg/experimental/qstreampanel/qml/QStream/EventListModel.qml new file mode 100644 index 0000000..423d170 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/EventListModel.qml @@ -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" + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStream/EventListSimulator.qml b/pkg/experimental/qstreampanel/qml/QStream/EventListSimulator.qml new file mode 100644 index 0000000..b6e5f3a --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/EventListSimulator.qml @@ -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() + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStream/designer/plugin.metainfo b/pkg/experimental/qstreampanel/qml/QStream/designer/plugin.metainfo new file mode 100644 index 0000000..571214c --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/designer/plugin.metainfo @@ -0,0 +1,13 @@ +MetaInfo { + Type { + name: "QStream.EventListSimulator" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: false + } + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStream/qmldir b/pkg/experimental/qstreampanel/qml/QStream/qmldir new file mode 100644 index 0000000..0cfd46b --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStream/qmldir @@ -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 diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/App.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/App.qml new file mode 100644 index 0000000..dab1194 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/App.qml @@ -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 + } + } + +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/Borders.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/Borders.qml new file mode 100644 index 0000000..0964973 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/Borders.qml @@ -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 + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/Controls.ui.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/Controls.ui.qml new file mode 100644 index 0000000..57ed53f --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/Controls.ui.qml @@ -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__ + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/ListViewStyled.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/ListViewStyled.qml new file mode 100644 index 0000000..218d4ad --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/ListViewStyled.qml @@ -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 + } + } +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/Monitor.ui.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/Monitor.ui.qml new file mode 100644 index 0000000..3bda0cb --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/Monitor.ui.qml @@ -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 +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/Settings.ui.qml b/pkg/experimental/qstreampanel/qml/QStreamContent/Settings.ui.qml new file mode 100644 index 0000000..724005d --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/Settings.ui.qml @@ -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 +} diff --git a/pkg/experimental/qstreampanel/qml/QStreamContent/fonts/fonts.txt b/pkg/experimental/qstreampanel/qml/QStreamContent/fonts/fonts.txt new file mode 100644 index 0000000..ab96122 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/QStreamContent/fonts/fonts.txt @@ -0,0 +1 @@ +Fonts in this folder are loaded automatically. diff --git a/pkg/experimental/qstreampanel/qml/main.go b/pkg/experimental/qstreampanel/qml/main.go new file mode 100644 index 0000000..5cb11d7 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/main.go @@ -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() {} diff --git a/pkg/experimental/qstreampanel/qml/qtquickcontrols2.conf b/pkg/experimental/qstreampanel/qml/qtquickcontrols2.conf new file mode 100644 index 0000000..7292357 --- /dev/null +++ b/pkg/experimental/qstreampanel/qml/qtquickcontrols2.conf @@ -0,0 +1,8 @@ +[Controls] +Style=Basic + +[Material] +Theme=Dark + +[Universal] +Theme=Dark diff --git a/pkg/hyprspace/config/config.go b/pkg/hyprspace/config/config.go new file mode 100644 index 0000000..b24e15c --- /dev/null +++ b/pkg/hyprspace/config/config.go @@ -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 +} diff --git a/pkg/hyprspace/up.go b/pkg/hyprspace/up.go new file mode 100644 index 0000000..1ef0b5b --- /dev/null +++ b/pkg/hyprspace/up.go @@ -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) + } +} diff --git a/pkg/hyprspace/vpn.go b/pkg/hyprspace/vpn.go new file mode 100644 index 0000000..26d8277 --- /dev/null +++ b/pkg/hyprspace/vpn.go @@ -0,0 +1,7 @@ +package hyprspace + +type VPN struct{} + +func New() *VPN { + return &VPN{} +} diff --git a/pkg/streamcontrol/config.go b/pkg/streamcontrol/config.go index 326b215..feddb52 100644 --- a/pkg/streamcontrol/config.go +++ b/pkg/streamcontrol/config.go @@ -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{}, } } diff --git a/pkg/streamcontrol/obs/obs.go b/pkg/streamcontrol/obs/obs.go index 09e0aea..38e56f1 100644 --- a/pkg/streamcontrol/obs/obs.go +++ b/pkg/streamcontrol/obs/obs.go @@ -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{ diff --git a/pkg/streamcontrol/twitch/twitch.go b/pkg/streamcontrol/twitch/twitch.go index b75fe18..3ebf83f 100644 --- a/pkg/streamcontrol/twitch/twitch.go +++ b/pkg/streamcontrol/twitch/twitch.go @@ -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 { diff --git a/pkg/streamcontrol/youtube/youtube.go b/pkg/streamcontrol/youtube/youtube.go index 26d6028..48d28b8 100644 --- a/pkg/streamcontrol/youtube/youtube.go +++ b/pkg/streamcontrol/youtube/youtube.go @@ -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,12 +240,22 @@ func (yt *YouTube) IterateUpcomingBroadcasts( callback func(broadcast *youtube.LiveBroadcast) error, parts ...string, ) error { - broadcasts, err := yt.YouTubeService.LiveBroadcasts. - List(append([]string{"id"}, parts...)). - BroadcastStatus("upcoming"). - Context(ctx).Do() - if err != nil { - return fmt.Errorf("unable to get the list of active broadcasts: %w", err) + 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 { @@ -199,14 +271,23 @@ func (yt *YouTube) IterateActiveBroadcasts( callback func(broadcast *youtube.LiveBroadcast) error, parts ...string, ) error { - broadcasts, err := yt.YouTubeService.LiveBroadcasts. - List(append([]string{"id"}, parts...)). - BroadcastStatus("active"). - Context(ctx).Do() - if err != nil { - return fmt.Errorf("unable to get the list of active broadcasts: %w", err) + 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,16 +789,23 @@ func (yt *YouTube) Flush( func (yt *YouTube) ListStreams( ctx context.Context, ) ([]*youtube.LiveStream, error) { - response, err := yt.YouTubeService. - LiveStreams. - List([]string{"id", "snippet", "cdn", "status"}). - Mine(true). - MaxResults(20). - Context(ctx).Do() - if err != nil { - return nil, fmt.Errorf("unable to query the list of streams: %w", err) + 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 } - return response.Items, nil } type LiveBroadcast = youtube.LiveBroadcast @@ -698,14 +813,49 @@ type LiveBroadcast = youtube.LiveBroadcast func (yt *YouTube) ListBroadcasts( ctx context.Context, ) ([]*youtube.LiveBroadcast, error) { - response, err := yt.YouTubeService. - LiveBroadcasts. - List([]string{"id", "snippet", "contentDetails", "monetizationDetails", "status"}). - Mine(true). - MaxResults(1000). - Context(ctx).Do() - if err != nil { - return nil, fmt.Errorf("unable to query the list of broadcasts: %w", err) + 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 } - 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 } diff --git a/pkg/streamd/client/client.go b/pkg/streamd/client/client.go index 0680e17..f3ca2fe 100644 --- a/pkg/streamd/client/client.go +++ b/pkg/streamd/client/client.go @@ -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 } diff --git a/pkg/streamd/config/config.go b/pkg/streamd/config/config.go index e8f453d..97e5704 100644 --- a/pkg/streamd/config/config.go +++ b/pkg/streamd/config/config.go @@ -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 { - CachePath *string `yaml:"cache_path"` - Commands struct { - OnStartStream string `yaml:"on_start_stream"` - OnStopStream string `yaml:"on_stop_stream"` - } +type config struct { + CachePath *string `yaml:"cache_path"` 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 +} diff --git a/pkg/streamd/config/read.go b/pkg/streamd/config/read.go new file mode 100644 index 0000000..7692b85 --- /dev/null +++ b/pkg/streamd/config/read.go @@ -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 +} diff --git a/pkg/streamd/config/write.go b/pkg/streamd/config/write.go new file mode 100644 index 0000000..fd89f7e --- /dev/null +++ b/pkg/streamd/config/write.go @@ -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 +} diff --git a/pkg/streamd/git_storage.go b/pkg/streamd/git_storage.go index 33e1cc0..fa9bfac 100644 --- a/pkg/streamd/git_storage.go +++ b/pkg/streamd/git_storage.go @@ -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 diff --git a/pkg/streamd/grpc/go/streamd_grpc/streamd.pb.go b/pkg/streamd/grpc/go/streamd_grpc/streamd.pb.go index 8297658..2a77f39 100644 --- a/pkg/streamd/grpc/go/streamd_grpc/streamd.pb.go +++ b/pkg/streamd/grpc/go/streamd_grpc/streamd.pb.go @@ -617,7 +617,8 @@ type GetStreamStatusRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PlatID string `protobuf:"bytes,1,opt,name=platID,proto3" json:"platID,omitempty"` + 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, }, diff --git a/pkg/streamd/grpc/go/streamd_grpc/streamd_grpc.pb.go b/pkg/streamd/grpc/go/streamd_grpc/streamd_grpc.pb.go index f20e3e9..fdf808d 100644 --- a/pkg/streamd/grpc/go/streamd_grpc/streamd_grpc.pb.go +++ b/pkg/streamd/grpc/go/streamd_grpc/streamd_grpc.pb.go @@ -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", } diff --git a/pkg/streamd/grpc/streamd.proto b/pkg/streamd/grpc/streamd.proto index 2c9a0c8..7989e9e 100644 --- a/pkg/streamd/grpc/streamd.proto +++ b/pkg/streamd/grpc/streamd.proto @@ -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; +} diff --git a/pkg/streamd/server/grpc.go b/pkg/streamd/server/grpc.go index 0aa7615..f07ecc7 100644 --- a/pkg/streamd/server/grpc.go +++ b/pkg/streamd/server/grpc.go @@ -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" @@ -19,27 +26,153 @@ import ( type GRPCServer struct { streamd_grpc.StreamDServer - StreamD api.StreamD + 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) func NewGRPCServer(streamd api.StreamD) *GRPCServer { return &GRPCServer{ - StreamD: streamd, + 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) +} diff --git a/pkg/streamd/stream_controller.go b/pkg/streamd/stream_controller.go index a99ecf6..7b012e1 100644 --- a/pkg/streamd/stream_controller.go +++ b/pkg/streamd/stream_controller.go @@ -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 diff --git a/pkg/streamd/streamd.go b/pkg/streamd/streamd.go index e326237..0a0cc78 100644 --- a/pkg/streamd/streamd.go +++ b/pkg/streamd/streamd.go @@ -27,12 +27,14 @@ type StreamControllers struct { YouTube *youtube.YouTube } +type SaveConfigFunc func(context.Context, config.Config) error + type StreamD struct { UI ui.UI - ConfigPath string - ConfigLock sync.Mutex - Config config.Config + SaveConfigFunc SaveConfigFunc + ConfigLock sync.Mutex + Config config.Config CacheLock sync.Mutex Cache *cache.Cache @@ -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, - Cache: &cache.Cache{}, + UI: ui, + 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) diff --git a/pkg/streampanel/config/config.go b/pkg/streampanel/config/config.go new file mode 100644 index 0000000..b4c7d3b --- /dev/null +++ b/pkg/streampanel/config/config.go @@ -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 +} diff --git a/pkg/streampanel/config/custom_config_key.go b/pkg/streampanel/config/custom_config_key.go new file mode 100644 index 0000000..2a22ad6 --- /dev/null +++ b/pkg/streampanel/config/custom_config_key.go @@ -0,0 +1,4 @@ +package config + +const CustomConfigKeyOnStreamStart = "on_start_stream" +const CustomConfigKeyOnStreamStop = "on_stop_stream" diff --git a/pkg/streampanel/config/options.go b/pkg/streampanel/config/options.go new file mode 100644 index 0000000..b078b0f --- /dev/null +++ b/pkg/streampanel/config/options.go @@ -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) +} diff --git a/pkg/streampanel/config/read.go b/pkg/streampanel/config/read.go new file mode 100644 index 0000000..3f0f2cf --- /dev/null +++ b/pkg/streampanel/config/read.go @@ -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 +} diff --git a/pkg/streampanel/config/write.go b/pkg/streampanel/config/write.go new file mode 100644 index 0000000..f44f412 --- /dev/null +++ b/pkg/streampanel/config/write.go @@ -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 +} diff --git a/pkg/streampanel/options.go b/pkg/streampanel/options.go index 75fdb03..cb0d973 100644 --- a/pkg/streampanel/options.go +++ b/pkg/streampanel/options.go @@ -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 diff --git a/pkg/streampanel/panel.go b/pkg/streampanel/panel.go index 601ace5..7e099c2 100644 --- a/pkg/streampanel/panel.go +++ b/pkg/streampanel/panel.go @@ -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 { +) (*Panel, error) { + configPath, err := getExpandedConfigPath(configPath) + if err != nil { + return nil, fmt.Errorf("unable to get the path to the config file: %w", err) + } + + 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).Config(), - } -} - -func NewRemote( - remoteAddr string, - opts ...Option, -) *Panel { - return &Panel{ - remoteAddr: remoteAddr, - config: Options(opts).Config(), - } + 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() - if err != nil { - return fmt.Errorf("unable to get the path to the data file: %w", err) - } + var err error + p.StreamD, err = streamd.New( + p.config.BuiltinStreamD, + p, + func(ctx context.Context, cfg streamdconfig.Config) error { + p.config.BuiltinStreamD = cfg - p.StreamD, err = streamd.New(dataPath, p, belt.CtxBelt(ctx)) + err = config.WriteConfigToPath(ctx, p.configPath, p.config) + if err != nil { + return fmt.Errorf("unable to save the config: %w", err) + } + + 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,21 +1224,23 @@ func (p *Panel) initMainWindow(ctx context.Context) { p.mainWindow = w p.profilesListWidget = profilesList - go func() { - p.getUpdatedStatus(ctx) - - t := time.NewTicker(5 * time.Second) - for { - select { - case <-ctx.Done(): - t.Stop() - return - case <-t.C: - } - + if _, ok := p.StreamD.(*client.Client); ok { + go func() { p.getUpdatedStatus(ctx) - } - }() + + t := time.NewTicker(1 * time.Second) + for { + select { + case <-ctx.Done(): + t.Stop() + return + case <-t.C: + } + + p.getUpdatedStatus(ctx) + } + }() + } } func (p *Panel) getSelectedProfile() Profile { @@ -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,46 +1507,54 @@ func cleanYoutubeRecordingName(in string) string { func (p *Panel) editProfileWindow(ctx context.Context) fyne.Window { oldProfile := getProfile(p.configCache, *p.selectedProfileName) - return p.profileWindow( - ctx, - fmt.Sprintf("Edit the profile '%s'", oldProfile.Name), - oldProfile, - func(ctx context.Context, profile Profile) error { - if err := p.profileCreateOrUpdate(ctx, profile); err != nil { - return fmt.Errorf("unable to create profile '%s': %w", profile.Name, err) - } - if profile.Name != oldProfile.Name { - if err := p.profileDelete(ctx, oldProfile.Name); err != nil { - return fmt.Errorf("unable to delete profile '%s': %w", oldProfile.Name, err) + var w fyne.Window + p.waitForResponse(func() { + w = p.profileWindow( + ctx, + fmt.Sprintf("Edit the profile '%s'", oldProfile.Name), + oldProfile, + func(ctx context.Context, profile Profile) error { + if err := p.profileCreateOrUpdate(ctx, profile); err != nil { + return fmt.Errorf("unable to create profile '%s': %w", profile.Name, err) } - } - p.profilesListWidget.UnselectAll() - return nil - }, - ) + if profile.Name != oldProfile.Name { + if err := p.profileDelete(ctx, oldProfile.Name); err != nil { + return fmt.Errorf("unable to delete profile '%s': %w", oldProfile.Name, err) + } + } + p.profilesListWidget.UnselectAll() + return nil + }, + ) + }) + return w } func (p *Panel) cloneProfileWindow(ctx context.Context) fyne.Window { oldProfile := getProfile(p.configCache, *p.selectedProfileName) - return p.profileWindow( - ctx, - "Create a profile", - oldProfile, - func(ctx context.Context, profile Profile) error { - if oldProfile.Name == profile.Name { - return fmt.Errorf("profile with name '%s' already exists", profile.Name) - } - if err := p.profileCreateOrUpdate(ctx, profile); err != nil { - return err - } - p.profilesListWidget.UnselectAll() - return nil - }, - ) + var w fyne.Window + p.waitForResponse(func() { + w = p.profileWindow( + ctx, + "Create a profile", + oldProfile, + func(ctx context.Context, profile Profile) error { + if oldProfile.Name == profile.Name { + return fmt.Errorf("profile with name '%s' already exists", profile.Name) + } + if err := p.profileCreateOrUpdate(ctx, profile); err != nil { + return err + } + p.profilesListWidget.UnselectAll() + 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,29 +1583,33 @@ func (p *Panel) deleteProfileWindow(ctx context.Context) fyne.Window { } func (p *Panel) newProfileWindow(ctx context.Context) fyne.Window { - return p.profileWindow( - ctx, - "Create a profile", - Profile{}, - func(ctx context.Context, profile Profile) error { - found := false - for _, platCfg := range p.configCache.Backends { - _, ok := platCfg.GetStreamProfile(profile.Name) - if ok { - found = true - break + var w fyne.Window + p.waitForResponse(func() { + w = p.profileWindow( + ctx, + "Create a profile", + Profile{}, + func(ctx context.Context, profile Profile) error { + found := false + for _, platCfg := range p.configCache.Backends { + _, ok := platCfg.GetStreamProfile(profile.Name) + if ok { + found = true + break + } } - } - if found { - return fmt.Errorf("profile with name '%s' already exists", profile.Name) - } - if err := p.profileCreateOrUpdate(ctx, profile); err != nil { - return err - } - p.profilesListWidget.UnselectAll() - return nil - }, - ) + if found { + return fmt.Errorf("profile with name '%s' already exists", profile.Name) + } + if err := p.profileCreateOrUpdate(ctx, profile); err != nil { + return err + } + p.profilesListWidget.UnselectAll() + 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 +} diff --git a/pkg/streampanel/streamd.go b/pkg/streampanel/streamd.go deleted file mode 100644 index a770d3a..0000000 --- a/pkg/streampanel/streamd.go +++ /dev/null @@ -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) -} diff --git a/pkg/streampanel/update_timer_handler.go b/pkg/streampanel/update_timer_handler.go index 134f9cf..fe23253 100644 --- a/pkg/streampanel/update_timer_handler.go +++ b/pkg/streampanel/update_timer_handler.go @@ -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 }