chaturbate-dvr/channel/channel.go
2025-06-25 06:04:49 +08:00

150 lines
4.1 KiB
Go

package channel
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/teacat/chaturbate-dvr/entity"
"github.com/teacat/chaturbate-dvr/internal"
"github.com/teacat/chaturbate-dvr/server"
)
// Channel represents a channel instance.
type Channel struct {
CancelFunc context.CancelFunc
LogCh chan string
UpdateCh chan bool
IsOnline bool
StreamedAt int64
Duration float64 // Seconds
Filesize int // Bytes
Sequence int
Logs []string
File *os.File
Config *entity.ChannelConfig
}
// New creates a new channel instance with the given manager and configuration.
func New(conf *entity.ChannelConfig) *Channel {
ch := &Channel{
LogCh: make(chan string),
UpdateCh: make(chan bool),
Config: conf,
CancelFunc: func() {},
}
go ch.Publisher()
return ch
}
// Publisher listens for log messages and updates from the channel
// and publishes once received.
func (ch *Channel) Publisher() {
for {
select {
case v := <-ch.LogCh:
// Append the log message to ch.Logs and keep only the last 100 rows
ch.Logs = append(ch.Logs, v)
if len(ch.Logs) > 100 {
ch.Logs = ch.Logs[len(ch.Logs)-100:]
}
server.Manager.Publish(entity.EventLog, ch.ExportInfo())
case <-ch.UpdateCh:
server.Manager.Publish(entity.EventUpdate, ch.ExportInfo())
}
}
}
// WithCancel creates a new context with a cancel function,
// then stores the cancel function in the channel's CancelFunc field.
//
// This is used to cancel the context when the channel is stopped or paused.
func (ch *Channel) WithCancel(ctx context.Context) (context.Context, context.CancelFunc) {
ctx, ch.CancelFunc = context.WithCancel(ctx)
return ctx, ch.CancelFunc
}
// Info logs an informational message.
func (ch *Channel) Info(format string, a ...any) {
ch.LogCh <- fmt.Sprintf("%s [INFO] %s", time.Now().Format("15:04"), fmt.Sprintf(format, a...))
log.Printf(" INFO [%s] %s", ch.Config.Username, fmt.Sprintf(format, a...))
}
// Error logs an error message.
func (ch *Channel) Error(format string, a ...any) {
ch.LogCh <- fmt.Sprintf("%s [ERROR] %s", time.Now().Format("15:04"), fmt.Sprintf(format, a...))
log.Printf("ERROR [%s] %s", ch.Config.Username, fmt.Sprintf(format, a...))
}
// ExportInfo exports the channel information as a ChannelInfo struct.
func (ch *Channel) ExportInfo() *entity.ChannelInfo {
var filename string
if ch.File != nil {
filename = ch.File.Name()
}
var streamedAt string
if ch.StreamedAt != 0 {
streamedAt = time.Unix(ch.StreamedAt, 0).Format("2006-01-02 15:04 AM")
}
return &entity.ChannelInfo{
IsOnline: ch.IsOnline,
IsPaused: ch.Config.IsPaused,
Username: ch.Config.Username,
MaxDuration: internal.FormatDuration(float64(ch.Config.MaxDuration * 60)), // MaxDuration from config is in minutes
MaxFilesize: internal.FormatFilesize(ch.Config.MaxFilesize * 1024 * 1024), // MaxFilesize from config is in MB
StreamedAt: streamedAt,
CreatedAt: ch.Config.CreatedAt,
Duration: internal.FormatDuration(ch.Duration),
Filesize: internal.FormatFilesize(ch.Filesize),
Filename: filename,
Logs: ch.Logs,
GlobalConfig: server.Config,
}
}
// Pause pauses the channel and cancels the context.
func (ch *Channel) Pause() {
// Stop the monitoring loop, this also updates `ch.IsOnline` to false
// `context.Canceled` → `ch.Monitor()` → `onRetry` → `ch.UpdateOnlineStatus(false)`.
ch.CancelFunc()
ch.Config.IsPaused = true
ch.Update()
ch.Info("channel paused")
}
// Stop stops the channel and cancels the context.
func (ch *Channel) Stop() {
// Stop the monitoring loop
ch.CancelFunc()
ch.Info("channel stopped")
}
// Resume resumes the channel monitoring.
//
// `startSeq` is used to prevent all channels from starting at the same time, preventing TooManyRequests errors.
// It's only be used when program starting and trying to resume all channels at once.
func (ch *Channel) Resume(startSeq int) {
ch.Config.IsPaused = false
ch.Update()
ch.Info("channel resumed")
<-time.After(time.Duration(startSeq) * time.Second)
go ch.Monitor()
}
// UpdateOnlineStatus updates the online status of the channel.
func (ch *Channel) UpdateOnlineStatus(isOnline bool) {
ch.IsOnline = isOnline
ch.Update()
}