Initial commit, pt. 36

This commit is contained in:
Dmitrii Okunev
2024-07-06 18:40:44 +01:00
parent 924c093376
commit 99b4f2927c
16 changed files with 333 additions and 122 deletions

View File

@@ -12,10 +12,14 @@ import (
"sync"
"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/facebookincubator/go-belt/tool/logger/implementation/logrus"
"github.com/getsentry/sentry-go"
"github.com/spf13/pflag"
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
"github.com/xaionaro-go/streamctl/pkg/observability"
"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"
@@ -36,7 +40,7 @@ func main() {
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
pflag.Parse()
l := zap.Default().WithLevel(loggerLevel)
l := logrus.Default().WithLevel(loggerLevel)
if *cpuProfile != "" {
f, err := os.Create(*cpuProfile)
@@ -129,8 +133,29 @@ func main() {
l.Fatalf("unable to initialize the streamd instance: %v", err)
}
l := l
if streamD.Config.SentryDSN != "" {
l.Infof("setting up Sentry at DSN '%s'", streamD.Config.SentryDSN)
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
Dsn: streamD.Config.SentryDSN,
})
if err != nil {
l.Fatal(err)
}
sentryErrorMonitor := errmonsentry.New(sentryClient)
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
sentryErrorMonitor,
))
ctx = logger.CtxWithLogger(ctx, l)
logger.Default = func() logger.Logger {
return l
}
}
if err = streamD.Run(ctx); err != nil {
l.Fatalf("streamd exited with error: %v", err)
l.Errorf("streamd returned an error: %v", err)
}
listener, err := net.Listen("tcp", *listenAddr)

View File

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

View File

