package filesvc import ( "context" "slices" "strings" "sync" "time" ) type MemStore struct { mu sync.Mutex items map[ID]File // optionales Eventing subs []chan ChangeEvent } func NewMemStore() *MemStore { return &MemStore{ items: make(map[ID]File), } } /*** Store ***/ func (m *MemStore) Get(_ context.Context, id ID) (File, error) { m.mu.Lock() defer m.mu.Unlock() it, ok := m.items[id] if !ok || it.Deleted { return File{}, ErrNotFound } return it, nil } func (m *MemStore) List(_ context.Context, next ID, limit int) ([]File, ID, error) { m.mu.Lock() defer m.mu.Unlock() if limit <= 0 || limit > 1000 { limit = 100 } // sortiere deterministisch nach UpdatedAt, dann ID all := make([]File, 0, len(m.items)) for _, v := range m.items { all = append(all, v) } slices.SortFunc(all, func(a, b File) int { if a.UpdatedAt == b.UpdatedAt { if a.ID == b.ID { return 0 } if a.ID < b.ID { return -1 } return 1 } if a.UpdatedAt < b.UpdatedAt { return -1 } return 1 }) start := 0 if next != "" { for i, it := range all { if it.ID >= next { start = i break } } } end := start + limit if end > len(all) { end = len(all) } out := make([]File, 0, end-start) for _, it := range all[start:end] { if !it.Deleted { out = append(out, it) } } var nextOut ID if end < len(all) { nextOut = all[end].ID } return out, nextOut, nil } func (m *MemStore) Create(_ context.Context, name string) (File, error) { name = strings.TrimSpace(name) if name == "" { return File{}, ErrBadInput } m.mu.Lock() defer m.mu.Unlock() now := time.Now().UnixNano() uid, err := NewUUIDv4() if err != nil { return File{}, err } it := File{ID: uid, Name: name, UpdatedAt: now} m.items[it.ID] = it m.emit(it) return it, nil } func (m *MemStore) Rename(_ context.Context, id ID, newName string) (File, error) { newName = strings.TrimSpace(newName) if newName == "" { return File{}, ErrBadInput } m.mu.Lock() defer m.mu.Unlock() it, ok := m.items[id] if !ok || it.Deleted { return File{}, ErrNotFound } it.Name = newName it.UpdatedAt = time.Now().UnixNano() m.items[id] = it m.emit(it) return it, nil } func (m *MemStore) Delete(_ context.Context, id ID) (File, error) { m.mu.Lock() defer m.mu.Unlock() it, ok := m.items[id] if !ok { return File{}, ErrNotFound } if it.Deleted { return it, nil } it.Deleted = true it.UpdatedAt = time.Now().UnixNano() m.items[id] = it m.emit(it) return it, nil } /*** Replicable ***/ func (m *MemStore) Snapshot(_ context.Context) (Snapshot, error) { m.mu.Lock() defer m.mu.Unlock() s := Snapshot{Items: make([]File, 0, len(m.items))} for _, it := range m.items { s.Items = append(s.Items, it) // inkl. Tombstones } return s, nil } func (m *MemStore) ApplyRemote(_ context.Context, s Snapshot) error { m.mu.Lock() defer m.mu.Unlock() for _, ri := range s.Items { li, ok := m.items[ri.ID] if !ok || ri.UpdatedAt > li.UpdatedAt { m.items[ri.ID] = ri m.emitLocked(ri) } } return nil } /*** Watchable (optional) ***/ func (m *MemStore) Watch(stop <-chan struct{}) <-chan ChangeEvent { ch := make(chan ChangeEvent, 32) m.mu.Lock() m.subs = append(m.subs, ch) m.mu.Unlock() go func() { <-stop m.mu.Lock() // entferne ch aus subs for i, s := range m.subs { if s == ch { m.subs = append(m.subs[:i], m.subs[i+1:]...) break } } m.mu.Unlock() close(ch) }() return ch } func (m *MemStore) emit(it File) { m.emitLocked(it) // mu wird im Aufrufer gehalten } func (m *MemStore) emitLocked(it File) { ev := ChangeEvent{At: time.Now(), Item: it} for _, s := range m.subs { select { case s <- ev: default: /* drop wenn voll */ } } }