package ocean import ( "encoding/json" "os" "path/filepath" "reflect" "sync" ) type Collection[T any] interface { All() <-chan *T Clear() Delete(key string) Exists(key string) bool Get(key string) (value *T, err error) Set(key string, value *T) } // collection is a hash map of homogeneous data. type collection[T any] struct { data sync.Map name string directory string } // New creates a new collection with the given name. func New[T any](directories ...string) (*collection[T], error) { name := reflect.TypeOf((*T)(nil)).Elem().Name() home, err := os.UserHomeDir() if err != nil { return nil, err } directories = append([]string{home, ".ocean"}, directories...) directories = append(directories, name) directory := filepath.Join(directories...) err = os.MkdirAll(directory, 0700) if err != nil { return nil, err } c := &collection[T]{ name: directories[len(directories)-1], directory: directory, } return c, c.loadFromDisk() } // All returns a channel of all objects in the collection. func (c *collection[T]) All() <-chan *T { channel := make(chan *T) go func() { c.data.Range(func(key, value any) bool { channel <- value.(*T) return true }) close(channel) }() return channel } // Get returns the value for the given key. func (c *collection[T]) Get(key string) (*T, error) { value, exists := c.data.Load(key) if !exists { return nil, &KeyNotFoundError{Key: key} } return value.(*T), nil } // Set sets the value for the given key. func (c *collection[T]) Set(key string, value *T) { c.data.Store(key, value) err := c.writeFileToDisk(key, value) if err != nil { panic(err) } } // Delete deletes a key from the collection. func (c *collection[T]) Delete(key string) { if !c.Exists(key) { return } c.data.Delete(key) os.Remove(c.keyFile(key)) } // Exists returns whether or not the key exists. func (c *collection[T]) Exists(key string) bool { _, exists := c.data.Load(key) return exists } // Clear deletes all objects from the collection. func (c *collection[T]) Clear() { c.data.Range(func(key, value any) bool { c.Delete(key.(string)) return true }) } // keyFile returns the file path for the given key. func (c *collection[T]) keyFile(key string) string { return filepath.Join(c.directory, key) } // loadFromDisk loads the collection data from the disk. func (c *collection[T]) loadFromDisk() error { file, err := os.Open(c.directory) if err != nil { return err } files, err := file.Readdirnames(0) for _, key := range files { fileError := c.loadFileFromDisk(key) if fileError != nil { return fileError } } if err != nil { return err } return file.Close() } // loadFileFromDisk loads a single file from the disk. func (c *collection[T]) loadFileFromDisk(key string) error { file, err := os.Open(filepath.Join(c.directory, key)) if err != nil { return err } value := new(T) decoder := json.NewDecoder(file) err = decoder.Decode(value) if err != nil { file.Close() return err } c.data.Store(key, value) return file.Close() } // writeFileToDisk writes the value for the key to disk as a JSON file. func (c *collection[T]) writeFileToDisk(key string, value *T) error { fileName := c.keyFile(key) file, err := os.Create(fileName) if err != nil { return err } encoder := json.NewEncoder(file) err = encoder.Encode(value) if err != nil { file.Close() return err } return file.Close() }