@@ -13,9 +13,10 @@ import (
"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/facebookincubator/go-belt/tool/logger/implementation/logrus"
"github.com/getsentry/sentry-go"
"github.com/spf13/pflag"
"github.com/xaionaro-go/streamctl/pkg/observability"
"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"
@@ -35,7 +36,7 @@ func main() {
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)
l := logrus.Default().WithLevel(loggerLevel)
if *cpuProfile != "" {
f, err := os.Create(*cpuProfile)
@@ -87,17 +88,29 @@ func main() {
listener.Close()
}()
var opts []streampanel.Option
if *remoteAddr != "" {
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
}
if *sentryDSN != "" {
opts = append(opts, streampanel.OptionSentryDSN(*sentryDSN))
}
panel, panelErr := streampanel.New(*configPath, opts...)
if panel.Config.SentryDSN != "" {
l.Infof("setting up Sentry at DSN '%s'", panel.Config.SentryDSN)
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
Dsn: *sentryDSN,
Dsn: panel.Config.SentryDSN,
})
if err != nil {
l.Fatal(err)
}
sentryErrorMonitor := errmonsentry.New(sentryClient)
errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
l.WithHooks(&ErrorMonitorLoggerHook{
ErrorMonitor: sentryErrorMonitor,
})
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
sentryErrorMonitor,
))
}
ctx = logger.CtxWithLogger(ctx, l)
logger.Default = func() logger.Logger {
@@ -105,13 +118,8 @@ func main() {
}
defer belt.Flush(ctx)
var opts []streampanel.Option
if *remoteAddr != "" {
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
}
panel, err := streampanel.New(*configPath, opts...)
if err != nil {
l.Fatal(err)
if panelErr != nil {
l.Fatal(panelErr)
}
if *listenAddr != "" {

4
go.mod
View File

@@ -8,7 +8,7 @@ toolchain go1.22.3
replace github.com/goccy/go-yaml v1.11.3 => github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b
require (
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef
github.com/go-git/go-billy/v5 v5.5.0
github.com/goccy/go-yaml v1.11.3
github.com/hashicorp/go-multierror v1.1.1
@@ -57,6 +57,7 @@ require (
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/xatomic v0.0.0-20230519181013-85c0ec87e55f // indirect
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-text/render v0.1.0 // indirect
@@ -149,6 +150,7 @@ require (
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/sirupsen/logrus v1.9.3 // 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

8
go.sum
View File

@@ -149,8 +149,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38 h1:6bNfYFYry8V+BHinBMDEPPnd19unWa76lQhsx88xU6k=
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38/go.mod h1:GX1P3GiO+6E2SxTZaI3z1cqlTGfE1GnKxEsxBVemVnY=
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef h1:ZsOEnpQjCQAF4cPK0gwt/fN65sbOg1e7zbfXIX4iMQ4=
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef/go.mod h1:BLkOQHXT14GLgKvM22LB/fDcq1stjijrTOJQEk3xrUA=
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=
@@ -211,6 +211,8 @@ github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2 h1:UkoycH6lT7QfBw3LqH
github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2/go.mod h1:bVEceuoz83G4yjq9Os7lCYe+lf46uY8EFEHkxSCywvM=
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7 h1:Ng6QMSlQSB+goG6430/Fp7O4YO2BJZXZJaldtg+7kEc=
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7/go.mod h1:QUXmOopthsqLYJ+rAybuCf16J7qQm60TLVdQR0w1Nus=
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f h1:lifmuIc/S/hzVoStbEAsdryJ6qBSNeXXb9NseP0vlfs=
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f/go.mod h1:+3P6aQ4zDVR6jGPnXq3g/7kHnKLB73EsnGBhM0adWhA=
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335 h1:N17hl+3/Zqxg3SM+33Q792qUHDoWwt4M7tVuOVpLspc=
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=
@@ -649,6 +651,8 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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=

View File

@@ -0,0 +1,191 @@
package observability
import (
"bytes"
"fmt"
"runtime"
"github.com/DataDog/gostackparse"
"github.com/facebookincubator/go-belt"
"github.com/facebookincubator/go-belt/pkg/field"
xruntime "github.com/facebookincubator/go-belt/pkg/runtime"
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
errmontypes "github.com/facebookincubator/go-belt/tool/experimental/errmon/types"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/facebookincubator/go-belt/tool/logger/adapter"
loggertypes "github.com/facebookincubator/go-belt/tool/logger/types"
)
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
SendChan chan ErrorMonitorMessage
}
func NewErrorMonitorLoggerHook(
errorMonitor errmon.ErrorMonitor,
) *ErrorMonitorLoggerHook {
result := &ErrorMonitorLoggerHook{
ErrorMonitor: errorMonitor,
SendChan: make(chan ErrorMonitorMessage, 10),
}
go result.senderLoop()
return result
}
var _ loggertypes.PreHook = (*ErrorMonitorLoggerHook)(nil)
func (h *ErrorMonitorLoggerHook) ProcessInput(traceIDs belt.TraceIDs, level loggertypes.Level, args ...any) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,
}
}
emitter := &mockEmitter{}
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
l.Log(level, args...)
h.sendReport(emitter.LastEntry)
return loggertypes.PreHookResult{
Skip: false,
}
}
func (h *ErrorMonitorLoggerHook) ProcessInputf(traceIDs belt.TraceIDs, level loggertypes.Level, format string, args ...any) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,
}
}
emitter := &mockEmitter{}
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
l.Logf(level, format, args...)
h.sendReport(emitter.LastEntry)
return loggertypes.PreHookResult{
Skip: false,
}
}
func (h *ErrorMonitorLoggerHook) ProcessInputFields(traceIDs belt.TraceIDs, level loggertypes.Level, message string, fields field.AbstractFields) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,
}
}
emitter := &mockEmitter{}
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
l.LogFields(level, message, fields)
h.sendReport(emitter.LastEntry)
return loggertypes.PreHookResult{
Skip: false,
}
}
func copyEntry(entry *loggertypes.Entry) *loggertypes.Entry {
entryDup := *entry
if entry.Fields != nil {
fields := make(field.Fields, 0, entry.Fields.Len())
entry.Fields.ForEachField(func(f *field.Field) bool {
fields = append(fields, *f)
return true
})
entryDup.Fields = fields
}
return &entryDup
}
type ErrorMonitorMessage struct {
Entry *loggertypes.Entry
Goroutines []errmontypes.Goroutine
CurrentGoroutineID int
StackTrace xruntime.PCs
}
func (h *ErrorMonitorLoggerHook) sendReport(
entry *loggertypes.Entry,
) {
if entry == nil {
logger.Default().Errorf("an attempt to send through sentry a nil entry")
return
}
logger.Default().Tracef("sending through sentry entry: %#+v", *entry)
entryDup := copyEntry(entry)
goroutines, currentGoroutineID := getGoroutines()
stackTrace := xruntime.CallerStackTrace(nil)
select {
case h.SendChan <- ErrorMonitorMessage{
Entry: entryDup,
Goroutines: goroutines,
CurrentGoroutineID: currentGoroutineID,
StackTrace: stackTrace,
}:
default:
logger.Default().Errorf("unable to send an error to Sentry, the channel is busy")
return
}
}
func (h *ErrorMonitorLoggerHook) senderLoop() {
for {
message := <-h.SendChan
h.ErrorMonitor.Emitter().Emit(&errmontypes.Event{
Entry: *message.Entry,
ID: "",
ExternalIDs: []any{},
Exception: errmontypes.Exception{
IsPanic: message.Entry.Level <= loggertypes.LevelPanic,
Error: fmt.Errorf("[%s] %s", message.Entry.Level, message.Entry.Message),
StackTrace: message.StackTrace,
},
CurrentGoroutineID: message.CurrentGoroutineID,
Goroutines: message.Goroutines,
})
}
}

