move all internal call to eventbus

This commit is contained in:
aynakeya
2026-02-19 11:10:42 +08:00
parent daae9b7cc4
commit 0fa54c6346
14 changed files with 594 additions and 156 deletions

View File

@@ -8,45 +8,65 @@ import (
)
var EventsMapping = map[string]any{
CmdLiveRoomAdd: CmdLiveRoomAddData{},
LiveRoomProviderUpdate: LiveRoomProviderUpdateEvent{},
CmdLiveRoomRemove: CmdLiveRoomRemoveData{},
UpdateLiveRoomRooms: UpdateLiveRoomRoomsData{},
UpdateLiveRoomStatus: UpdateLiveRoomStatusData{},
CmdLiveRoomConfigChange: CmdLiveRoomConfigChangeData{},
CmdLiveRoomOperation: CmdLiveRoomOperationData{},
PlayerVolumeChangeCmd: PlayerVolumeChangeCmdEvent{},
PlayerPlayCmd: PlayerPlayCmdEvent{},
PlayerPlayErrorUpdate: PlayerPlayErrorUpdateEvent{},
PlayerSeekCmd: PlayerSeekCmdEvent{},
PlayerToggleCmd: PlayerToggleCmdEvent{},
PlayerSetPauseCmd: PlayerSetPauseCmdEvent{},
PlayerPlayNextCmd: PlayerPlayNextCmdEvent{},
CmdGetCurrentLyric: CmdGetCurrentLyricData{},
UpdateCurrentLyric: UpdateCurrentLyricData{},
PlayerLyricPosUpdate: PlayerLyricPosUpdateEvent{},
PlayerPlayingUpdate: PlayerPlayingUpdateEvent{},
PlayerPropertyPauseUpdate: PlayerPropertyPauseUpdateEvent{},
PlayerPropertyPercentPosUpdate: PlayerPropertyPercentPosUpdateEvent{},
PlayerPropertyStateUpdate: PlayerPropertyStateUpdateEvent{},
PlayerPropertyTimePosUpdate: PlayerPropertyTimePosUpdateEvent{},
PlayerPropertyDurationUpdate: PlayerPropertyDurationUpdateEvent{},
PlayerPropertyVolumeUpdate: PlayerPropertyVolumeUpdateEvent{},
PlayerVideoPlayerSetWindowHandleCmd: PlayerVideoPlayerSetWindowHandleCmdEvent{},
PlayerSetAudioDeviceCmd: PlayerSetAudioDeviceCmdEvent{},
PlayerAudioDeviceUpdate: PlayerAudioDeviceUpdateEvent{},
PlaylistManagerSetSystemCmd: PlaylistManagerSetSystemCmdEvent{},
PlaylistManagerSystemUpdate: PlaylistManagerSystemUpdateEvent{},
PlaylistManagerRefreshCurrentCmd: PlaylistManagerRefreshCurrentCmdEvent{},
PlaylistManagerGetCurrentCmd: PlaylistManagerGetCurrentCmdEvent{},
PlaylistManagerCurrentUpdate: PlaylistManagerCurrentUpdateEvent{},
PlaylistManagerInfoUpdate: PlaylistManagerInfoUpdateEvent{},
PlaylistManagerAddPlaylistCmd: PlaylistManagerAddPlaylistCmdEvent{},
PlaylistManagerRemovePlaylistCmd: PlaylistManagerRemovePlaylistCmdEvent{},
MediaProviderUpdate: MediaProviderUpdateEvent{},
CmdMiaosicSearch: CmdMiaosicSearchData{},
ReplyMiaosicSearch: ReplyMiaosicSearchData{},
GUISetPlayerWindowOpenCmd: GUISetPlayerWindowOpenCmdEvent{},
CmdLiveRoomAdd: CmdLiveRoomAddData{},
LiveRoomProviderUpdate: LiveRoomProviderUpdateEvent{},
CmdLiveRoomRemove: CmdLiveRoomRemoveData{},
UpdateLiveRoomRooms: UpdateLiveRoomRoomsData{},
UpdateLiveRoomStatus: UpdateLiveRoomStatusData{},
CmdLiveRoomConfigChange: CmdLiveRoomConfigChangeData{},
CmdLiveRoomOperation: CmdLiveRoomOperationData{},
PlayerVolumeChangeCmd: PlayerVolumeChangeCmdEvent{},
PlayerPlayCmd: PlayerPlayCmdEvent{},
PlayerPlayErrorUpdate: PlayerPlayErrorUpdateEvent{},
PlayerSeekCmd: PlayerSeekCmdEvent{},
PlayerToggleCmd: PlayerToggleCmdEvent{},
PlayerSetPauseCmd: PlayerSetPauseCmdEvent{},
PlayerPlayNextCmd: PlayerPlayNextCmdEvent{},
CmdGetCurrentLyric: CmdGetCurrentLyricData{},
UpdateCurrentLyric: UpdateCurrentLyricData{},
PlayerLyricPosUpdate: PlayerLyricPosUpdateEvent{},
PlayerPlayingUpdate: PlayerPlayingUpdateEvent{},
PlayerPropertyPauseUpdate: PlayerPropertyPauseUpdateEvent{},
PlayerPropertyPercentPosUpdate: PlayerPropertyPercentPosUpdateEvent{},
PlayerPropertyStateUpdate: PlayerPropertyStateUpdateEvent{},
PlayerPropertyTimePosUpdate: PlayerPropertyTimePosUpdateEvent{},
PlayerPropertyDurationUpdate: PlayerPropertyDurationUpdateEvent{},
PlayerPropertyVolumeUpdate: PlayerPropertyVolumeUpdateEvent{},
PlayerVideoPlayerSetWindowHandleCmd: PlayerVideoPlayerSetWindowHandleCmdEvent{},
PlayerSetAudioDeviceCmd: PlayerSetAudioDeviceCmdEvent{},
PlayerAudioDeviceUpdate: PlayerAudioDeviceUpdateEvent{},
PlaylistManagerSetSystemCmd: PlaylistManagerSetSystemCmdEvent{},
PlaylistManagerSystemUpdate: PlaylistManagerSystemUpdateEvent{},
PlaylistManagerRefreshCurrentCmd: PlaylistManagerRefreshCurrentCmdEvent{},
PlaylistManagerGetCurrentCmd: PlaylistManagerGetCurrentCmdEvent{},
PlaylistManagerCurrentUpdate: PlaylistManagerCurrentUpdateEvent{},
PlaylistManagerInfoUpdate: PlaylistManagerInfoUpdateEvent{},
PlaylistManagerAddPlaylistCmd: PlaylistManagerAddPlaylistCmdEvent{},
PlaylistManagerRemovePlaylistCmd: PlaylistManagerRemovePlaylistCmdEvent{},
MediaProviderUpdate: MediaProviderUpdateEvent{},
CmdMiaosicListProviders: CmdMiaosicListProvidersData{},
ReplyMiaosicListProviders: ReplyMiaosicListProvidersData{},
CmdMiaosicMatchMediaByProvider: CmdMiaosicMatchMediaByProviderData{},
ReplyMiaosicMatchMediaByProvider: ReplyMiaosicMatchMediaByProviderData{},
CmdMiaosicSearch: CmdMiaosicSearchData{},
ReplyMiaosicSearch: ReplyMiaosicSearchData{},
CmdMiaosicGetMediaInfo: CmdMiaosicGetMediaInfoData{},
ReplyMiaosicGetMediaInfo: ReplyMiaosicGetMediaInfoData{},
CmdMiaosicGetMediaUrl: CmdMiaosicGetMediaUrlData{},
ReplyMiaosicGetMediaUrl: ReplyMiaosicGetMediaUrlData{},
CmdMiaosicQrLogin: CmdMiaosicQrLoginData{},
ReplyMiaosicQrLogin: ReplyMiaosicQrLoginData{},
CmdMiaosicQrLoginVerify: CmdMiaosicQrLoginVerifyData{},
ReplyMiaosicQrLoginVerify: ReplyMiaosicQrLoginVerifyData{},
CmdMiaosicLogoutByProvider: CmdMiaosicLogoutByProviderData{},
ReplyMiaosicLogoutByProvider: ReplyMiaosicLogoutByProviderData{},
CmdMiaosicIsLoginByProvider: CmdMiaosicIsLoginByProviderData{},
ReplyMiaosicIsLoginByProvider: ReplyMiaosicIsLoginByProviderData{},
CmdMiaosicRestoreSessionByProvider: CmdMiaosicRestoreSessionByProviderData{},
ReplyMiaosicRestoreSessionByProvider: ReplyMiaosicRestoreSessionByProviderData{},
CmdMiaosicSaveSessionByProvider: CmdMiaosicSaveSessionByProviderData{},
ReplyMiaosicSaveSessionByProvider: ReplyMiaosicSaveSessionByProviderData{},
GUISetPlayerWindowOpenCmd: GUISetPlayerWindowOpenCmdEvent{},
}
func init() {

View File

@@ -2,6 +2,35 @@ package events
import "github.com/AynaLivePlayer/miaosic"
const CmdMiaosicListProviders = "cmd.miaosic.listProviders"
type CmdMiaosicListProvidersData struct{}
const ReplyMiaosicListProviders = "reply.miaosic.listProviders"
type MiaosicProviderInfo struct {
Name string `json:"name"`
Loginable bool `json:"loginable"`
}
type ReplyMiaosicListProvidersData struct {
Providers []MiaosicProviderInfo `json:"providers"`
}
const CmdMiaosicMatchMediaByProvider = "cmd.miaosic.matchMediaByProvider"
type CmdMiaosicMatchMediaByProviderData struct {
Provider string `json:"provider"`
Keyword string `json:"keyword"`
}
const ReplyMiaosicMatchMediaByProvider = "reply.miaosic.matchMediaByProvider"
type ReplyMiaosicMatchMediaByProviderData struct {
Meta miaosic.MetaData `json:"meta"`
Found bool `json:"found"`
}
const CmdMiaosicGetMediaInfo = "cmd.miaosic.getMediaInfo"
type CmdMiaosicGetMediaInfoData struct {
@@ -55,3 +84,54 @@ type ReplyMiaosicQrLoginVerifyData struct {
Result miaosic.QrLoginResult `json:"result"`
Error error `json:"error"`
}
const CmdMiaosicLogoutByProvider = "cmd.miaosic.logoutByProvider"
type CmdMiaosicLogoutByProviderData struct {
Provider string `json:"provider"`
}
const ReplyMiaosicLogoutByProvider = "reply.miaosic.logoutByProvider"
type ReplyMiaosicLogoutByProviderData struct {
Error error `json:"error"`
}
const CmdMiaosicIsLoginByProvider = "cmd.miaosic.isLoginByProvider"
type CmdMiaosicIsLoginByProviderData struct {
Provider string `json:"provider"`
}
const ReplyMiaosicIsLoginByProvider = "reply.miaosic.isLoginByProvider"
type ReplyMiaosicIsLoginByProviderData struct {
IsLogin bool `json:"is_login"`
Error error `json:"error"`
}
const CmdMiaosicRestoreSessionByProvider = "cmd.miaosic.restoreSessionByProvider"
type CmdMiaosicRestoreSessionByProviderData struct {
Provider string `json:"provider"`
Session string `json:"session"`
}
const ReplyMiaosicRestoreSessionByProvider = "reply.miaosic.restoreSessionByProvider"
type ReplyMiaosicRestoreSessionByProviderData struct {
Error error `json:"error"`
}
const CmdMiaosicSaveSessionByProvider = "cmd.miaosic.saveSessionByProvider"
type CmdMiaosicSaveSessionByProviderData struct {
Provider string `json:"provider"`
}
const ReplyMiaosicSaveSessionByProvider = "reply.miaosic.saveSessionByProvider"
type ReplyMiaosicSaveSessionByProviderData struct {
Session string `json:"session"`
Error error `json:"error"`
}

View File

@@ -3,5 +3,6 @@ package events
const MediaProviderUpdate = "update.media.provider.update"
type MediaProviderUpdateEvent struct {
Providers []string
Providers []string
ProviderInfos []MiaosicProviderInfo
}

View File

@@ -15,4 +15,5 @@ const ReplyMiaosicSearch = "update.search_result"
type ReplyMiaosicSearchData struct {
Medias []model.Media
Error error
}

4
go.mod
View File

@@ -12,7 +12,7 @@ replace (
)
require (
fyne.io/fyne/v2 v2.7.1
fyne.io/fyne/v2 v2.7.2
github.com/AynaLivePlayer/liveroom-sdk v0.1.0
github.com/AynaLivePlayer/miaosic v0.2.5
github.com/adrg/libvlc-go/v3 v3.1.6
@@ -38,7 +38,7 @@ require (
)
require (
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 // indirect
fyne.io/systray v1.12.0 // indirect
github.com/AynaLivePlayer/blivedm-go v0.0.0-20251109134927-cc4a4ca07110 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3 // indirect

8
go.sum
View File

@@ -1,7 +1,7 @@
fyne.io/fyne/v2 v2.7.1 h1:ja7rNHWWEooha4XBIZNnPP8tVFwmTfwMJdpZmLxm2Zc=
fyne.io/fyne/v2 v2.7.1/go.mod h1:xClVlrhxl7D+LT+BWYmcrW4Nf+dJTvkhnPgji7spAwE=
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlFYSruNlXD8bRHDiqm0VNI=
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
fyne.io/fyne/v2 v2.7.2 h1:XiNpWkn0PzX43ZCjbb0QYGg1RCxVbugwfVgikWZBCMw=
fyne.io/fyne/v2 v2.7.2/go.mod h1:PXbqY3mQmJV3J1NRUR2VbVgUUx3vgvhuFJxyjRK/4Ug=
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/AynaLivePlayer/blivedm-go v0.0.0-20251109134927-cc4a4ca07110 h1:A6IBTHcv/Pisf65FAsM4oxN1OXpVjpIYlVXDugCIuiw=
github.com/AynaLivePlayer/blivedm-go v0.0.0-20251109134927-cc4a4ca07110/go.mod h1:CtiYF0MDMCzrPUuM96b2194ANTtRxnA/YdtJNdAdxuQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

View File

@@ -36,6 +36,7 @@ func Initialize() {
log = global.Logger.WithPrefix("MediaProvider")
loadMediaProvider()
handleProvider()
handleSearch()
handleInfo()
createLyricLoader()
@@ -43,6 +44,7 @@ func Initialize() {
_ = global.EventBus.Publish(
events.MediaProviderUpdate, events.MediaProviderUpdateEvent{
Providers: miaosic.ListAvailableProviders(),
Providers: miaosic.ListAvailableProviders(),
ProviderInfos: listProviderInfos(),
})
}

View File

@@ -12,28 +12,8 @@ func handleSourceLogin() {
events.CmdMiaosicQrLogin, "internal.media_provider.qrlogin_handler", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicQrLoginData)
log.Infof("trying login %s", data.Provider)
pvdr, ok := miaosic.GetProvider(data.Provider)
if !ok {
_ = global.EventBus.Reply(
event, events.ReplyMiaosicQrLogin,
events.ReplyMiaosicQrLoginData{
Session: miaosic.QrLoginSession{},
Error: miaosic.ErrorNoSuchProvider,
})
return
}
result, ok := pvdr.(miaosic.Loginable)
if !ok {
_ = global.EventBus.Reply(
event, events.ReplyMiaosicQrLogin,
events.ReplyMiaosicQrLoginData{
Session: miaosic.QrLoginSession{},
Error: miaosic.ErrNotImplemented,
})
return
}
var session miaosic.QrLoginSession
sess, err := result.QrLogin()
sess, err := miaosic.QrLoginByProvider(data.Provider)
if err == nil && sess != nil {
session = *sess
}
@@ -51,28 +31,8 @@ func handleSourceLogin() {
events.CmdMiaosicQrLoginVerify, "internal.media_provider.qrloginverify_handler", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicQrLoginVerifyData)
log.Infof("trying login verify %s", data.Provider)
pvdr, ok := miaosic.GetProvider(data.Provider)
if !ok {
_ = global.EventBus.Reply(
event, events.ReplyMiaosicQrLoginVerify,
events.ReplyMiaosicQrLoginVerifyData{
Result: miaosic.QrLoginResult{},
Error: miaosic.ErrorNoSuchProvider,
})
return
}
loginable, ok := pvdr.(miaosic.Loginable)
if !ok {
_ = global.EventBus.Reply(
event, events.ReplyMiaosicQrLoginVerify,
events.ReplyMiaosicQrLoginVerifyData{
Result: miaosic.QrLoginResult{},
Error: miaosic.ErrNotImplemented,
})
return
}
var result miaosic.QrLoginResult
res, err := loginable.QrLoginVerify(&data.Session)
res, err := miaosic.QrLoginVerifyByProvider(data.Provider, &data.Session)
if err == nil && res != nil {
result = *res
}
@@ -86,4 +46,60 @@ func handleSourceLogin() {
if err != nil {
log.ErrorW("Subscribe miaosic qrloginverify failed", "error", err)
}
err = global.EventBus.Subscribe("",
events.CmdMiaosicLogoutByProvider, "internal.media_provider.logout_by_provider", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicLogoutByProviderData)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicLogoutByProvider,
events.ReplyMiaosicLogoutByProviderData{
Error: miaosic.LogoutByProvider(data.Provider),
})
})
if err != nil {
log.ErrorW("Subscribe miaosic logout failed", "error", err)
}
err = global.EventBus.Subscribe("",
events.CmdMiaosicIsLoginByProvider, "internal.media_provider.is_login_by_provider", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicIsLoginByProviderData)
isLogin, loginErr := miaosic.IsLoginByProvider(data.Provider)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicIsLoginByProvider,
events.ReplyMiaosicIsLoginByProviderData{
IsLogin: isLogin,
Error: loginErr,
})
})
if err != nil {
log.ErrorW("Subscribe miaosic is login failed", "error", err)
}
err = global.EventBus.Subscribe("",
events.CmdMiaosicRestoreSessionByProvider, "internal.media_provider.restore_session_by_provider", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicRestoreSessionByProviderData)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicRestoreSessionByProvider,
events.ReplyMiaosicRestoreSessionByProviderData{
Error: miaosic.RestoreSessionByProvider(data.Provider, data.Session),
})
})
if err != nil {
log.ErrorW("Subscribe miaosic restore session failed", "error", err)
}
err = global.EventBus.Subscribe("",
events.CmdMiaosicSaveSessionByProvider, "internal.media_provider.save_session_by_provider", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicSaveSessionByProviderData)
session, sessionErr := miaosic.SaveSessionByProvider(data.Provider)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicSaveSessionByProvider,
events.ReplyMiaosicSaveSessionByProviderData{
Session: session,
Error: sessionErr,
})
})
if err != nil {
log.ErrorW("Subscribe miaosic save session failed", "error", err)
}
}

