package data import ( "os" "maps" "errors" "encoding/json" ) // A very simple datastructure, implementing the IDatastore interface. It uses // a simple text file to the data as json. type FileDatastore struct { path string data map[string]string } // Creates a new FileDatastore object, creating the storage file in the // process. func NewFileDatastore(path string) (*FileDatastore, error) { fds := &FileDatastore{ path: path } if _, err := fds.readMapObj(); err != nil { if err := fds.writeMapObj(make(map[string]string)); err != nil { return nil, err } } return fds, nil } // Read the contents of the storage file and convert to a map object. May throw // an error, if the file does not exit or the file content can not be // converted. func (fds *FileDatastore) readMapObj() (map[string]string, error) { if fds.data != nil { return fds.data, nil } dat, err := os.ReadFile(fds.path) if err != nil { return nil, err } var mapobj map[string]string err = json.Unmarshal(dat, &mapobj) if err != nil { return nil, err } return mapobj, nil } // Write the map object to the storage file. Will overwrite the content of the // file. May throw an error, if the file cannot be created or written to. func (fds *FileDatastore) writeMapObj(m map[string]string) error { file, err := os.Create(fds.path) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) if err := encoder.Encode(m); err != nil { return err } fds.data = m return nil } // --- implement IDatastore interface --- // Sets the key value pair given, overwriting if the key already exists. May // through an error if the file cannot be opened or the contents cannot be // decoded correctly. func (fds *FileDatastore) Set(key string, val string) error { m, err := fds.readMapObj() if err != nil { return err } m[key] = val err = fds.writeMapObj(m) if err != nil { return err } return nil } // Check if for the given key a entry does exit. May through an error if the // file cannot be opened or the contents cannot be decoded correctly. func (fds *FileDatastore) KeyExists(key string) (bool, error) { m, err := fds.readMapObj() if err != nil { return false, err } _, ok := m[key] return ok, nil } // Gets the value for the given key. May through an error if the key does not // exit, the file cannot be opened or the contents cannot be decoded // correctly. func (fds *FileDatastore) Get(key string) (string, error) { m, err := fds.readMapObj() if err != nil { return "", err } val, ok := m[key] if !ok { return "", errors.New("key not found") } return val, nil } // Gets all the key value pairs from the file and returns them as a map object. // May through an error if the file cannot be opened or the contents cannot be // decoded correctly. func (fds *FileDatastore) GetAll() (map[string]string, error) { return fds.readMapObj() } // Gets all the key the file and returns them as a map object. May through an // error if the file cannot be opened or the contents cannot be decoded // correctly. func (fds *FileDatastore) GetAllKeys() (map[string]bool, error) { m, err := fds.readMapObj() if err != nil { return nil, err } out := make(map[string]bool) for key := range maps.Keys(m) { out[key] = true } return out, nil } // Deletes the entry with the given key. May through an error if the file // cannot be opened or the contents cannot be decoded or encoded correctly. func (fds *FileDatastore) Delete(key string) error { m, err := fds.readMapObj() if err != nil { return err } delete(m, key) err = fds.writeMapObj(m) if err != nil { return err } return nil }