Files
meta-core/internal/watcher/dispatcher.go
T
2026-02-15 22:38:28 +01:00

123 lines
2.9 KiB
Go

package watcher
import (
"fmt"
"log"
"sync"
"github.com/metazla/meta-core/internal/storage"
)
const (
// EventsStream is the Redis Stream name for file events
EventsStream = "file:events"
)
// Dispatcher sends file events to Redis Stream
type Dispatcher struct {
mu sync.RWMutex
storageClient *storage.Client
}
// NewDispatcher creates a new event dispatcher
func NewDispatcher() *Dispatcher {
return &Dispatcher{}
}
// SetStorageClient sets the storage client for Redis Stream publishing
func (d *Dispatcher) SetStorageClient(client *storage.Client) {
d.mu.Lock()
defer d.mu.Unlock()
d.storageClient = client
log.Println("[Dispatcher] Storage client set for Redis Stream publishing")
}
// Dispatch sends an event to Redis Stream
func (d *Dispatcher) Dispatch(event FileEvent) {
go d.dispatchToStream(event)
}
// dispatchToStream publishes event to Redis Stream for service consumption
func (d *Dispatcher) dispatchToStream(event FileEvent) {
d.mu.RLock()
client := d.storageClient
d.mu.RUnlock()
if client == nil || !client.IsConnected() {
return
}
// Build stream fields
fields := map[string]interface{}{
"type": string(event.Type),
"path": event.Path,
"timestamp": fmt.Sprintf("%d", event.Timestamp),
}
// Add optional fields
if event.Size > 0 {
fields["size"] = fmt.Sprintf("%d", event.Size)
}
if event.MidHash256 != "" {
fields["midhash256"] = event.MidHash256
}
if event.OldPath != "" {
fields["oldPath"] = event.OldPath
}
if event.WatcherID != "" {
fields["watcherId"] = event.WatcherID
}
// Publish to stream (no MaxLen - stream is cleared on reset)
_, err := client.XAdd(EventsStream, 0, fields)
if err != nil {
log.Printf("[Dispatcher] Failed to publish to stream: %v", err)
}
}
// EmitReset clears the stream and emits a reset event
// This is called when triggering a full rescan
func (d *Dispatcher) EmitReset(watcherID string) error {
d.mu.RLock()
client := d.storageClient
d.mu.RUnlock()
if client == nil || !client.IsConnected() {
return fmt.Errorf("storage client not connected")
}
// Delete the stream to clear all historical events
if err := client.ClearStream(EventsStream); err != nil {
log.Printf("[Dispatcher] Warning: failed to clear stream: %v", err)
// Continue anyway - we'll emit the reset event
}
log.Printf("[Dispatcher] Cleared stream %s for reset", EventsStream)
// Emit reset event as the first message in the fresh stream
event := FileEvent{
Type: EventTypeReset,
Path: "", // No specific path for reset
WatcherID: watcherID,
Timestamp: NowMS(),
}
// Dispatch synchronously for reset
d.dispatchToStream(event)
return nil
}
// GetStreamLength returns the current stream length
func (d *Dispatcher) GetStreamLength() (int64, error) {
d.mu.RLock()
client := d.storageClient
d.mu.RUnlock()
if client == nil || !client.IsConnected() {
return 0, fmt.Errorf("storage client not connected")
}
return client.GetStreamLength(EventsStream)
}