View File

@@ -0,0 +1,54 @@
package source
import (
"AynaLivePlayer/core/events"
"AynaLivePlayer/global"
"AynaLivePlayer/pkg/eventbus"
"github.com/AynaLivePlayer/miaosic"
)
func listProviderInfos() []events.MiaosicProviderInfo {
providers := make([]events.MiaosicProviderInfo, 0)
for _, providerName := range miaosic.ListAvailableProviders() {
p, ok := miaosic.GetProvider(providerName)
if !ok {
continue
}
_, loginable := p.(miaosic.Loginable)
providers = append(providers, events.MiaosicProviderInfo{
Name: providerName,
Loginable: loginable,
})
}
return providers
}
func handleProvider() {
err := global.EventBus.Subscribe("",
events.CmdMiaosicListProviders, "internal.media_provider.list_providers", func(event *eventbus.Event) {
providers := listProviderInfos()
_ = global.EventBus.Reply(
event, events.ReplyMiaosicListProviders,
events.ReplyMiaosicListProvidersData{
Providers: providers,
})
})
if err != nil {
log.ErrorW("Subscribe list providers event failed", "error", err)
}
err = global.EventBus.Subscribe("",
events.CmdMiaosicMatchMediaByProvider, "internal.media_provider.match_media_by_provider", func(event *eventbus.Event) {
data := event.Data.(events.CmdMiaosicMatchMediaByProviderData)
meta, found := miaosic.MatchMediaByProvider(data.Provider, data.Keyword)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicMatchMediaByProvider,
events.ReplyMiaosicMatchMediaByProviderData{
Meta: meta,
Found: found,
})
})
if err != nil {
log.ErrorW("Subscribe match media by provider event failed", "error", err)
}
}