View File

@@ -0,0 +1,14 @@
package observability
import logger "github.com/facebookincubator/go-belt/tool/logger/types"
type mockEmitter struct {
LastEntry *logger.Entry
}
var _ logger.Emitter = (*mockEmitter)(nil)
func (e *mockEmitter) Emit(entry *logger.Entry) {
e.LastEntry = entry
}
func (e *mockEmitter) Flush() {}

View File

@@ -183,6 +183,9 @@ func (c *abstractStreamController) StreamProfileType() reflect.Type {
}
func ToAbstract[T StreamProfile](c StreamController[T]) AbstractStreamController {
if c == nil {
return nil
}
var zeroProfile T
profileType := reflect.TypeOf(zeroProfile)
return &abstractStreamController{

View File

@@ -219,6 +219,15 @@ func getToken(ctx context.Context, cfg Config) (*oauth2.Token, error) {
func (yt *YouTube) Ping(ctx context.Context) error {
counter := 0
for {
if yt == nil {
return fmt.Errorf("yt is nil")
}
if yt.YouTubeService == nil {
return fmt.Errorf("yt.YouTubeService == nil")
}
if yt.YouTubeService.I18nLanguages == nil {
return fmt.Errorf("yt.YouTubeService.I18nLanguages == nil")
}
_, err := yt.YouTubeService.I18nLanguages.List(nil).Context(ctx).Do()
logger.Debugf(ctx, "YouTube.I18nLanguages result: %v", err)
if err != nil {

View File

@@ -30,6 +30,7 @@ type config struct {
GitRepo GitRepoConfig
Backends streamcontrol.Config
ProfileMetadata map[streamcontrol.ProfileName]ProfileMetadata
SentryDSN string `yaml:"sentry_dsn"`
}
type Config config

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/hashicorp/go-multierror"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
@@ -22,6 +23,7 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
sort.Slice(platNames, func(i, j int) bool {
return platNames[i] < platNames[j]
})
var result *multierror.Error
for _, platName := range platNames {
var err error
switch strings.ToLower(string(platName)) {
@@ -32,11 +34,14 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
case strings.ToLower(string(youtube.ID)):
err = d.initYouTubeBackend(ctx)
}
if err != nil && err != ErrSkipBackend {
return fmt.Errorf("unable to initialize '%s': %w", platName, err)
if err == ErrSkipBackend {
continue
}
if err != nil {
result = multierror.Append(result, fmt.Errorf("unable to initialize '%s': %w", platName, err))
}
}
return nil
return result.ErrorOrNil()
}
var ErrSkipBackend = streamd.ErrSkipBackend

View File

@@ -50,7 +50,12 @@ type StreamD struct {
var _ api.StreamD = (*StreamD)(nil)
func New(config config.Config, ui ui.UI, saveCfgFunc SaveConfigFunc, 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)
d := &StreamD{
@@ -417,16 +422,27 @@ func (d *StreamD) Restart(ctx context.Context) error {
func (d *StreamD) streamController(
platID streamcontrol.PlatformName,
) (streamcontrol.AbstractStreamController, error) {
var result streamcontrol.AbstractStreamController
switch platID {
case obs.ID:
return streamcontrol.ToAbstract(d.StreamControllers.OBS), nil
if d.StreamControllers.OBS != nil {
result = streamcontrol.ToAbstract(d.StreamControllers.OBS)
}
case twitch.ID:
return streamcontrol.ToAbstract(d.StreamControllers.Twitch), nil
if d.StreamControllers.Twitch != nil {
result = streamcontrol.ToAbstract(d.StreamControllers.Twitch)
}
case youtube.ID:
return streamcontrol.ToAbstract(d.StreamControllers.YouTube), nil
if d.StreamControllers.YouTube != nil {
result = streamcontrol.ToAbstract(d.StreamControllers.YouTube)
}
default:
return nil, fmt.Errorf("unexpected platform ID: '%s'", platID)
}
if result == nil {
return nil, fmt.Errorf("controller '%s' is not initialized", platID)
}
return result, nil
}
func (d *StreamD) GetStreamStatus(
ctx context.Context,
@@ -437,6 +453,10 @@ func (d *StreamD) GetStreamStatus(
return nil, err
}
if c == nil {
return nil, fmt.Errorf("controller '%s' is not initialized", platID)
}
return c.GetStreamStatus(ctx)
}

View File

@@ -10,6 +10,7 @@ import (
)
type Config struct {
SentryDSN string `yaml:"sentry_dsn"`
RemoteStreamDAddr string `yaml:"streamd_remote"`
BuiltinStreamD streamd.Config `yaml:"streamd_builtin"`
}

View File

@@ -18,3 +18,9 @@ type OptionRemoteStreamDAddr string
func (o OptionRemoteStreamDAddr) Apply(cfg *Config) {
cfg.RemoteStreamDAddr = string(o)
}
type OptionSentryDSN string
func (o OptionSentryDSN) Apply(cfg *Config) {
cfg.SentryDSN = string(o)
}

View File

@@ -6,3 +6,4 @@ type Config = config.Config
type Option = config.Option
type Options = config.Options
type OptionRemoteStreamDAddr = config.OptionRemoteStreamDAddr
type OptionSentryDSN = config.OptionSentryDSN

View File

@@ -51,7 +51,7 @@ type Panel struct {
StreamD api.StreamD
app fyne.App
config Config
Config Config
streamMutex sync.Mutex
updateTimerHandler *updateTimerHandler
profilesOrder []streamcontrol.ProfileName
@@ -100,7 +100,7 @@ func New(
return &Panel{
configPath: configPath,
config: Options(opts).ApplyOverrides(cfg),
Config: Options(opts).ApplyOverrides(cfg),
}, nil
}
@@ -117,11 +117,11 @@ func (p *Panel) Loop(ctx context.Context) error {
}
p.defaultContext = ctx
logger.Debug(ctx, "config", p.config)
logger.Debug(ctx, "config", p.Config)
if p.config.RemoteStreamDAddr != "" {
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)
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.Config.RemoteStreamDAddr, err)
}
} else {
if err := p.initBuiltinStreamD(ctx); err != nil {
@@ -133,7 +133,7 @@ func (p *Panel) Loop(ctx context.Context) error {
go func() {
var loadingWindow fyne.Window
if p.config.RemoteStreamDAddr == "" {
if p.Config.RemoteStreamDAddr == "" {
loadingWindow = p.newLoadingWindow(ctx)
resizeWindow(loadingWindow, fyne.NewSize(600, 600))
loadingWindowText := widget.NewRichTextFromMarkdown("")
@@ -183,7 +183,7 @@ func (p *Panel) Loop(ctx context.Context) error {
p.DisplayError(err)
}
if p.config.RemoteStreamDAddr == "" {
if p.Config.RemoteStreamDAddr == "" {
logger.Tracef(ctx, "hiding the loading window")
loadingWindow.Hide()
}
@@ -212,12 +212,12 @@ func getExpandedConfigPath(configPath string) (string, error) {
func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
var err error
p.StreamD, err = streamd.New(
p.config.BuiltinStreamD,
p.Config.BuiltinStreamD,
p,
func(ctx context.Context, cfg streamdconfig.Config) error {
p.config.BuiltinStreamD = cfg
p.Config.BuiltinStreamD = cfg
err = config.WriteConfigToPath(ctx, p.configPath, p.config)
err = config.WriteConfigToPath(ctx, p.configPath, p.Config)
if err != nil {
return fmt.Errorf("unable to save the config: %w", err)
}
@@ -234,7 +234,7 @@ func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
}
func (p *Panel) initRemoteStreamD(context.Context) error {
p.StreamD = client.New(p.config.RemoteStreamDAddr)
p.StreamD = client.New(p.Config.RemoteStreamDAddr)
return nil
}