Disable buttons backing non-implemented functions

This commit is contained in:
Dmitrii Okunev
2024-10-31 00:11:04 +00:00
parent ebd71e92b1
commit 12e051c5b3
18 changed files with 1952 additions and 1657 deletions

View File

@@ -0,0 +1,11 @@
package streamcontrol
type Capability uint
const (
CapabilityUndefined = Capability(iota)
CapabilitySendChatMessage
CapabilityDeleteChatMessage
CapabilityBanUser
EndOfCapability
)

View File

@@ -179,3 +179,18 @@ func (k *Kick) StartStream(
logger.Warnf(ctx, "not implemented yet")
return nil
}
func (k *Kick) IsCapable(
ctx context.Context,
cap streamcontrol.Capability,
) bool {
switch cap {
case streamcontrol.CapabilitySendChatMessage:
return false
case streamcontrol.CapabilityDeleteChatMessage:
return false
case streamcontrol.CapabilityBanUser:
return false
}
return false
}

View File

@@ -284,3 +284,10 @@ func (obs *OBS) BanUser(
) error {
return fmt.Errorf("not implemented, yet")
}
func (obs *OBS) IsCapable(
ctx context.Context,
cap streamcontrol.Capability,
) bool {
return false
}

View File

@@ -131,6 +131,8 @@ type StreamControllerCommons interface {
SendChatMessage(ctx context.Context, message string) error
RemoveChatMessage(ctx context.Context, messageID ChatMessageID) error
BanUser(ctx context.Context, userID ChatUserID, reason string, deadline time.Time) error
IsCapable(context.Context, Capability) bool
}
type StreamController[ProfileType StreamProfile] interface {
@@ -241,6 +243,9 @@ func (c *abstractStreamController) RemoveChatMessage(ctx context.Context, messag
func (c *abstractStreamController) BanUser(ctx context.Context, userID ChatUserID, reason string, deadline time.Time) error {
return c.StreamController.BanUser(ctx, userID, reason, deadline)
}
func (c *abstractStreamController) IsCapable(ctx context.Context, cap Capability) bool {
return c.StreamController.IsCapable(ctx, cap)
}
func ToAbstract[T StreamProfile](c StreamController[T]) AbstractStreamController {
if c == nil {

View File

@@ -903,3 +903,18 @@ func (t *Twitch) BanUser(
})
return err
}
func (t *Twitch) IsCapable(
ctx context.Context,
cap streamcontrol.Capability,
) bool {
switch cap {
case streamcontrol.CapabilitySendChatMessage:
return true
case streamcontrol.CapabilityDeleteChatMessage:
return true
case streamcontrol.CapabilityBanUser:
return true
}
return false
}

View File

@@ -1400,3 +1400,18 @@ func (yt *YouTube) BanUser(
) error {
return fmt.Errorf("not implemented, yet")
}
func (yt *YouTube) IsCapable(
ctx context.Context,
cap streamcontrol.Capability,
) bool {
switch cap {
case streamcontrol.CapabilitySendChatMessage:
return true
case streamcontrol.CapabilityDeleteChatMessage:
return true
case streamcontrol.CapabilityBanUser:
return false
}
return false
}

View File

@@ -0,0 +1,10 @@
package api
import (
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
)
type BackendInfo struct {
Data any
Capabilities map[streamcontrol.Capability]struct{}
}

View File

@@ -66,10 +66,10 @@ type StreamD interface {
profile streamcontrol.AbstractStreamProfile,
customArgs ...any,
) error
GetBackendData(
GetBackendInfo(
ctx context.Context,
platID streamcontrol.PlatformName,
) (any, error)
) (*BackendInfo, error)
EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) error
GetStreamStatus(
ctx context.Context,

View File

@@ -26,9 +26,6 @@ import (
"github.com/xaionaro-go/streamctl/pkg/player"
"github.com/xaionaro-go/streamctl/pkg/player/protobuf/go/player_grpc"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
streamdconfig "github.com/xaionaro-go/streamctl/pkg/streamd/config"
@@ -710,10 +707,10 @@ func (c *Client) EndStream(
return err
}
func (c *Client) GetBackendData(
func (c *Client) GetBackendInfo(
ctx context.Context,
platID streamcontrol.PlatformName,
) (any, error) {
) (*api.BackendInfo, error) {
reply, err := withStreamDClient(ctx, c, func(
ctx context.Context,
client streamd_grpc.StreamDClient,
@@ -735,50 +732,17 @@ func (c *Client) GetBackendData(
)
}
var data any
switch platID {
case obs.ID:
_data := api.BackendDataOBS{}
err = json.Unmarshal(
[]byte(reply.GetData()),
&_data,
)
data = _data
case twitch.ID:
_data := api.BackendDataTwitch{}
err = json.Unmarshal(
[]byte(reply.GetData()),
&_data,
)
data = _data
case kick.ID:
_data := api.BackendDataKick{}
err = json.Unmarshal(
[]byte(reply.GetData()),
&_data,
)
data = _data
case youtube.ID:
_data := api.BackendDataYouTube{}
err = json.Unmarshal(
[]byte(reply.GetData()),
&_data,
)
data = _data
default:
return nil, fmt.Errorf(
"unknown platform: '%s'",
platID,
)
caps := goconv.CapabilitiesGRPC2Go(ctx, reply.Capabilities)
data, err := goconv.BackendDataGRPC2Go(platID, reply.GetData())
if err != nil {
return nil, fmt.Errorf("unable to deserialize data: %w", err)
}
if err != nil {
return nil, fmt.Errorf(
"unable to deserialize data: %w",
err,
)
}
return data, nil
return &api.BackendInfo{
Data: data,
Capabilities: caps,
}, nil
}
func (c *Client) Restart(ctx context.Context) error {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
package goconv
import (
"encoding/json"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
)
func BackendDataGRPC2Go(
platID streamcontrol.PlatformName,
dataString string,
) (any, error) {
var data any
var err error
switch platID {
case obs.ID:
_data := api.BackendDataOBS{}
err = json.Unmarshal([]byte(dataString), &_data)
data = _data
case twitch.ID:
_data := api.BackendDataTwitch{}
err = json.Unmarshal([]byte(dataString), &_data)
data = _data
case kick.ID:
_data := api.BackendDataKick{}
err = json.Unmarshal([]byte(dataString), &_data)
data = _data
case youtube.ID:
_data := api.BackendDataYouTube{}
err = json.Unmarshal([]byte(dataString), &_data)
data = _data
default:
return nil, fmt.Errorf(
"unknown platform: '%s'",
platID,
)
}
return data, err
}

View File

@@ -0,0 +1,57 @@
package goconv
import (
"context"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
)
func CapabilitiesGRPC2Go(
ctx context.Context,
caps []streamd_grpc.Capability,
) map[streamcontrol.Capability]struct{} {
m := map[streamcontrol.Capability]struct{}{}
for _, cap := range caps {
var r streamcontrol.Capability
switch cap {
case streamd_grpc.Capability_SendChatMessage:
r = streamcontrol.CapabilitySendChatMessage
case streamd_grpc.Capability_DeleteChatMessage:
r = streamcontrol.CapabilityDeleteChatMessage
case streamd_grpc.Capability_BanUser:
r = streamcontrol.CapabilityBanUser
default:
logger.Warnf(ctx, "unexpected capability: %v", cap)
continue
}
m[r] = struct{}{}
}
return m
}
func CapabilitiesGo2GRPC(
ctx context.Context,
caps map[streamcontrol.Capability]struct{},
) []streamd_grpc.Capability {
var result []streamd_grpc.Capability
for cap := range caps {
var item streamd_grpc.Capability
switch cap {
case streamcontrol.CapabilitySendChatMessage:
item = streamd_grpc.Capability_SendChatMessage
case streamcontrol.CapabilityDeleteChatMessage:
item = streamd_grpc.Capability_DeleteChatMessage
case streamcontrol.CapabilityBanUser:
item = streamd_grpc.Capability_BanUser
default:
logger.Warnf(ctx, "unexpected capability: %v", cap)
continue
}
result = append(result, item)
}
return result
}

View File

@@ -160,6 +160,7 @@ message GetBackendInfoRequest {
message GetBackendInfoReply {
bool isInitialized = 1;
string data = 2;
repeated Capability capabilities = 3;
}
message IsBackendEnabledRequest {
string platID = 1;
@@ -167,6 +168,12 @@ message IsBackendEnabledRequest {
message IsBackendEnabledReply {
bool isInitialized = 1;
}
enum Capability {
capabilityUndefined = 0;
SendChatMessage = 1;
DeleteChatMessage = 2;
BanUser = 3;
}
message RestartRequest {}
message RestartReply {}

View File

@@ -334,14 +334,15 @@ func (grpc *GRPCServer) GetBackendInfo(
err,
)
}
data, err := grpc.StreamD.GetBackendData(ctx, platID)
info, err := grpc.StreamD.GetBackendInfo(ctx, platID)
if err != nil {
return nil, fmt.Errorf(
"unable to get the backend info: %w",
err,
)
}
dataSerialized, err := json.Marshal(data)
dataSerialized, err := json.Marshal(info.Data)
if err != nil {
return nil, fmt.Errorf(
"unable to serialize the backend info: %w",
@@ -352,6 +353,7 @@ func (grpc *GRPCServer) GetBackendInfo(
return &streamd_grpc.GetBackendInfoReply{
IsInitialized: isEnabled,
Data: string(dataSerialized),
Capabilities: goconv.CapabilitiesGo2GRPC(ctx, info.Capabilities),
}, nil
}

View File

@@ -680,9 +680,37 @@ func (d *StreamD) EndStream(ctx context.Context, platID streamcontrol.PlatformNa
})
}
func (d *StreamD) GetBackendData(
func (d *StreamD) GetBackendInfo(
ctx context.Context,
platID streamcontrol.PlatformName,
) (*api.BackendInfo, error) {
ctrl, err := d.streamController(ctx, platID)
if err != nil {
return nil, fmt.Errorf("unable to get stream controller for platform '%s': %w", platID, err)
}
caps := map[streamcontrol.Capability]struct{}{}
for cap := streamcontrol.CapabilityUndefined + 1; cap < streamcontrol.EndOfCapability; cap++ {
isCapable := ctrl.IsCapable(ctx, cap)
if isCapable {
caps[cap] = struct{}{}
}
}
data, err := d.getBackendData(ctx, platID)
if err != nil {
return nil, fmt.Errorf("unable to get backend data: %w", err)
}
return &api.BackendInfo{
Data: data,
Capabilities: caps,
}, nil
}
func (d *StreamD) getBackendData(
_ context.Context,
platID streamcontrol.PlatformName,
) (any, error) {
switch platID {
case obs.ID:

View File

@@ -31,6 +31,9 @@ type chatUI struct {
MessagesHistoryLocker sync.Mutex
MessagesHistory []api.ChatMessage
CapabilitiesCacheLocker sync.Mutex
CapabilitiesCache map[streamcontrol.PlatformName]map[streamcontrol.Capability]struct{}
CurrentlyPlayingChatMessageSoundCount int32
// TODO: do not store ctx in a struct:
@@ -42,8 +45,9 @@ func newChatUI(
panel *Panel,
) (*chatUI, error) {
ui := &chatUI{
Panel: panel,
ctx: ctx,
Panel: panel,
CapabilitiesCache: make(map[streamcontrol.PlatformName]map[streamcontrol.Capability]struct{}),
ctx: ctx,
}
if err := ui.init(ctx); err != nil {
return nil, err
@@ -224,6 +228,28 @@ func (ui *chatUI) listCreateItem() fyne.CanvasObject {
)
}
func (ui *chatUI) getPlatformCapabilities(
ctx context.Context,
platID streamcontrol.PlatformName,
) (_ret map[streamcontrol.Capability]struct{}, _err error) {
logger.Debugf(ctx, "getPlatformCapabilities(ctx, '%s')", platID)
defer func() { logger.Debugf(ctx, "/getPlatformCapabilities(ctx, '%s'): %#+v, %v", platID, _ret, _err) }()
ui.CapabilitiesCacheLocker.Lock()
defer ui.CapabilitiesCacheLocker.Unlock()
if m, ok := ui.CapabilitiesCache[platID]; ok {
return m, nil
}
info, err := ui.Panel.StreamD.GetBackendInfo(ctx, platID)
if err != nil {
return nil, fmt.Errorf("GetBackendInfo returned error: %w", err)
}
ui.CapabilitiesCache[platID] = info.Capabilities
return info.Capabilities, nil
}
func (ui *chatUI) listUpdateItem(
rowID int,
obj fyne.CanvasObject,
@@ -234,6 +260,12 @@ func (ui *chatUI) listUpdateItem(
entryID := len(ui.MessagesHistory) - 1 - rowID
msg := ui.MessagesHistory[entryID]
platCaps, err := ui.getPlatformCapabilities(ctx, msg.Platform)
if err != nil {
ui.Panel.ReportError(fmt.Errorf("unable to get capabilities of platform '%s': %w", msg.Platform, err))
platCaps = map[streamcontrol.Capability]struct{}{}
}
containerPtr := obj.(*fyne.Container)
objs := containerPtr.Objects
label := objs[0].(*widget.Label)
@@ -252,6 +284,11 @@ func (ui *chatUI) listUpdateItem(
)
w.Show()
}
if _, ok := platCaps[streamcontrol.CapabilityBanUser]; !ok {
banUserButton.Disable()
} else {
banUserButton.Enable()
}
removeMsgButton := subContainer.Objects[1].(*widget.Button)
removeMsgButton.OnTapped = func() {
w := dialog.NewConfirm(
@@ -267,6 +304,11 @@ func (ui *chatUI) listUpdateItem(
)
w.Show()
}
if _, ok := platCaps[streamcontrol.CapabilityDeleteChatMessage]; !ok {
removeMsgButton.Disable()
} else {
removeMsgButton.Enable()
}
label.SetText(fmt.Sprintf(
"%s: %s: %s: %s",
msg.CreatedAt.Format("15:04"),

View File

@@ -3222,14 +3222,14 @@ func (p *Panel) profileWindow(
}
backendEnabled[backendID] = isEnabled
data, err := p.StreamD.GetBackendData(ctx, backendID)
info, err := p.StreamD.GetBackendInfo(ctx, backendID)
if err != nil {
w.Close()
p.DisplayError(fmt.Errorf("unable to get data of backend '%s': %w", backendID, err))
return nil
}
backendData[backendID] = data
backendData[backendID] = info.Data
}
_ = backendData[obs.ID].(api.BackendDataOBS)
dataTwitch := backendData[twitch.ID].(api.BackendDataTwitch)

View File

@@ -406,6 +406,7 @@ func (p *Panel) displayIncomingServers(
playButton := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
p.DisplayError(fmt.Errorf("playback is not implemented, yet"))
})
playButton.Hide() // TODO: unhide when it will be implemented
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
w := dialog.NewConfirm(
fmt.Sprintf("Delete incoming server %s ?", stream.StreamID),