Files
silo-server/internal/notifications/hub.go
T

187 lines
5.4 KiB
Go

package notifications
import (
"context"
"encoding/json"
"time"
"github.com/Silo-Server/silo-server/internal/cache"
evt "github.com/Silo-Server/silo-server/internal/events"
"github.com/Silo-Server/silo-server/internal/models"
)
type Type string
const (
TypeLibraryChanged Type = "library.changed"
TypeLibraryItemAdded Type = "library.item_added"
TypeMetadataUpdated Type = "metadata.updated"
TypeJobCreated Type = "job.created"
TypeJobProgress Type = "job.progress"
TypeJobCompleted Type = "job.completed"
TypeJobFailed Type = "job.failed"
)
type Envelope struct {
Type Type `json:"type"`
Timestamp time.Time `json:"timestamp"`
SourceID string `json:"source_id,omitempty"`
AdminOnly bool `json:"admin_only,omitempty"`
LibraryID int `json:"library_id,omitempty"`
ContentID string `json:"content_id,omitempty"`
New int `json:"new,omitempty"`
Updated int `json:"updated,omitempty"`
Missing int `json:"missing,omitempty"`
MatchedFiles int `json:"matched_files,omitempty"`
RetriedItems int `json:"retried_items,omitempty"`
StillUnmatchedWarnings int `json:"still_unmatched_warnings,omitempty"`
Job *models.AdminJob `json:"job,omitempty"`
}
type LibraryChangeEvent struct {
LibraryID int
New int
Updated int
Missing int
MatchedFiles int
RetriedItems int
StillUnmatchedWarnings int
}
type MetadataUpdateEvent struct {
LibraryID int
ContentID string
}
type Hub struct {
inner *evt.Hub
}
func NewHub(sourceID string, eventBus cache.EventBus) *Hub {
return &Hub{inner: evt.NewHub(sourceID, eventBus)}
}
func (h *Hub) Start(ctx context.Context) error {
if h == nil || h.inner == nil {
return nil
}
return h.inner.Start(ctx)
}
func (h *Hub) Subscribe() (<-chan Envelope, func()) {
if h == nil || h.inner == nil {
ch := make(chan Envelope)
close(ch)
return ch, func() {}
}
innerCh, unsubscribe := h.inner.Subscribe()
out := make(chan Envelope, 32)
go func() {
defer close(out)
for env := range innerCh {
converted, ok := convertEnvelope(env)
if !ok {
continue
}
out <- converted
}
}()
return out, unsubscribe
}
func (h *Hub) PublishLibraryChanged(ctx context.Context, event LibraryChangeEvent) error {
if h == nil || h.inner == nil {
return nil
}
payload := map[string]any{
"library_id": event.LibraryID,
"new": event.New,
"updated": event.Updated,
"missing": event.Missing,
"matched_files": event.MatchedFiles,
"retried_items": event.RetriedItems,
"still_unmatched_warnings": event.StillUnmatchedWarnings,
}
if err := h.inner.PublishJSON(ctx, evt.ChannelCatalog, string(TypeLibraryChanged), payload, evt.PublishOptions{}); err != nil {
return err
}
if event.New <= 0 {
return nil
}
return h.inner.PublishJSON(ctx, evt.ChannelCatalog, string(TypeLibraryItemAdded), map[string]any{
"library_id": event.LibraryID,
"new": event.New,
}, evt.PublishOptions{})
}
func (h *Hub) PublishMetadataUpdated(ctx context.Context, event MetadataUpdateEvent) error {
if h == nil || h.inner == nil {
return nil
}
return h.inner.PublishJSON(ctx, evt.ChannelCatalog, string(TypeMetadataUpdated), map[string]any{
"library_id": event.LibraryID,
"content_id": event.ContentID,
}, evt.PublishOptions{})
}
func (h *Hub) PublishJob(ctx context.Context, eventType Type, job *models.AdminJob) error {
if h == nil || h.inner == nil || job == nil {
return nil
}
return h.inner.PublishJSON(ctx, evt.ChannelJobs, string(eventType), job, evt.PublishOptions{
AdminOnly: true,
})
}
func (h *Hub) EventsHub() *evt.Hub {
if h == nil {
return nil
}
return h.inner
}
func convertEnvelope(env evt.Envelope) (Envelope, bool) {
converted := Envelope{
Type: Type(env.Event),
Timestamp: env.Timestamp,
SourceID: env.SourceID,
AdminOnly: env.AdminOnly,
}
if len(env.Data) == 0 {
return converted, true
}
switch env.Channel {
case evt.ChannelCatalog:
var payload struct {
LibraryID int `json:"library_id"`
ContentID string `json:"content_id"`
New int `json:"new"`
Updated int `json:"updated"`
Missing int `json:"missing"`
MatchedFiles int `json:"matched_files"`
RetriedItems int `json:"retried_items"`
StillUnmatchedWarnings int `json:"still_unmatched_warnings"`
}
if err := json.Unmarshal(env.Data, &payload); err != nil {
return Envelope{}, false
}
converted.LibraryID = payload.LibraryID
converted.ContentID = payload.ContentID
converted.New = payload.New
converted.Updated = payload.Updated
converted.Missing = payload.Missing
converted.MatchedFiles = payload.MatchedFiles
converted.RetriedItems = payload.RetriedItems
converted.StillUnmatchedWarnings = payload.StillUnmatchedWarnings
case evt.ChannelJobs:
var job models.AdminJob
if err := json.Unmarshal(env.Data, &job); err != nil {
return Envelope{}, false
}
converted.Job = &job
}
return converted, true
}