seekia/internal/myDatastores/myList/myList.go

256 lines
6.3 KiB
Go

// myList provides functions to create and manage user string lists
// Lists are concurrency safe, and are stored in-memory and on disk.
// Examples of lists include matches, viewed hosts, and viewed moderators
package myList
import "seekia/internal/appMemory"
import "seekia/internal/helpers"
import "seekia/internal/localFilesystem"
import "encoding/json"
import "path/filepath"
import "errors"
import "sync"
import "slices"
//TODO: We might want to create a new file, rename, delete old file, and rename new file, so we can better protect against losing power or computer crashing and data being lost?
func InitializeMyListsFolder()error{
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return err }
myListsFolderpath := filepath.Join(userDirectory, "MyLists")
_, err = localFilesystem.CreateFolder(myListsFolderpath)
if (err != nil) { return err }
return nil
}
type MyList struct{
// Name of list. Name of file is derived from this name
listName string
// Folderpath of the list file.
listFolderpath string
// We lock this whenever we are updating the memory list and list file
updatingListMutex sync.Mutex
// We lock this when we edit the memoryList
// memoryMutex only needs to be RLock/RUnlocked by GetList, because updatingListMutex is locked for all other reads
memoryMutex sync.RWMutex
// The list stored in memory
memoryList []string
}
func CreateNewList(newListName string)(*MyList, error){
if (newListName == ""){
return nil, errors.New("CreateNewList called with empty myList name.")
}
var newList MyList
newList.listName = newListName
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return nil, err }
listFolderpath := filepath.Join(userDirectory, "MyLists")
newList.listFolderpath = listFolderpath
getListFromFile := func()([]string, error){
listFilepath := filepath.Join(listFolderpath, newListName + "List.json")
fileExists, fileBytes, err := localFilesystem.GetFileContents(listFilepath)
if (err != nil) { return nil, err }
if (fileExists == false || len(fileBytes) == 0){
emptyList := make([]string, 0)
return emptyList, nil
}
var currentList []string
err = json.Unmarshal(fileBytes, &currentList)
if (err != nil){ return nil, err }
return currentList, nil
}
listFromFile, err := getListFromFile()
if (err != nil) { return nil, err }
newList.memoryList = listFromFile
return &newList, nil
}
func (listObject *MyList) GetList()([]string, error){
if (listObject == nil){
return nil, errors.New("GetList called when MyList is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return nil, errors.New("GetList called when no user is signed in.")
}
listObject.memoryMutex.RLock()
currentList := listObject.memoryList
listCopy := slices.Clone(currentList)
listObject.memoryMutex.RUnlock()
return listCopy, nil
}
func (listObject *MyList) AddListItem(newItem string)error{
if (listObject == nil){
return errors.New("AddListItem called when MyList is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("AddListItem called when no user is signed in.")
}
listObject.updatingListMutex.Lock()
defer listObject.updatingListMutex.Unlock()
listObject.memoryMutex.Lock()
currentList := listObject.memoryList
currentList = append(currentList, newItem)
listObject.memoryList = currentList
listObject.memoryMutex.Unlock()
listFolderpath := listObject.listFolderpath
listName := listObject.listName
err := overwriteListFileWithList(listFolderpath, listName, currentList)
if (err != nil) { return err }
return nil
}
func (listObject *MyList) DeleteListItem(item string)error{
if (listObject == nil){
return errors.New("DeleteListItem called when MyList is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("DeleteListItem called when no user is signed in.")
}
listObject.updatingListMutex.Lock()
defer listObject.updatingListMutex.Unlock()
listObject.memoryMutex.Lock()
currentList := listObject.memoryList
newList, anyDeleted := helpers.DeleteAllMatchingItemsFromStringList(currentList, item)
if (anyDeleted == false){
listObject.memoryMutex.Unlock()
return nil
}
listObject.memoryList = newList
listObject.memoryMutex.Unlock()
listFolderpath := listObject.listFolderpath
listName := listObject.listName
err := overwriteListFileWithList(listFolderpath, listName, newList)
if (err != nil) { return err }
return nil
}
func (listObject *MyList) DeleteList()error{
if (listObject == nil){
return errors.New("DeleteList called when MyList is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("DeleteList called when no user is signed in.")
}
listObject.updatingListMutex.Lock()
defer listObject.updatingListMutex.Unlock()
emptyList := make([]string, 0)
listObject.memoryMutex.Lock()
listObject.memoryList = emptyList
listObject.memoryMutex.Unlock()
listFolderpath := listObject.listFolderpath
listName := listObject.listName
err := overwriteListFileWithList(listFolderpath, listName, emptyList)
if (err != nil) { return err }
return nil
}
// Note: Input list is not copied
func (listObject *MyList) OverwriteList(newList []string) error{
if (listObject == nil){
return errors.New("OverwriteList called when MyList is not initialized.")
}
exists, _ := appMemory.GetMemoryEntry("AppUser")
if (exists == false){
return errors.New("OverwriteList called when no user is signed in.")
}
listObject.updatingListMutex.Lock()
defer listObject.updatingListMutex.Unlock()
listObject.memoryMutex.Lock()
listObject.memoryList = newList
listObject.memoryMutex.Unlock()
listFolderpath := listObject.listFolderpath
listName := listObject.listName
err := overwriteListFileWithList(listFolderpath, listName, newList)
if (err != nil) { return err }
return nil
}
func overwriteListFileWithList(listFolderpath string, listName string, inputList []string)error{
newFileBytes, err := json.MarshalIndent(inputList, "", "\t")
if (err != nil) { return err }
listFileName := listName + "List.json"
err = localFilesystem.CreateOrOverwriteFile(newFileBytes, listFolderpath, listFileName)
if (err != nil) { return err }
return nil
}