2024-04-11 15:51:56 +02:00
// 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
2024-08-11 14:31:40 +02:00
newList , anyDeleted := helpers . DeleteAllMatchingItemsFromList ( currentList , item )
2024-04-11 15:51:56 +02:00
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
}