View File

@@ -16,6 +16,12 @@ func handleSearch() {
searchResult, err := miaosic.SearchByProvider(data.Provider, data.Keyword, 1, 10)
if err != nil {
log.Warnf("Search %s using %s failed: %s", data.Keyword, data.Provider, err)
_ = global.EventBus.Reply(
event, events.ReplyMiaosicSearch,
events.ReplyMiaosicSearchData{
Medias: make([]model.Media, 0),
Error: err,
})
return
}
medias := make([]model.Media, len(searchResult))
@@ -29,6 +35,7 @@ func handleSearch() {
event, events.ReplyMiaosicSearch,
events.ReplyMiaosicSearchData{
Medias: medias,
Error: nil,
})
})
if err != nil {

View File

@@ -3,6 +3,9 @@ package eventbus
import (
"errors"
"fmt"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
@@ -51,6 +54,10 @@ type bus struct {
// logger
log Logger
// worker context markers for deadlock detection in Call()
workerCtxMu sync.RWMutex
workerCtx map[uint64]int // goroutine id -> worker idx
}
// New creates a new Bus.
@@ -75,22 +82,23 @@ func New(opts ...Option) Bus {
pending: make([]*Event, 0, 16),
echoWaiter: make(map[string]chan *Event),
log: option.log,
workerCtx: make(map[uint64]int),
}
for i := 0; i < option.maxWorkerSize; i++ {
b.addWorker()
b.addWorker(i)
}
return b
}
func (b *bus) addWorker() {
func (b *bus) addWorker(workerIdx int) {
b.mu.Lock()
q := make(chan task, b.queueSize)
b.queues = append(b.queues, q)
go b.workerLoop(q)
go b.workerLoop(workerIdx, q)
b.mu.Unlock()
}
func (b *bus) workerLoop(q chan task) {
func (b *bus) workerLoop(workerIdx int, q chan task) {
for {
select {
case <-b.stopCh:
@@ -105,7 +113,14 @@ func (b *bus) workerLoop(q chan task) {
}
case t := <-q:
func() {
gid := curGID()
b.workerCtxMu.Lock()
b.workerCtx[gid] = workerIdx
b.workerCtxMu.Unlock()
defer func() {
b.workerCtxMu.Lock()
delete(b.workerCtx, gid)
b.workerCtxMu.Unlock()
if r := recover(); r != nil {
b.log.Printf("handler panic recovered: event=%s handler=%s panic=%v", t.ev.Id, t.h.name, r)
}
@@ -301,6 +316,9 @@ func (b *bus) Call(eventId string, subEvtId string, data interface{}) (*Event, e
if eventId == "" {
return nil, errors.New("empty eventId")
}
if b.willDeadlockOnCall(eventId) {
return nil, fmt.Errorf("potential deadlock detected: sync Call(%s) from same worker shard", eventId)
}
echo := b.nextEchoId()
wait := make(chan *Event, 1)
@@ -328,6 +346,40 @@ func (b *bus) Call(eventId string, subEvtId string, data interface{}) (*Event, e
}
}
func (b *bus) willDeadlockOnCall(eventId string) bool {
gid := curGID()
b.workerCtxMu.RLock()
currentWorker, inWorker := b.workerCtx[gid]
b.workerCtxMu.RUnlock()
if !inWorker {
return false
}
b.mu.RLock()
targetWorker, hasWorker := b.workerIdxes[eventId]
b.mu.RUnlock()
if !hasWorker {
return false
}
return currentWorker == targetWorker
}
func curGID() uint64 {
var buf [64]byte
n := runtime.Stack(buf[:], false)
// first line format: "goroutine 123 [running]:\n"
line := strings.TrimPrefix(string(buf[:n]), "goroutine ")
space := strings.IndexByte(line, ' ')
if space <= 0 {
return 0
}
id, err := strconv.ParseUint(line[:space], 10, 64)
if err != nil {
return 0
}
return id
}
func (b *bus) Reply(req *Event, eventId string, data interface{}) error {
return b.PublishEvent(&Event{
Id: eventId,

View File

@@ -243,6 +243,37 @@ func TestCall(t *testing.T) {
require.Equal(t, "response to my-data", resp.Data)
}
func TestCall_FastFailOnPotentialDeadlock(t *testing.T) {
b := New(WithMaxWorkerSize(1), WithQueueSize(10))
err := b.Start()
require.NoError(t, err)
defer b.Stop()
// Ensure target event has a worker shard assignment.
err = b.Subscribe("", "inner-request", "inner-responder", func(event *Event) {
_ = b.Reply(event, "inner-response", "ok")
})
require.NoError(t, err)
done := make(chan error, 1)
err = b.Subscribe("", "outer-request", "outer-handler", func(event *Event) {
_, callErr := b.Call("inner-request", "inner-response", nil)
done <- callErr
})
require.NoError(t, err)
err = b.Publish("outer-request", nil)
require.NoError(t, err)
select {
case callErr := <-done:
require.Error(t, callErr)
require.Contains(t, callErr.Error(), "potential deadlock detected")
case <-time.After(2 * time.Second):
t.Fatal("outer handler did not finish in time")
}
}
// TestCall_StopDuringWait checks that Call returns an error if the bus is stopped while waiting.
func TestCall_StopDuringWait(t *testing.T) {
b := New(WithMaxWorkerSize(1), WithQueueSize(10))

View File

@@ -168,20 +168,24 @@ func (d *Diange) Enable() error {
}
d.isCurrentSystem = (!data.Media.IsLiveRoomUser()) && (data.Media.ToUser().Name == model.SystemUser.Name)
})
// check if all available source has a command in sourceConfigs, if not add this source to sourceConfigs
// actually, if default config exists, then this code does nothing.
prvdrs := miaosic.ListAvailableProviders()
for _, pvdr := range prvdrs {
// found pvdr in command list
if _, ok := d.sourceConfigs[pvdr]; ok {
continue
}
d.sourceConfigs[pvdr] = &sourceConfig{
Enable: true,
Command: "点" + pvdr + "歌",
Priority: len(d.sourceConfigs) + 1,
}
}
global.EventBus.Subscribe("",
events.MediaProviderUpdate,
"plugin.diange.provider.update",
func(event *eventbus.Event) {
// check if all available source has a command in sourceConfigs, if not add this source to sourceConfigs
// actually, if default config exists, then this code does nothing.
data := event.Data.(events.MediaProviderUpdateEvent)
for _, pvdr := range data.Providers {
if _, ok := d.sourceConfigs[pvdr]; ok {
continue
}
d.sourceConfigs[pvdr] = &sourceConfig{
Enable: true,
Command: "点" + pvdr + "歌",
Priority: len(d.sourceConfigs) + 1,
}
}
})
return nil
}
@@ -216,6 +220,22 @@ func (d *Diange) getSource(cmd string) []string {
return sources
}
func (d *Diange) searchByProvider(provider, keywords string) ([]model.Media, error) {
resp, err := global.EventBus.Call(
events.CmdMiaosicSearch,
events.ReplyMiaosicSearch,
events.CmdMiaosicSearchData{
Keyword: keywords,
Provider: provider,
},
)
if err != nil {
return nil, err
}
reply := resp.Data.(events.ReplyMiaosicSearchData)
return reply.Medias, reply.Error
}
func (d *Diange) handleMessage(event *eventbus.Event) {
message := event.Data.(events.LiveRoomMessageReceiveEvent).Message
msgs := strings.Split(message.Message, " ")
@@ -292,7 +312,20 @@ func (d *Diange) handleMessage(event *eventbus.Event) {
var mediaMeta miaosic.MetaData
found := false
for _, source := range sources {
mediaMeta, found = miaosic.MatchMediaByProvider(source, keywords)
resp, err := global.EventBus.Call(
events.CmdMiaosicMatchMediaByProvider,
events.ReplyMiaosicMatchMediaByProvider,
events.CmdMiaosicMatchMediaByProviderData{
Provider: source,
Keyword: keywords,
},
)
if err != nil {
continue
}
match := resp.Data.(events.ReplyMiaosicMatchMediaByProviderData)
mediaMeta = match.Meta
found = match.Found
if found {
break
}
@@ -302,22 +335,34 @@ func (d *Diange) handleMessage(event *eventbus.Event) {
if !found {
for _, source := range sources {
medias, err := miaosic.SearchByProvider(source, keywords, 1, 10)
medias, err := d.searchByProvider(source, keywords)
if len(medias) == 0 || err != nil {
continue
}
media = medias[0]
media = medias[0].Info
found = true
break
}
} else {
d.log.Info("Match media: ", mediaMeta)
m, err := miaosic.GetMediaInfo(mediaMeta)
resp, err := global.EventBus.Call(
events.CmdMiaosicGetMediaInfo,
events.ReplyMiaosicGetMediaInfo,
events.CmdMiaosicGetMediaInfoData{
Meta: mediaMeta,
},
)
if err != nil {
d.log.Error("Get media info failed: ", err)
found = false
} else {
reply := resp.Data.(events.ReplyMiaosicGetMediaInfoData)
if reply.Error != nil {
d.log.Error("Get media info failed: ", reply.Error)
found = false
}
media = reply.Info
}
media = m
}
if found {
@@ -410,11 +455,7 @@ func (d *Diange) CreatePanel() fyne.CanvasObject {
skipPlaylistCheck,
)
sourceCfgs := []fyne.CanvasObject{}
prvdrs := miaosic.ListAvailableProviders()
for source, cfg := range d.sourceConfigs {
if !slices.Contains(prvdrs, source) {
continue
}
sourceCfgs = append(
sourceCfgs, container.NewGridWithColumns(2,
widget.NewLabel(source),

View File

@@ -6,6 +6,7 @@ import (
"AynaLivePlayer/gui/component"
config2 "AynaLivePlayer/gui/views/config"
"AynaLivePlayer/pkg/config"
"AynaLivePlayer/pkg/eventbus"
"AynaLivePlayer/pkg/i18n"
"AynaLivePlayer/pkg/logger"
"AynaLivePlayer/resource"
@@ -55,15 +56,14 @@ func (w *SourceLogin) Enable() error {
func (w *SourceLogin) Disable() error {
w.log.Info("save session for all provider")
providers := miaosic.ListAvailableProviders()
for _, pname := range providers {
if p, ok := miaosic.GetProvider(pname); ok {
pl, ok2 := p.(miaosic.Loginable)
if ok2 {
w.log.Infof("save session for %s", pname)
w.sessions[pname] = pl.SaveSession()
}
for _, provider := range w.listLoginableProviders() {
w.log.Infof("save session for %s", provider)
session, err := w.saveSession(provider)
if err != nil {
w.log.Warnf("save session for %s failed: %v", provider, err)
continue
}
w.sessions[provider] = session
}
return nil
}
@@ -86,30 +86,16 @@ func (w *SourceLogin) CreatePanel() fyne.CanvasObject {
widget.NewLabel(i18n.T("plugin.sourcelogin.current_user")),
currentUser)
providers := miaosic.ListAvailableProviders()
loginableProviders := make([]string, 0)
loginables := make(map[string]miaosic.MediaProvider)
for _, pname := range providers {
if p, ok := miaosic.GetProvider(pname); ok {
pl, ok2 := p.(miaosic.Loginable)
if ok2 {
loginableProviders = append(loginableProviders, pname)
loginables[pname] = p
if session, ok3 := w.sessions[pname]; ok3 {
err := pl.RestoreSession(session)
if err != nil {
w.log.Error("failed to restore session for ", pname)
}
}
}
}
}
providerChoice := widget.NewSelect(loginableProviders, func(s string) {
providerChoice := widget.NewSelect([]string{}, func(s string) {
w.log.Info("switching provider to ", s)
if s != "" {
pvdr, _ := miaosic.GetProvider(s)
provider := pvdr.(miaosic.Loginable)
if provider.IsLogin() {
isLogin, err := w.isLogin(s)
if err != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: err})
return
}
if isLogin {
currentUser.SetText(i18n.T("plugin.sourcelogin.current_user.loggedin"))
} else {
currentUser.SetText(i18n.T("plugin.sourcelogin.current_user.notlogin"))
@@ -119,11 +105,49 @@ func (w *SourceLogin) CreatePanel() fyne.CanvasObject {
sourcePanel := container.NewGridWithColumns(2,
providerChoice, currentStatus)
restoredSessions := make(map[string]bool)
_ = global.EventBus.Subscribe("",
events.MediaProviderUpdate,
"plugin.sourcelogin.providers",
func(event *eventbus.Event) {
data := event.Data.(events.MediaProviderUpdateEvent)
loginableProviders := make([]string, 0)
for _, providerInfo := range data.ProviderInfos {
if providerInfo.Loginable {
loginableProviders = append(loginableProviders, providerInfo.Name)
}
}
for _, provider := range loginableProviders {
if restoredSessions[provider] {
continue
}
session, ok := w.sessions[provider]
if !ok || session == "" {
continue
}
restoredSessions[provider] = true
go func(providerName string, providerSession string) {
if err := w.restoreSession(providerName, providerSession); err != nil {
w.log.Warnf("failed to restore session for %s: %v", providerName, err)
}
}(provider, session)
}
fyne.DoAndWait(func() {
providerChoice.Options = loginableProviders
providerChoice.Refresh()
if providerChoice.Selected == "" && len(loginableProviders) > 0 {
providerChoice.SetSelected(loginableProviders[0])
}
})
})
logoutBtn := component.NewAsyncButton(
i18n.T("plugin.sourcelogin.logout"),
func() {
err := loginables[providerChoice.Selected].(miaosic.Loginable).Logout()
if providerChoice.Selected == "" {
return
}
err := w.logout(providerChoice.Selected)
if err != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: err})
@@ -153,14 +177,25 @@ func (w *SourceLogin) CreatePanel() fyne.CanvasObject {
qrStatus.SetText("")
})
w.log.Info("getting a new qr code for login")
pvdr, _ := miaosic.GetProvider(providerChoice.Selected)
provider := pvdr.(miaosic.Loginable)
currentLoginSession, err = provider.QrLogin()
resp, err := global.EventBus.Call(
events.CmdMiaosicQrLogin,
events.ReplyMiaosicQrLogin,
events.CmdMiaosicQrLoginData{
Provider: providerChoice.Selected,
},
)
if err != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: err})
return
}
qrData := resp.Data.(events.ReplyMiaosicQrLoginData)
if qrData.Error != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: qrData.Error})
return
}
currentLoginSession = &qrData.Session
w.log.Debugf("trying encode url %s to qrcode", currentLoginSession.Url)
data, err := qrcode.Encode(currentLoginSession.Url, qrcode.Medium, 256)
if err != nil {
@@ -186,26 +221,43 @@ func (w *SourceLogin) CreatePanel() fyne.CanvasObject {
if currentProvider == "" {
return
}
pvdr, _ := miaosic.GetProvider(currentProvider)
provider := pvdr.(miaosic.Loginable)
w.log.Info("checking qr status")
result, err := provider.QrLoginVerify(currentLoginSession)
resp, err := global.EventBus.Call(
events.CmdMiaosicQrLoginVerify,
events.ReplyMiaosicQrLoginVerify,
events.CmdMiaosicQrLoginVerifyData{
Provider: currentProvider,
Session: *currentLoginSession,
},
)
if err != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: err})
return
}
resultData := resp.Data.(events.ReplyMiaosicQrLoginVerifyData)
if resultData.Error != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: resultData.Error})
return
}
fyne.DoAndWait(func() {
qrStatus.SetText(result.Message)
qrStatus.SetText(resultData.Result.Message)
})
if result.Success {
if resultData.Result.Success {
currentLoginSession = nil
fyne.DoAndWait(func() {
qrcodeImg.Resource = resource.ImageEmptyQrCode
qrcodeImg.Refresh()
providerChoice.OnChanged(currentProvider)
})
w.sessions[currentProvider] = provider.SaveSession()
session, sessionErr := w.saveSession(currentProvider)
if sessionErr != nil {
_ = global.EventBus.Publish(events.ErrorUpdate,
events.ErrorUpdateEvent{Error: sessionErr})
return
}
w.sessions[currentProvider] = session
}
},
)
@@ -216,3 +268,84 @@ func (w *SourceLogin) CreatePanel() fyne.CanvasObject {
w.panel = container.NewVBox(sourcePanel, controlBox, qrImagePanel)
return w.panel
}
func (w *SourceLogin) listLoginableProviders() []string {
resp, err := global.EventBus.Call(
events.CmdMiaosicListProviders,
events.ReplyMiaosicListProviders,
events.CmdMiaosicListProvidersData{},
)
if err != nil {
w.log.Warnf("list providers failed: %v", err)
return []string{}
}
data := resp.Data.(events.ReplyMiaosicListProvidersData)
providers := make([]string, 0)
for _, provider := range data.Providers {
if provider.Loginable {
providers = append(providers, provider.Name)
}
}
return providers
}
func (w *SourceLogin) isLogin(provider string) (bool, error) {
resp, err := global.EventBus.Call(
events.CmdMiaosicIsLoginByProvider,
events.ReplyMiaosicIsLoginByProvider,
events.CmdMiaosicIsLoginByProviderData{
Provider: provider,
},
)
if err != nil {
return false, err
}
data := resp.Data.(events.ReplyMiaosicIsLoginByProviderData)
return data.IsLogin, data.Error
}
func (w *SourceLogin) logout(provider string) error {
resp, err := global.EventBus.Call(
events.CmdMiaosicLogoutByProvider,
events.ReplyMiaosicLogoutByProvider,
events.CmdMiaosicLogoutByProviderData{
Provider: provider,
},
)
if err != nil {
return err
}
data := resp.Data.(events.ReplyMiaosicLogoutByProviderData)
return data.Error
}
func (w *SourceLogin) restoreSession(provider, session string) error {
resp, err := global.EventBus.Call(
events.CmdMiaosicRestoreSessionByProvider,
events.ReplyMiaosicRestoreSessionByProvider,
events.CmdMiaosicRestoreSessionByProviderData{
Provider: provider,
Session: session,
},
)
if err != nil {
return err
}
data := resp.Data.(events.ReplyMiaosicRestoreSessionByProviderData)
return data.Error
}
func (w *SourceLogin) saveSession(provider string) (string, error) {
resp, err := global.EventBus.Call(
events.CmdMiaosicSaveSessionByProvider,
events.ReplyMiaosicSaveSessionByProvider,
events.CmdMiaosicSaveSessionByProviderData{
Provider: provider,
},
)
if err != nil {
return "", err
}
data := resp.Data.(events.ReplyMiaosicSaveSessionByProviderData)
return data.Session, data.Error
}