seekia/internal/myDatastores/myMap/myMap.go

290 lines
6.9 KiB
Go

// 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, &currentMap)
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
}