// 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, ¤tList) 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 }