290 lines
6.9 KiB
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, ¤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
|
|
}
|
|
|
|
|