package app import ( "os" "strconv" "strings" "sync" "time" ) // --------------------------------------------------------------------- // события / event bus // --------------------------------------------------------------------- // EN: In-memory bounded event bus used for SSE replay and polling watchers. // EN: It keeps only the latest N events and assigns monotonically increasing IDs. // RU: Ограниченная in-memory шина событий для SSE-реплея и фоновых вотчеров. // RU: Хранит только последние N событий и присваивает монотонно растущие ID. type eventBus struct { mu sync.Mutex cond *sync.Cond buf []Event cap int next int64 } // --------------------------------------------------------------------- // EN: `newEventBus` creates a new instance for event bus. // RU: `newEventBus` - создает новый экземпляр для event bus. // --------------------------------------------------------------------- func newEventBus(capacity int) *eventBus { if capacity < 16 { capacity = 16 } b := &eventBus{ cap: capacity, buf: make([]Event, 0, capacity), } b.cond = sync.NewCond(&b.mu) return b } // --------------------------------------------------------------------- // EN: `push` contains core logic for push. // RU: `push` - содержит основную логику для push. // --------------------------------------------------------------------- func (b *eventBus) push(kind string, data interface{}) Event { b.mu.Lock() defer b.mu.Unlock() b.next++ evt := Event{ ID: b.next, Kind: kind, Ts: time.Now().UTC().Format(time.RFC3339Nano), Data: data, } if len(b.buf) >= b.cap { b.buf = b.buf[1:] } b.buf = append(b.buf, evt) b.cond.Broadcast() return evt } // --------------------------------------------------------------------- // EN: `since` contains core logic for since. // RU: `since` - содержит основную логику для since. // --------------------------------------------------------------------- func (b *eventBus) since(id int64) []Event { b.mu.Lock() defer b.mu.Unlock() return b.sinceLocked(id) } // --------------------------------------------------------------------- // EN: `sinceLocked` contains core logic for since locked. // RU: `sinceLocked` - содержит основную логику для since locked. // --------------------------------------------------------------------- func (b *eventBus) sinceLocked(id int64) []Event { if len(b.buf) == 0 { return nil } var out []Event for _, ev := range b.buf { if ev.ID > id { out = append(out, ev) } } return out } // --------------------------------------------------------------------- // env helpers // --------------------------------------------------------------------- // EN: Positive integer env reader with safe default fallback. // RU: Чтение положительного целого из env с безопасным fallback на дефолт. func envInt(key string, def int) int { if v := strings.TrimSpace(os.Getenv(key)); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 { return n } } return def } var events = newEventBus(envInt("SVPN_EVENTS_CAP", defaultEventsCapacity))