// myMap provides functions for managing user maps // Maps are concurrency safe, and are stored in-memory and on disk. package myMap import "seekia/internal/localFilesystem" import "seekia/internal/appMemory" import "encoding/json" import "path/filepath" import "sync" import "errors" import "maps" func InitializeMyMapsFolder()error{ userDirectory, err := localFilesystem.GetAppUserFolderPath() if (err != nil) { return err } myMapsFolderpath := filepath.Join(userDirectory, "MyMaps") _, err = localFilesystem.CreateFolder(myMapsFolderpath) if (err != nil) { return err } return nil } type MyMap struct{ // Name of the map. The map filename is derived from this name. mapName string // The folderpath where the map file is stored. mapFolderpath string // We lock this whenever we are editing the memory map and map file updatingMapMutex sync.Mutex // We lock this when we edit the memoryMap // memoryMutex only needs to be RLock/RUnlocked by GetMap/GetMapEntry, because updatingMapMutex is locked for all other reads memoryMutex sync.RWMutex // The map stored in memory memoryMap map[string]string } func CreateNewMap(newMapName string)(*MyMap, error){ if (newMapName == ""){ return nil, errors.New("CreateNewMap called with empty myMap name") } var newMap MyMap newMap.mapName = newMapName userDirectory, err := localFilesystem.GetAppUserFolderPath() if (err != nil) { return nil, err } mapFolderpath := filepath.Join(userDirectory, "MyMaps") newMap.mapFolderpath = mapFolderpath mapFilepath := filepath.Join(mapFolderpath, newMapName + "Map.json") getMapFromFile := func()(map[string]string, error){ fileExists, fileBytes, err := localFilesystem.GetFileContents(mapFilepath) if (err != nil) { return nil, err } if (fileExists == false){ emptyMap := make(map[string]string) return emptyMap, nil } currentMap := make(map[string]string) err = json.Unmarshal(fileBytes, ¤tMap) if (err != nil) { return nil, errors.New("Stored myMap is corrupted: " + newMapName) } return currentMap, nil } mapFromFile, err := getMapFromFile() if (err != nil) { return nil, err } newMap.memoryMap = mapFromFile return &newMap, nil } func (inputMapObject *MyMap) GetMap()(map[string]string, error){ if (inputMapObject == nil){ return nil, errors.New("GetMap called when MyMap is not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return nil, errors.New("GetMap called when no user is signed in.") } inputMapObject.memoryMutex.RLock() currentMap := inputMapObject.memoryMap mapCopy := maps.Clone(currentMap) inputMapObject.memoryMutex.RUnlock() return mapCopy, nil } //Outputs: // -bool: Entry exists // -string: Entry value // -error func (inputMapObject *MyMap) GetMapEntry(key string)(bool, string, error){ if (inputMapObject == nil){ return false, "", errors.New("GetMapEntry called when MyMap is not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return false, "", errors.New("GetMapEntry called when no user is signed in.") } inputMapObject.memoryMutex.RLock() value, exists := inputMapObject.memoryMap[key] inputMapObject.memoryMutex.RUnlock() if (exists == false){ return false, "", nil } return true, value, nil } func (inputMapObject *MyMap) SetMapEntry(key string, value string)error{ if (inputMapObject == nil){ return errors.New("SetMapEntry called when MyMap not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return errors.New("SetMapEntry called when no user is signed in.") } // We see if identical entry already exists exists, currentValue, err := inputMapObject.GetMapEntry(key) if (err != nil) { return err } if (exists == true && currentValue == value){ return nil } inputMapObject.updatingMapMutex.Lock() defer inputMapObject.updatingMapMutex.Unlock() inputMapObject.memoryMutex.Lock() inputMapObject.memoryMap[key] = value inputMapObject.memoryMutex.Unlock() mapFolderpath := inputMapObject.mapFolderpath mapName := inputMapObject.mapName newMap := inputMapObject.memoryMap err = overwriteMapFileWithMap(mapFolderpath, mapName, newMap) if (err != nil) { return err } return nil } func (inputMapObject *MyMap) DeleteMapEntry(key string)error{ if (inputMapObject == nil){ return errors.New("DeleteMapEntry called when MyMap is not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return errors.New("DeleteMapEntry called when no user is signed in.") } // We see if entry exists exists, _, err := inputMapObject.GetMapEntry(key) if (err != nil) { return err } if (exists == false){ // Nothing to delete return nil } inputMapObject.updatingMapMutex.Lock() defer inputMapObject.updatingMapMutex.Unlock() inputMapObject.memoryMutex.Lock() delete(inputMapObject.memoryMap, key) inputMapObject.memoryMutex.Unlock() mapFolderpath := inputMapObject.mapFolderpath mapName := inputMapObject.mapName newMap := inputMapObject.memoryMap err = overwriteMapFileWithMap(mapFolderpath, mapName, newMap) if (err != nil) { return err } return nil } func (inputMapObject *MyMap) DeleteMap() error{ if (inputMapObject == nil){ return errors.New("DeleteMap called when MyMap is not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return errors.New("DeleteMap called when no user is signed in.") } inputMapObject.updatingMapMutex.Lock() defer inputMapObject.updatingMapMutex.Unlock() emptyMap := make(map[string]string) inputMapObject.memoryMutex.Lock() inputMapObject.memoryMap = emptyMap inputMapObject.memoryMutex.Unlock() mapFolderpath := inputMapObject.mapFolderpath mapName := inputMapObject.mapName err := overwriteMapFileWithMap(mapFolderpath, mapName, emptyMap) if (err != nil) { return err } return nil } func (inputMapObject *MyMap) OverwriteMap(newMap map[string]string) error{ if (inputMapObject == nil){ return errors.New("OverwriteMap called when MyMap is not initialized.") } exists, _ := appMemory.GetMemoryEntry("AppUser") if (exists == false){ return errors.New("OverwriteMap called when no user is signed in.") } inputMapObject.updatingMapMutex.Lock() defer inputMapObject.updatingMapMutex.Unlock() inputMapObject.memoryMutex.Lock() inputMapObject.memoryMap = newMap inputMapObject.memoryMutex.Unlock() mapFolderpath := inputMapObject.mapFolderpath mapName := inputMapObject.mapName err := overwriteMapFileWithMap(mapFolderpath, mapName, newMap) if (err != nil) { return err } return nil } func overwriteMapFileWithMap(mapFolderpath string, mapName string, inputMap map[string]string)error{ fileContents, err := json.MarshalIndent(inputMap, "", "\t") if (err != nil) { return err } mapFileName := mapName + "Map.json" err = localFilesystem.CreateOrOverwriteFile(fileContents, mapFolderpath, mapFileName) if (err != nil) { return err } return nil }