435 lines
11 KiB
Go
435 lines
11 KiB
Go
|
|
// localFilesystem provides functions to read and write to the filesystem
|
|
|
|
package localFilesystem
|
|
|
|
// localFilesystem tries to prevent the concurrent writing of files
|
|
// Each filepath and folderpath has its own mutex, which prevents the concurrent write of a file/folder
|
|
// This only works properly if each file is deleted individually using DeleteFileOrFolder, as opposed to using something like os.RemoveAll
|
|
//
|
|
// It is not perfect, because a file could be created within a folder that is being deleted
|
|
// To avoid this, we need to avoid deleting folders when the user is signed in
|
|
|
|
import "seekia/internal/globalSettings"
|
|
import "seekia/internal/appMemory"
|
|
|
|
import goFilepath "path/filepath"
|
|
|
|
import "sync"
|
|
import "os"
|
|
import "errors"
|
|
|
|
var filesystemPathMutexesMapMutex sync.RWMutex
|
|
|
|
var filesystemPathMutexesMap map[string]*sync.RWMutex = make(map[string]*sync.RWMutex)
|
|
|
|
func getFilesystemPathMutex(filepath string)*sync.RWMutex{
|
|
|
|
filesystemPathMutexesMapMutex.RLock()
|
|
currentMutex, exists := filesystemPathMutexesMap[filepath]
|
|
filesystemPathMutexesMapMutex.RUnlock()
|
|
if (exists == true){
|
|
return currentMutex
|
|
}
|
|
|
|
newMutex := new(sync.RWMutex)
|
|
|
|
filesystemPathMutexesMapMutex.Lock()
|
|
filesystemPathMutexesMap[filepath] = newMutex
|
|
filesystemPathMutexesMapMutex.Unlock()
|
|
|
|
return newMutex
|
|
}
|
|
|
|
// This must be run upon application startup
|
|
// It also must be run before certain tests
|
|
func InitializeAppDatastores()error{
|
|
|
|
seekiaDirectoryPath, err := GetSeekiaDataFolderPath()
|
|
if (err != nil) { return err }
|
|
|
|
_, err = CreateFolder(seekiaDirectoryPath)
|
|
if (err != nil) { return err }
|
|
|
|
err = globalSettings.InitializeGlobalSettingsDatastore()
|
|
if (err != nil) { return err }
|
|
|
|
databaseFolderPath, err := GetAppDatabaseFolderPath()
|
|
if (err != nil) { return err }
|
|
|
|
_, err = CreateFolder(databaseFolderPath)
|
|
if (err != nil) { return err }
|
|
|
|
userDataFolderPath, err := GetAppUsersDataFolderPath()
|
|
if (err != nil) { return err }
|
|
|
|
_, err = CreateFolder(userDataFolderPath)
|
|
if (err != nil) { return err }
|
|
|
|
parametersFolderpath := goFilepath.Join(seekiaDirectoryPath, "Parameters")
|
|
parametersNetwork1Folderpath := goFilepath.Join(parametersFolderpath, "Network1")
|
|
parametersNetwork2Folderpath := goFilepath.Join(parametersFolderpath, "Network2")
|
|
|
|
_, err = CreateFolder(parametersFolderpath)
|
|
if (err != nil) { return err }
|
|
_, err = CreateFolder(parametersNetwork1Folderpath)
|
|
if (err != nil) { return err }
|
|
_, err = CreateFolder(parametersNetwork2Folderpath)
|
|
if (err != nil) { return err }
|
|
|
|
return nil
|
|
}
|
|
|
|
// This returns the folderpath where Seekia globalSettings and userData is stored
|
|
// It may also contain the database, unless the user has manually changed the database location
|
|
func GetSeekiaDataFolderPath() (string, error) {
|
|
|
|
localFileDirectory, err := os.UserConfigDir()
|
|
if (err != nil) { return "", err }
|
|
|
|
seekiaDirectory := goFilepath.Join(localFileDirectory, "SeekiaData")
|
|
|
|
return seekiaDirectory, nil
|
|
}
|
|
|
|
// This folder stores all app user data within it
|
|
// Each user has a folder, the name being the name of the user
|
|
func GetAppUsersDataFolderPath()(string, error){
|
|
|
|
seekiaDirectoryPath, err := GetSeekiaDataFolderPath()
|
|
if (err != nil) { return "", err }
|
|
|
|
appUsersDirectory := goFilepath.Join(seekiaDirectoryPath, "UserData")
|
|
|
|
return appUsersDirectory, nil
|
|
}
|
|
|
|
// This returns the folder where the currently signed-in user's data is stored
|
|
func GetAppUserFolderPath() (string, error) {
|
|
|
|
exists, appUserName := appMemory.GetMemoryEntry("AppUser")
|
|
if (exists == false){
|
|
return "", errors.New("GetUserDirectoryPath called when user is not signed in.")
|
|
}
|
|
|
|
appUsersFolderPath, err := GetAppUsersDataFolderPath()
|
|
if (err != nil) { return "", err }
|
|
|
|
userDirectory := goFilepath.Join(appUsersFolderPath, appUserName)
|
|
|
|
return userDirectory, nil
|
|
}
|
|
|
|
// This folder location is customizable in the app.
|
|
func GetAppDatabaseFolderPath()(string, error){
|
|
|
|
exists, directoryPath, err := globalSettings.GetSetting("DatabaseFolderpath")
|
|
if (err != nil) { return "", err }
|
|
if (exists == true){
|
|
return directoryPath, nil
|
|
}
|
|
|
|
seekiaDirectory, err := GetSeekiaDataFolderPath()
|
|
if (err != nil) { return "", err }
|
|
|
|
databasePath := goFilepath.Join(seekiaDirectory, "SeekiaDatabase")
|
|
|
|
return databasePath, nil
|
|
}
|
|
|
|
|
|
// Function will create new file or overwite existing:
|
|
func CreateOrOverwriteFile(content []byte, folderPath string, filename string) error{
|
|
|
|
_, err := CreateFolder(folderPath)
|
|
if (err != nil) { return err }
|
|
|
|
filepath := goFilepath.Join(folderPath, filename)
|
|
|
|
newFile, err := os.Create(filepath)
|
|
if (err != nil) { return err }
|
|
|
|
_, err = newFile.Write(content)
|
|
if (err != nil) {
|
|
newFile.Close()
|
|
return err
|
|
}
|
|
newFile.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -bool: File exists
|
|
// -[]byte: File contents
|
|
// -error
|
|
func GetFileContents(filePath string) (bool, []byte, error){
|
|
|
|
filepathMutex := getFilesystemPathMutex(filePath)
|
|
|
|
filepathMutex.RLock()
|
|
fileContents, err := os.ReadFile(filePath)
|
|
filepathMutex.RUnlock()
|
|
if (err == nil) {
|
|
return true, fileContents, nil
|
|
}
|
|
|
|
isNotExistError := os.IsNotExist(err)
|
|
if (isNotExistError == false){
|
|
return false, nil, err
|
|
}
|
|
|
|
emptyFileContents := make([]byte, 0)
|
|
return false, emptyFileContents, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -[][]byte: List of each file's contents
|
|
// -error
|
|
func GetAllFilesInFolderAsList(folderPath string)([][]byte, error){
|
|
|
|
folderMap, err := GetFolderContentsAsMap(folderPath)
|
|
if (err != nil) { return nil, err }
|
|
|
|
filesList := make([][]byte, 0, len(folderMap))
|
|
|
|
for _, fileContents := range folderMap{
|
|
|
|
filesList = append(filesList, fileContents)
|
|
}
|
|
|
|
return filesList, nil
|
|
}
|
|
|
|
// Outputs:
|
|
// -map[string][]byte: File name -> File Contents
|
|
// -error
|
|
func GetFolderContentsAsMap(folderPath string)(map[string][]byte, error) {
|
|
|
|
folderpathMutex := getFilesystemPathMutex(folderPath)
|
|
|
|
folderpathMutex.RLock()
|
|
fileList, err := os.ReadDir(folderPath)
|
|
folderpathMutex.RUnlock()
|
|
if (err != nil) { return nil, err }
|
|
|
|
folderMap := make(map[string][]byte)
|
|
|
|
for _, filesystemObject := range fileList{
|
|
|
|
filepathIsFolder := filesystemObject.IsDir()
|
|
if (filepathIsFolder == true){
|
|
continue
|
|
}
|
|
|
|
fileName := filesystemObject.Name()
|
|
filePath := goFilepath.Join(folderPath, fileName)
|
|
|
|
filepathMutex := getFilesystemPathMutex(filePath)
|
|
|
|
filepathMutex.RLock()
|
|
fileBytes, err := os.ReadFile(filePath)
|
|
filepathMutex.RUnlock()
|
|
if (err != nil){ return nil, err }
|
|
|
|
folderMap[fileName] = fileBytes
|
|
}
|
|
|
|
return folderMap, nil
|
|
}
|
|
|
|
// This does not work with nested folders
|
|
// You must create all parent folders before calling this function on a folderPath
|
|
//Outputs:
|
|
// -bool: Folder already exists
|
|
// -error
|
|
func CreateFolder(folderPath string)(bool, error){
|
|
|
|
folderpathMutex := getFilesystemPathMutex(folderPath)
|
|
|
|
folderpathMutex.RLock()
|
|
filesystemObject, err := os.Open(folderPath)
|
|
folderpathMutex.RUnlock()
|
|
if (err == nil){
|
|
// Folder already exists
|
|
filesystemObject.Close()
|
|
return true, nil
|
|
}
|
|
|
|
isNotExistErr := os.IsNotExist(err)
|
|
if (isNotExistErr == false){
|
|
return false, err
|
|
}
|
|
|
|
//Folder does not exist, create folder
|
|
|
|
folderpathMutex.Lock()
|
|
err = os.Mkdir(folderPath, os.ModePerm)
|
|
folderpathMutex.Unlock()
|
|
if (err != nil){ return false, err }
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func CheckIfFileExists(filepath string)(bool, error){
|
|
|
|
filepathMutex := getFilesystemPathMutex(filepath)
|
|
|
|
filepathMutex.RLock()
|
|
_, err := os.Stat(filepath)
|
|
filepathMutex.RUnlock()
|
|
if (err != nil) {
|
|
isNotExistErr := os.IsNotExist(err)
|
|
if (isNotExistErr == true) {
|
|
// File does not exist
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// This function works for files and empty directories
|
|
//Outputs:
|
|
// -bool: File/Folder existed and was deleted
|
|
// -error
|
|
func DeleteFileOrFolder(filepath string)(bool, error){
|
|
|
|
filepathMutex := getFilesystemPathMutex(filepath)
|
|
|
|
filepathMutex.Lock()
|
|
err := os.Remove(filepath)
|
|
filepathMutex.Unlock()
|
|
if (err != nil) {
|
|
isNotExistErr := os.IsNotExist(err)
|
|
if (isNotExistErr == true) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -bool: Folder exists and its contents were deleted
|
|
// -error
|
|
func DeleteAllFolderContents(inputFolderpath string)(bool, error){
|
|
|
|
folderpathMutex := getFilesystemPathMutex(inputFolderpath)
|
|
|
|
folderpathMutex.RLock()
|
|
fileList, err := os.ReadDir(inputFolderpath)
|
|
folderpathMutex.RUnlock()
|
|
if (err != nil) {
|
|
isNotExistError := os.IsNotExist(err)
|
|
if (isNotExistError == true){
|
|
// Folder does not exist, nothing to delete
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
for _, filesystemObject := range fileList {
|
|
|
|
fileName := filesystemObject.Name()
|
|
filePath := goFilepath.Join(inputFolderpath, fileName)
|
|
|
|
filepathMutex := getFilesystemPathMutex(filePath)
|
|
|
|
filepathIsFolder := filesystemObject.IsDir()
|
|
if (filepathIsFolder == true){
|
|
|
|
filepathMutex.RLock()
|
|
fileList, err := os.ReadDir(filePath)
|
|
filepathMutex.RUnlock()
|
|
if (err != nil) { return false, err }
|
|
if (len(fileList) != 0){
|
|
|
|
// Filepath is a folder with items in it
|
|
// We recursively delete all items
|
|
|
|
folderExists, err := DeleteAllFolderContents(filePath)
|
|
if (err != nil) { return false, err }
|
|
if (folderExists == false){
|
|
return false, errors.New("Folder not found after being found already during DeleteAllFolderContents.")
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err := DeleteFileOrFolder(filePath)
|
|
if (err != nil) { return false, err }
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
|
|
func GetFolderSizeInBytes(folderPath string)(int64, error){
|
|
|
|
sizeInBytes := int64(0)
|
|
|
|
filepathMutex := getFilesystemPathMutex(folderPath)
|
|
|
|
filepathMutex.RLock()
|
|
filesList, err := os.ReadDir(folderPath)
|
|
filepathMutex.RUnlock()
|
|
if (err != nil) { return 0, err }
|
|
|
|
for _, fileObject := range filesList{
|
|
|
|
isFolder := fileObject.IsDir()
|
|
if (isFolder == true){
|
|
|
|
subFolderName := fileObject.Name()
|
|
|
|
subFolderPath := goFilepath.Join(folderPath, subFolderName)
|
|
|
|
subFolderSize, err := GetFolderSizeInBytes(subFolderPath)
|
|
if (err != nil) { return 0, err }
|
|
|
|
sizeInBytes += subFolderSize
|
|
|
|
continue
|
|
}
|
|
|
|
fileName := fileObject.Name()
|
|
filePath := goFilepath.Join(folderPath, fileName)
|
|
fileExists, fileSize, err := GetFileSize(filePath)
|
|
if (err != nil) { return 0, err }
|
|
if (fileExists == false) {
|
|
return 0, errors.New("File not found after being found during GetFolderSizeInBytes.")
|
|
}
|
|
|
|
sizeInBytes += fileSize
|
|
}
|
|
|
|
return sizeInBytes, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -bool: File exists
|
|
// -int64: Size in bytes
|
|
// -error
|
|
func GetFileSize(filepath string)(bool, int64, error){
|
|
|
|
filepathMutex := getFilesystemPathMutex(filepath)
|
|
|
|
filepathMutex.RLock()
|
|
fileObject, err := os.Stat(filepath)
|
|
filepathMutex.RUnlock()
|
|
if (err != nil){
|
|
isNotExistError := os.IsNotExist(err)
|
|
if (isNotExistError == true){
|
|
return false, 0, nil
|
|
}
|
|
return false, 0, err
|
|
}
|
|
|
|
fileSizeBytes := fileObject.Size()
|
|
|
|
return true, fileSizeBytes, nil
|
|
}
|
|
|
|
|