seekia/internal/myDatastores/myMapList/myMapList.go

344 lines
9.2 KiB
Go

// myMapList provides functions for managing user map lists
// Map lists are concurrency safe, and are stored in-memory and on disk.
// Examples of map lists include saved contacts, my chat messages, and my chat conversations
package myMapList
import "seekia/internal/localFilesystem"
import "seekia/internal/helpers"
import "seekia/internal/appMemory"
import "encoding/json"
import "path/filepath"
import "errors"
import "sync"
import "maps"
func InitializeMyMapListsFolder()error{
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return err }
myMapListsFolderpath := filepath.Join(userDirectory, "MyMapLists")
_, err = localFilesystem.CreateFolder(myMapListsFolderpath)
if (err != nil) { return err }
return nil
}
type MyMapList struct{
// Name of map list. The file name is derived from this name.
mapListName string
// Folderpath of the mapList file
mapListFolderpath string
// We lock this whenever we are editing the memory map and map file
updatingMapListMutex sync.Mutex
// We lock this whenever we edit the memoryMapList
// memoryMutex only needs to be RLock/RUnlocked by GetMapList/GetMapListItems, because updatingMapListMutex is locked for all other reads
memoryMutex sync.RWMutex
// The map list stored in memory
memoryMapList []map[string]string
}
func CreateNewMapList(newMapListName string)(*MyMapList, error){
if (newMapListName == ""){
return nil, errors.New("CreateNewMapList called with empty myMapList name")
}
var newMapList MyMapList
newMapList.mapListName = newMapListName
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return nil, err }
mapListFolderpath := filepath.Join(userDirectory, "MyMapLists")
newMapList.mapListFolderpath = mapListFolderpath
getMapListFromFile := func()([]map[string]string, error){
mapListFilepath := filepath.Join(mapListFolderpath, newMapListName + "MapList.json")
fileExists, fileBytes, err := localFilesystem.GetFileContents(mapListFilepath)
if (err != nil) { return nil, err }
if (fileExists == false){
emptyMapList := make([]map[string]string, 0)
return emptyMapList, nil
}
currentMapList := make([]map[string]string, 0)
err = json.Unmarshal(fileBytes, &currentMapList)
if (err != nil) {
return nil, errors.New("My Map list is corrupted: " + newMapListName)
}
return currentMapList, nil
}
mapListFromFile, err := getMapListFromFile()
if (err != nil) { return nil, err }
newMapList.memoryMapList = mapListFromFile
return &newMapList, nil
}
//Outputs:
// -[]map[string]string
// -error
func (inputMapListObject *MyMapList) GetMapList()([]map[string]string, error){
if (inputMapListObject == nil){
return nil, errors.New("GetMapList called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return nil, errors.New("GetMapList called when no user is signed in.")
}
inputMapListObject.memoryMutex.RLock()
currentMapList := inputMapListObject.memoryMapList
mapListCopy := helpers.DeepCopyStringToStringMapList(currentMapList)
inputMapListObject.memoryMutex.RUnlock()
return mapListCopy, nil
}
// Note: Added items are not copied
func (inputMapListObject *MyMapList) AddMapListItem(newItem map[string]string)error{
if (inputMapListObject == nil){
return errors.New("AddMapListItem called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("AddMapListItem called when no user is signed in.")
}
inputMapListObject.updatingMapListMutex.Lock()
defer inputMapListObject.updatingMapListMutex.Unlock()
inputMapListObject.memoryMutex.Lock()
currentMapList := inputMapListObject.memoryMapList
currentMapList = append(currentMapList, newItem)
inputMapListObject.memoryMapList = currentMapList
inputMapListObject.memoryMutex.Unlock()
mapListFolderpath := inputMapListObject.mapListFolderpath
mapListName := inputMapListObject.mapListName
err := overwriteMapListFileWithMapList(mapListFolderpath, mapListName, currentMapList)
if (err != nil) { return err }
return nil
}
// This can be used to delete multiple entries at a time.
// Any maps that contain the same entries as the input map will be deleted
func (inputMapListObject *MyMapList) DeleteMapListItems(mapItem map[string]string)error{
if (inputMapListObject == nil){
return errors.New("DeleteMapListItems called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("DeleteMapListItems called when no user is signed in.")
}
inputMapListObject.updatingMapListMutex.Lock()
defer inputMapListObject.updatingMapListMutex.Unlock()
currentMapList := inputMapListObject.memoryMapList
if (len(currentMapList) == 0){
return nil
}
newMapList := make([]map[string]string, 0)
anyItemDeleted := false
for _, element := range currentMapList{
areEqual := checkIfMapContainsMapEntries(mapItem, element)
if (areEqual == false){
newMapList = append(newMapList, element)
continue
}
anyItemDeleted = true
}
if (anyItemDeleted == false){
// No items were deleted. Nothing left to do.
return nil
}
inputMapListObject.memoryMutex.Lock()
inputMapListObject.memoryMapList = newMapList
inputMapListObject.memoryMutex.Unlock()
mapListFolderpath := inputMapListObject.mapListFolderpath
mapListName := inputMapListObject.mapListName
err := overwriteMapListFileWithMapList(mapListFolderpath, mapListName, newMapList)
if (err != nil) { return err }
return nil
}
//Outputs:
// bool: Any item was found
// []map[string]string: list of matching items
// error
func (inputMapListObject *MyMapList) GetMapListItems(lookupItem map[string]string)(bool, []map[string]string, error){
if (inputMapListObject == nil){
return false, nil, errors.New("GetMapListItems called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return false, nil, errors.New("GetMapListItems called when no user is signed in.")
}
inputMapListObject.memoryMutex.RLock()
currentMapList := inputMapListObject.memoryMapList
matchingItemsMapList := make([]map[string]string, 0)
for _, element := range currentMapList{
areEqual := checkIfMapContainsMapEntries(lookupItem, element)
if (areEqual == true){
matchingItemCopy := maps.Clone(element)
matchingItemsMapList = append(matchingItemsMapList, matchingItemCopy)
}
}
inputMapListObject.memoryMutex.RUnlock()
if (len(matchingItemsMapList) == 0) {
return false, matchingItemsMapList, nil
}
return true, matchingItemsMapList, nil
}
// Note: Input map list is not copied
func (inputMapListObject *MyMapList) OverwriteMapList(newMapList []map[string]string) error{
if (inputMapListObject == nil){
return errors.New("OverwriteMapList called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("OverwriteMapList called when no user is signed in.")
}
inputMapListObject.updatingMapListMutex.Lock()
defer inputMapListObject.updatingMapListMutex.Unlock()
inputMapListObject.memoryMutex.Lock()
inputMapListObject.memoryMapList = newMapList
inputMapListObject.memoryMutex.Unlock()
mapListFolderpath := inputMapListObject.mapListFolderpath
mapListName := inputMapListObject.mapListName
err := overwriteMapListFileWithMapList(mapListFolderpath, mapListName, newMapList)
if (err != nil) { return err }
return nil
}
func (inputMapListObject *MyMapList) DeleteMapList() error{
if (inputMapListObject == nil){
return errors.New("DeleteMapList called when MyMapList object is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("DeleteMapList called when no user is signed in.")
}
inputMapListObject.updatingMapListMutex.Lock()
defer inputMapListObject.updatingMapListMutex.Unlock()
emptyMapList := make([]map[string]string, 0)
inputMapListObject.memoryMutex.Lock()
inputMapListObject.memoryMapList = emptyMapList
inputMapListObject.memoryMutex.Unlock()
mapListFolderpath := inputMapListObject.mapListFolderpath
mapListName := inputMapListObject.mapListName
err := overwriteMapListFileWithMapList(mapListFolderpath, mapListName, emptyMapList)
if (err != nil) { return err }
return nil
}
// We use this function to find matching items to delete/retrieve
// Map2 must contain all of map1's entries.
// If any map2 entries do not match match1 entries, return false
// If map1 contains more entries than map2, return false
// Map2 is allowed to contain more entries than map1
func checkIfMapContainsMapEntries(map1 map[string]string, map2 map[string]string) bool {
for key, value := range map1 {
currentValue, exists := map2[key]
if (exists == false) {
return false
}
if (value != currentValue) {
return false
}
}
return true
}
func overwriteMapListFileWithMapList(mapListFolderpath string, mapListName string, inputMapList []map[string]string)error{
fileContents, err := json.MarshalIndent(inputMapList, "", "\t")
if (err != nil) { return err }
mapListFileName := mapListName + "MapList.json"
err = localFilesystem.CreateOrOverwriteFile(fileContents, mapListFolderpath, mapListFileName)
if (err != nil) { return err }
return nil
}