package permissions import ( "container/list" "sync" ) // Cache — простой LRU-кэш вычисленных прав с инвалидацией при записи // (AGENT.md 9.1). Ключи: сервер+пользователь и комната+пользователь. type Cache struct { mu sync.Mutex capacity int items map[guildKey]*list.Element channels map[channelKey]*list.Element order *list.List } type guildKey struct { guildID uint64 userID uint64 } type channelKey struct { // guildID нужен, чтобы сброс сервера (роли, тайм-аут, оверрайды) сбрасывал // и кэш его комнат: иначе участник с тайм-аутом продолжал писать. guildID uint64 channelID uint64 userID uint64 } type cacheEntry struct { guild guildKey channel channelKey value Resolved isChan bool } func NewCache(capacity int) *Cache { if capacity <= 0 { capacity = 1024 } return &Cache{ capacity: capacity, items: make(map[guildKey]*list.Element, capacity), channels: make(map[channelKey]*list.Element, capacity), order: list.New(), } } func (c *Cache) GetGuild(guildID, userID uint64) (Resolved, bool) { c.mu.Lock() defer c.mu.Unlock() element, ok := c.items[guildKey{guildID: guildID, userID: userID}] if !ok { return Resolved{}, false } c.order.MoveToFront(element) return element.Value.(*cacheEntry).value, true } func (c *Cache) PutGuild(guildID, userID uint64, value Resolved) { c.mu.Lock() defer c.mu.Unlock() key := guildKey{guildID: guildID, userID: userID} if element, ok := c.items[key]; ok { element.Value.(*cacheEntry).value = value c.order.MoveToFront(element) return } entry := &cacheEntry{guild: key, value: value} c.items[key] = c.order.PushFront(entry) c.evictLocked() } func (c *Cache) GetChannel(guildID, channelID, userID uint64) (Resolved, bool) { c.mu.Lock() defer c.mu.Unlock() element, ok := c.channels[channelKey{guildID: guildID, channelID: channelID, userID: userID}] if !ok { return Resolved{}, false } c.order.MoveToFront(element) return element.Value.(*cacheEntry).value, true } func (c *Cache) PutChannel(guildID, channelID, userID uint64, value Resolved) { c.mu.Lock() defer c.mu.Unlock() key := channelKey{guildID: guildID, channelID: channelID, userID: userID} if element, ok := c.channels[key]; ok { element.Value.(*cacheEntry).value = value c.order.MoveToFront(element) return } entry := &cacheEntry{channel: key, value: value, isChan: true} c.channels[key] = c.order.PushFront(entry) c.evictLocked() } // InvalidateGuild сбрасывает права всех пользователей сервера, включая кэш // его комнат: роли, тайм-аут и оверрайды влияют и на права в комнатах. func (c *Cache) InvalidateGuild(guildID uint64) { c.mu.Lock() defer c.mu.Unlock() for key, element := range c.items { if key.guildID == guildID { c.order.Remove(element) delete(c.items, key) } } for key, element := range c.channels { if key.guildID == guildID { c.order.Remove(element) delete(c.channels, key) } } } // InvalidateChannel сбрасывает кэш комнаты. func (c *Cache) InvalidateChannel(channelID uint64) { c.mu.Lock() defer c.mu.Unlock() for key, element := range c.channels { if key.channelID == channelID { c.order.Remove(element) delete(c.channels, key) } } } func (c *Cache) Clear() { c.mu.Lock() defer c.mu.Unlock() c.items = make(map[guildKey]*list.Element, c.capacity) c.channels = make(map[channelKey]*list.Element, c.capacity) c.order.Init() } func (c *Cache) Len() int { c.mu.Lock() defer c.mu.Unlock() return c.order.Len() } func (c *Cache) evictLocked() { for c.order.Len() > c.capacity { oldest := c.order.Back() if oldest == nil { return } entry := oldest.Value.(*cacheEntry) if entry.isChan { delete(c.channels, entry.channel) } else { delete(c.items, entry.guild) } c.order.Remove(oldest) } }