mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-06-27 18:21:35 +08:00
feat: implement task event system for progress tracking and reporting (#220)
This commit is contained in:
88
pkg/taskevent/taskevent.go
Normal file
88
pkg/taskevent/taskevent.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Package taskevent provides a decoupled, context-scoped event bus for task
|
||||
// lifecycle progress. Producers (task implementations) emit events via Emit;
|
||||
// consumers (e.g. the API progress store, the Telegram message editor) register
|
||||
// as Sinks and are injected through context. This keeps the task layer free of
|
||||
// any concrete progress-display dependency, so new task types gain progress
|
||||
// reporting for free and new observers can be added without touching tasks.
|
||||
package taskevent
|
||||
|
||||
import "context"
|
||||
|
||||
// Phase marks a stage in a task's lifecycle.
|
||||
type Phase int
|
||||
|
||||
const (
|
||||
PhaseStart Phase = iota
|
||||
PhaseProgress
|
||||
PhaseDone
|
||||
)
|
||||
|
||||
func (p Phase) String() string {
|
||||
switch p {
|
||||
case PhaseStart:
|
||||
return "start"
|
||||
case PhaseProgress:
|
||||
return "progress"
|
||||
case PhaseDone:
|
||||
return "done"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Event describes a single progress observation for a task. Byte fields are
|
||||
// populated by byte-stream tasks; file-count fields by count-based tasks. A
|
||||
// task may fill whichever subset it has; observers ignore zero values.
|
||||
type Event struct {
|
||||
TaskID string
|
||||
Phase Phase
|
||||
TotalBytes int64
|
||||
DownloadedBytes int64
|
||||
TotalFiles int
|
||||
DownloadedFiles int
|
||||
Err error
|
||||
}
|
||||
|
||||
// Sink receives task events. Implementations must be safe for concurrent use.
|
||||
type Sink interface {
|
||||
Emit(Event)
|
||||
}
|
||||
|
||||
// SinkFunc is a function adapter for Sink.
|
||||
type SinkFunc func(Event)
|
||||
|
||||
func (f SinkFunc) Emit(e Event) { f(e) }
|
||||
|
||||
type sinkKey struct{}
|
||||
|
||||
// WithSink returns a ctx carrying the given sinks. Multiple sinks can be passed
|
||||
// and all will receive every emitted event. Sinks already present in ctx are
|
||||
// preserved.
|
||||
func WithSink(ctx context.Context, sinks ...Sink) context.Context {
|
||||
if len(sinks) == 0 {
|
||||
return ctx
|
||||
}
|
||||
var existing []Sink
|
||||
if v, ok := ctx.Value(sinkKey{}).([]Sink); ok {
|
||||
existing = v
|
||||
}
|
||||
merged := make([]Sink, 0, len(existing)+len(sinks))
|
||||
merged = append(merged, existing...)
|
||||
merged = append(merged, sinks...)
|
||||
return context.WithValue(ctx, sinkKey{}, merged)
|
||||
}
|
||||
|
||||
// Emit broadcasts an event to all sinks carried by ctx. It is a no-op when no
|
||||
// sink is attached, so producers can call it unconditionally.
|
||||
func Emit(ctx context.Context, e Event) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
sinks, ok := ctx.Value(sinkKey{}).([]Sink)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, s := range sinks {
|
||||
s.Emit(e)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user