mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-05-09 01:51:06 +08:00
285 lines
6.7 KiB
Go
285 lines
6.7 KiB
Go
package m7s
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/logrusorgru/aurora/v4"
|
|
"github.com/mcuadros/go-defaults"
|
|
"gopkg.in/yaml.v3"
|
|
. "m7s.live/m7s/v5/pkg"
|
|
"m7s.live/m7s/v5/pkg/config"
|
|
"m7s.live/m7s/v5/pkg/util"
|
|
)
|
|
|
|
type DefaultYaml string
|
|
|
|
type PluginMeta struct {
|
|
Name string
|
|
Version string //插件版本
|
|
Type reflect.Type
|
|
defaultYaml DefaultYaml //默认配置
|
|
}
|
|
|
|
func (plugin *PluginMeta) Init(s *Server, userConfig map[string]any) {
|
|
instance := reflect.New(plugin.Type).Interface().(IPlugin)
|
|
defaults.SetDefaults(instance)
|
|
p := reflect.ValueOf(instance).Elem().FieldByName("Plugin").Addr().Interface().(*Plugin)
|
|
p.handler = instance
|
|
p.Meta = plugin
|
|
p.server = s
|
|
p.eventChan = make(chan any, 10)
|
|
p.Logger = s.Logger.With("plugin", plugin.Name)
|
|
p.Context, p.CancelCauseFunc = context.WithCancelCause(s.Context)
|
|
s.Plugins = append(s.Plugins, p)
|
|
if os.Getenv(strings.ToUpper(plugin.Name)+"_ENABLE") == "false" {
|
|
p.Disabled = true
|
|
p.Warn("disabled by env")
|
|
return
|
|
}
|
|
p.Config.Parse(p.GetCommonConf())
|
|
p.Config.Parse(instance, strings.ToUpper(plugin.Name))
|
|
for _, fname := range MergeConfigs {
|
|
if name := strings.ToLower(fname); p.Config.Has(name) {
|
|
p.Config.Get(name).ParseGlobal(s.Config.Get(name))
|
|
}
|
|
}
|
|
if plugin.defaultYaml != "" {
|
|
var defaultConf map[string]any
|
|
if err := yaml.Unmarshal([]byte(plugin.defaultYaml), &defaultConf); err != nil {
|
|
p.Error("parsing default config", "error", err)
|
|
} else {
|
|
p.Config.ParseDefaultYaml(defaultConf)
|
|
}
|
|
}
|
|
|
|
p.Config.ParseUserFile(userConfig)
|
|
if s.DisableAll {
|
|
p.Disabled = true
|
|
}
|
|
if userConfig["enable"] == false {
|
|
p.Disabled = true
|
|
} else if userConfig["enable"] == true {
|
|
p.Disabled = false
|
|
}
|
|
if p.Disabled {
|
|
p.Warn("plugin disabled")
|
|
} else {
|
|
p.assign()
|
|
}
|
|
p.Info("init", "version", plugin.Version)
|
|
instance.OnInit()
|
|
go p.Start()
|
|
}
|
|
|
|
type IPlugin interface {
|
|
OnInit()
|
|
OnEvent(any)
|
|
}
|
|
type ITCPPlugin interface {
|
|
OnTCPConnect(*net.TCPConn)
|
|
}
|
|
|
|
var plugins []PluginMeta
|
|
|
|
func InstallPlugin[C IPlugin](options ...any) error {
|
|
var c C
|
|
t := reflect.TypeOf(c).Elem()
|
|
meta := PluginMeta{
|
|
Name: strings.TrimSuffix(t.Name(), "Plugin"),
|
|
Type: t,
|
|
}
|
|
|
|
_, pluginFilePath, _, _ := runtime.Caller(1)
|
|
configDir := filepath.Dir(pluginFilePath)
|
|
|
|
if _, after, found := strings.Cut(configDir, "@"); found {
|
|
meta.Version = after
|
|
} else {
|
|
meta.Version = pluginFilePath
|
|
}
|
|
for _, option := range options {
|
|
switch v := option.(type) {
|
|
case DefaultYaml:
|
|
meta.defaultYaml = v
|
|
}
|
|
}
|
|
plugins = append(plugins, meta)
|
|
return nil
|
|
}
|
|
|
|
func sendPromiseToServer[T any](server *Server, value T) (err error) {
|
|
promise := util.NewPromise(value)
|
|
server.eventChan <- promise
|
|
<-promise.Done()
|
|
if err = context.Cause(promise.Context); err == util.ErrResolve {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
type Plugin struct {
|
|
Unit
|
|
Disabled bool
|
|
Meta *PluginMeta
|
|
eventChan chan any
|
|
config config.Common
|
|
config.Config
|
|
Publishers []*Publisher
|
|
handler IPlugin
|
|
server *Server
|
|
sync.RWMutex
|
|
}
|
|
|
|
func (p *Plugin) GetCommonConf() *config.Common {
|
|
return &p.config
|
|
}
|
|
|
|
func (opt *Plugin) settingPath() string {
|
|
return filepath.Join(opt.server.SettingDir, strings.ToLower(opt.Meta.Name)+".yaml")
|
|
}
|
|
|
|
func (p *Plugin) assign() {
|
|
f, err := os.Open(p.settingPath())
|
|
defer f.Close()
|
|
if err == nil {
|
|
var modifyConfig map[string]any
|
|
err = yaml.NewDecoder(f).Decode(&modifyConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
p.Config.ParseModifyFile(modifyConfig)
|
|
}
|
|
// p.registerHandler()
|
|
}
|
|
|
|
func (p *Plugin) Start() {
|
|
var err error
|
|
httpConf := p.config.HTTP
|
|
defer httpConf.StopListen()
|
|
if httpConf.ListenAddrTLS != "" && (httpConf.ListenAddrTLS != p.server.config.HTTP.ListenAddrTLS) {
|
|
go func() {
|
|
p.Info("https listen at ", "addr", aurora.Blink(httpConf.ListenAddrTLS))
|
|
p.Stop(httpConf.ListenTLS())
|
|
}()
|
|
}
|
|
if httpConf.ListenAddr != "" && (httpConf.ListenAddr != p.server.config.HTTP.ListenAddr) {
|
|
go func() {
|
|
p.Info("http listen at ", "addr", aurora.Blink(httpConf.ListenAddr))
|
|
p.Stop(httpConf.Listen())
|
|
}()
|
|
}
|
|
tcpConf := p.config.TCP
|
|
tcphandler, ok := p.handler.(ITCPPlugin)
|
|
if !ok {
|
|
tcphandler = p
|
|
}
|
|
count := p.config.TCP.ListenNum
|
|
if count == 0 {
|
|
count = runtime.NumCPU()
|
|
}
|
|
if p.config.TCP.ListenAddr != "" {
|
|
l, err := net.Listen("tcp", tcpConf.ListenAddr)
|
|
if err != nil {
|
|
p.Error("listen tcp", "addr", tcpConf.ListenAddr, "error", err)
|
|
p.Stop(err)
|
|
return
|
|
}
|
|
defer l.Close()
|
|
p.Info("listen tcp", "addr", tcpConf.ListenAddr)
|
|
for i := 0; i < count; i++ {
|
|
go tcpConf.Listen(l, tcphandler.OnTCPConnect)
|
|
}
|
|
}
|
|
if tcpConf.ListenAddrTLS != "" {
|
|
keyPair, _ := tls.X509KeyPair(config.LocalCert, config.LocalKey)
|
|
if tcpConf.CertFile != "" || tcpConf.KeyFile != "" {
|
|
keyPair, err = tls.LoadX509KeyPair(tcpConf.CertFile, tcpConf.KeyFile)
|
|
}
|
|
if err != nil {
|
|
p.Error("LoadX509KeyPair", "error", err)
|
|
p.Stop(err)
|
|
return
|
|
}
|
|
l, err := tls.Listen("tcp", tcpConf.ListenAddrTLS, &tls.Config{
|
|
Certificates: []tls.Certificate{keyPair},
|
|
})
|
|
if err != nil {
|
|
p.Error("listen tcp tls", "addr", tcpConf.ListenAddrTLS, "error", err)
|
|
p.Stop(err)
|
|
return
|
|
}
|
|
defer l.Close()
|
|
p.Info("listen tcp tls", "addr", tcpConf.ListenAddrTLS)
|
|
for i := 0; i < count; i++ {
|
|
go tcpConf.Listen(l, tcphandler.OnTCPConnect)
|
|
}
|
|
}
|
|
for {
|
|
select {
|
|
case event := <-p.eventChan:
|
|
// switch event.(type) {
|
|
// case *Subscriber:
|
|
// }
|
|
p.handler.OnEvent(event)
|
|
case <-p.Done():
|
|
return
|
|
default:
|
|
for i := 0; i < len(p.Publishers); i++ {
|
|
publisher := p.Publishers[i]
|
|
select {
|
|
case <-publisher.Done():
|
|
if publisher.Closer != nil {
|
|
publisher.Closer.Close()
|
|
}
|
|
p.Publishers = slices.Delete(p.Publishers, i, i+1)
|
|
i--
|
|
p.server.eventChan <- UnpublishEvent{Publisher: publisher}
|
|
case <-publisher.TimeoutTimer.C:
|
|
if err := publisher.timeout(); err != nil {
|
|
publisher.Stop(err)
|
|
}
|
|
default:
|
|
for subscriber := range publisher.Subscribers {
|
|
select {
|
|
case <-subscriber.Done():
|
|
subscriber.Publisher.RemoveSubscriber(subscriber)
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) OnEvent(event any) {
|
|
|
|
}
|
|
|
|
func (p *Plugin) OnTCPConnect(conn *net.TCPConn) {
|
|
p.handler.OnEvent(conn)
|
|
}
|
|
|
|
func (p *Plugin) Publish(streamPath string, options ...any) (publisher *Publisher, err error) {
|
|
publisher = &Publisher{Publish: p.config.Publish}
|
|
publisher.Init(p, streamPath, options...)
|
|
err = sendPromiseToServer(p.server, publisher)
|
|
return
|
|
}
|
|
|
|
func (p *Plugin) Subscribe(streamPath string, options ...any) (subscriber *Subscriber, err error) {
|
|
subscriber = &Subscriber{Subscribe: p.config.Subscribe}
|
|
subscriber.Init(p, streamPath, options...)
|
|
err = sendPromiseToServer(p.server, subscriber)
|
|
return
|
|
}
|