added locks for databases; refactoring

This commit is contained in:
Andreas Schulte 2023-10-24 21:10:49 +02:00
parent 1832d06382
commit b9a2ca4207
Signed by: andreas
GPG Key ID: DCD1B6A247B69DB6

200
jsondb.go
View File

@ -8,6 +8,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"git.0x0001f346.de/andreas/utils" "git.0x0001f346.de/andreas/utils"
) )
@ -18,62 +19,81 @@ type Database map[string]interface{}
var cluster Cluster = Cluster{} var cluster Cluster = Cluster{}
var locationOfCluster string = utils.GetPathToParrentFolderOfThisExecutable() var locationOfCluster string = utils.GetPathToParrentFolderOfThisExecutable()
var nameOfCluster string = "jsondb" var nameOfCluster string = "jsondb"
var databaseIsLocked map[string]bool = map[string]bool{}
const databaseExtension string = ".json" const databaseExtension string = ".json"
// CreateDatabase creates a database or returns nil if it already exists // CreateDatabase creates a database or returns nil if it already exists
func CreateDatabase(name string) error { func CreateDatabase(nameOfDatabase string) error {
_, databaseAlreadyExists := cluster[name] _, databaseAlreadyExists := cluster[nameOfDatabase]
if databaseAlreadyExists { if databaseAlreadyExists {
return nil return nil
} }
err := safeDatabase(name, Database{}) err := safeDatabase(nameOfDatabase, Database{})
if err != nil { if err != nil {
return err return err
} }
unlockDatabase(nameOfDatabase)
return nil return nil
} }
// DeleteDatabase deletes a database // DeleteDatabase deletes a database
func DeleteDatabase(nameOfDatabase string) error { func DeleteDatabase(nameOfDatabase string) error {
err := os.Remove(getFullPathToDatabase(nameOfDatabase)) _, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return nil
}
lockDatabase(nameOfDatabase)
err := deleteDatabase(nameOfDatabase)
if err != nil { if err != nil {
return err return err
} }
delete(cluster, nameOfDatabase)
return nil return nil
} }
// DeleteKeyFromDatabase deletes a key in a database and write it to file // DeleteKeyFromDatabase deletes a key in a database and write it to file
func DeleteKeyFromDatabase(nameOfDatabase string, key string) error { func DeleteKeyFromDatabase(nameOfDatabase string, key string) error {
_, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return errors.New("database does not exist")
}
lockDatabase(nameOfDatabase)
err := ReloadDatabase(nameOfDatabase) err := ReloadDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
database, err := getDeepCopyOfDatabase(nameOfDatabase) database, err := getDeepCopyOfDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
delete(database, key) delete(database, key)
err = safeDatabase(nameOfDatabase, database) err = safeDatabase(nameOfDatabase, database)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
cluster[nameOfDatabase] = database cluster[nameOfDatabase] = database
unlockDatabase(nameOfDatabase)
return nil return nil
} }
// GetDatabase returns a database if it exists // GetDatabase returns a database if it exists
func GetDatabase(name string) (Database, error) { func GetDatabase(nameOfDatabase string) (Database, error) {
database, err := getDeepCopyOfDatabase(name) database, err := getDeepCopyOfDatabase(nameOfDatabase)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -127,64 +147,89 @@ func LoadCluster() error {
} }
cluster[databaseName] = database cluster[databaseName] = database
databaseIsLocked[databaseName] = false
} }
return nil return nil
} }
// ReloadDatabase reloads a database from file // ReloadDatabase reloads a database from file
func ReloadDatabase(name string) error { func ReloadDatabase(nameOfDatabase string) error {
_, databaseExists := cluster[name] _, databaseExists := cluster[nameOfDatabase]
if !databaseExists { if !databaseExists {
return errors.New("database does not exist") return errors.New("database does not exist")
} }
database, err := loadDatabase(name) database, err := loadDatabase(nameOfDatabase)
if err != nil { if err != nil {
return err return err
} }
cluster[name] = database cluster[nameOfDatabase] = database
return nil return nil
} }
// RenameDatabase renames a database // RenameDatabase renames a database
func RenameDatabase(nameOfDatabase string, newNameOfDatabase string) error { func RenameDatabase(nameOfDatabase string, newNameOfDatabase string) error {
_, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return errors.New("database does not exist")
}
lockDatabase(nameOfDatabase)
err := ReloadDatabase(nameOfDatabase) err := ReloadDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
database, err := getDeepCopyOfDatabase(nameOfDatabase) database, err := getDeepCopyOfDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
err = safeDatabase(newNameOfDatabase, database) err = safeDatabase(newNameOfDatabase, database)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
cluster[newNameOfDatabase] = database cluster[newNameOfDatabase] = database
lockDatabase(newNameOfDatabase)
err = DeleteDatabase(nameOfDatabase) err = deleteDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
unlockDatabase(newNameOfDatabase)
return err return err
} }
unlockDatabase(newNameOfDatabase)
return nil return nil
} }
// RenameKeyInDatabase renames a key in a database and write it to file // RenameKeyInDatabase renames a key in a database and write it to file
func RenameKeyInDatabase(nameOfDatabase string, keyOld string, keyNew string) error { func RenameKeyInDatabase(nameOfDatabase string, keyOld string, keyNew string) error {
_, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return errors.New("database does not exist")
}
lockDatabase(nameOfDatabase)
err := ReloadDatabase(nameOfDatabase) err := ReloadDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
database, err := getDeepCopyOfDatabase(nameOfDatabase) database, err := getDeepCopyOfDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
@ -192,50 +237,67 @@ func RenameKeyInDatabase(nameOfDatabase string, keyOld string, keyNew string) er
delete(database, keyOld) delete(database, keyOld)
err = safeDatabase(nameOfDatabase, database) err = safeDatabase(nameOfDatabase, database)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
cluster[nameOfDatabase] = database cluster[nameOfDatabase] = database
unlockDatabase(nameOfDatabase)
return nil return nil
} }
// SetDatabase sets a database and writes it to file // SetDatabase sets a database and writes it to file
func SetDatabase(name string, database Database) error { func SetDatabase(nameOfDatabase string, database Database) error {
_, databaseExists := cluster[name] _, databaseExists := cluster[nameOfDatabase]
if !databaseExists { if !databaseExists {
return errors.New("database does not exist") return errors.New("database does not exist")
} }
err := safeDatabase(name, database) lockDatabase(nameOfDatabase)
err := safeDatabase(nameOfDatabase, database)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
cluster[name] = database cluster[nameOfDatabase] = database
unlockDatabase(nameOfDatabase)
return nil return nil
} }
// SetKeyInDatabase sets a key from in a database and write it to file // SetKeyInDatabase sets a key from in a database and write it to file
func SetKeyInDatabase(nameOfDatabase string, key string, value interface{}) error { func SetKeyInDatabase(nameOfDatabase string, key string, value interface{}) error {
_, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return errors.New("database does not exist")
}
lockDatabase(nameOfDatabase)
err := ReloadDatabase(nameOfDatabase) err := ReloadDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
database, err := getDeepCopyOfDatabase(nameOfDatabase) database, err := getDeepCopyOfDatabase(nameOfDatabase)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
database[key] = value database[key] = value
err = safeDatabase(nameOfDatabase, database) err = safeDatabase(nameOfDatabase, database)
if err != nil { if err != nil {
unlockDatabase(nameOfDatabase)
return err return err
} }
cluster[nameOfDatabase] = database cluster[nameOfDatabase] = database
unlockDatabase(nameOfDatabase)
return nil return nil
} }
@ -262,6 +324,50 @@ func SetNameOfCluster(name string) error {
return nil return nil
} }
func createCluster() error {
if utils.DoesFolderExist(getFullPathToCluster()) {
return nil
}
err := utils.CreateFolder(getFullPathToCluster())
if err != nil {
return err
}
return nil
}
func deleteDatabase(nameOfDatabase string) error {
_, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return nil
}
err := os.Remove(getFullPathToDatabase(nameOfDatabase))
if err != nil {
return err
}
delete(cluster, nameOfDatabase)
delete(databaseIsLocked, nameOfDatabase)
return nil
}
func getDeepCopyOfDatabase(nameOfDatabase string) (Database, error) {
originalDatabase, databaseExists := cluster[nameOfDatabase]
if !databaseExists {
return nil, errors.New("database does not exist")
}
copyOfDatabase := make(Database)
for key, value := range originalDatabase {
copyOfDatabase[key] = value
}
return copyOfDatabase, nil
}
func getFullPathToCluster() string { func getFullPathToCluster() string {
return filepath.Join( return filepath.Join(
locationOfCluster, locationOfCluster,
@ -269,10 +375,10 @@ func getFullPathToCluster() string {
) )
} }
func getFullPathToDatabase(name string) string { func getFullPathToDatabase(nameOfDatabase string) string {
return filepath.Join( return filepath.Join(
getFullPathToCluster(), getFullPathToCluster(),
fmt.Sprintf("%s%s", name, databaseExtension), fmt.Sprintf("%s%s", nameOfDatabase, databaseExtension),
) )
} }
@ -294,22 +400,9 @@ func isValidName(s string) bool {
return !utils.DoesStringContainsNonWhitelistedSubstrings(s, whitelist) return !utils.DoesStringContainsNonWhitelistedSubstrings(s, whitelist)
} }
func createCluster() error { func loadDatabase(nameOfDatabase string) (Database, error) {
if utils.DoesFolderExist(getFullPathToCluster()) {
return nil
}
err := utils.CreateFolder(getFullPathToCluster())
if err != nil {
return err
}
return nil
}
func loadDatabase(name string) (Database, error) {
dbContentAsString, err := utils.LoadStringFromFile( dbContentAsString, err := utils.LoadStringFromFile(
getFullPathToDatabase(name), getFullPathToDatabase(nameOfDatabase),
) )
if err != nil { if err != nil {
return Database{}, err return Database{}, err
@ -324,30 +417,43 @@ func loadDatabase(name string) (Database, error) {
return database, nil return database, nil
} }
func safeDatabase(name string, database Database) error { func lockDatabase(nameOfDatabase string) {
databaseAsJSONBytes, err := json.MarshalIndent(database, "", "\t") waitForDatabaseToUnlock(nameOfDatabase)
if err != nil {
return errors.New("could not marshal database: " + name) databaseIsLocked[nameOfDatabase] = true
} }
err = utils.WriteStringToFile(getFullPathToDatabase(name), string(databaseAsJSONBytes)) func safeDatabase(nameOfDatabase string, database Database) error {
databaseAsJSONBytes, err := json.MarshalIndent(database, "", "\t")
if err != nil { if err != nil {
return errors.New("could not write to file: " + getFullPathToDatabase(name)) return errors.New("could not marshal database: " + nameOfDatabase)
}
err = utils.WriteStringToFile(getFullPathToDatabase(nameOfDatabase), string(databaseAsJSONBytes))
if err != nil {
return errors.New("could not write to file: " + getFullPathToDatabase(nameOfDatabase))
} }
return nil return nil
} }
func getDeepCopyOfDatabase(name string) (Database, error) { func unlockDatabase(nameOfDatabase string) {
originalDatabase, databaseExists := cluster[name] databaseIsLocked[nameOfDatabase] = false
}
func waitForDatabaseToUnlock(nameOfDatabase string) {
isLocked, databaseExists := databaseIsLocked[nameOfDatabase]
if !databaseExists { if !databaseExists {
return nil, errors.New("database does not exist") return
} }
copyOfDatabase := make(Database) for {
for key, value := range originalDatabase { if !isLocked {
copyOfDatabase[key] = value break
} }
return copyOfDatabase, nil time.Sleep(5 * time.Millisecond)
isLocked = databaseIsLocked[nameOfDatabase]
}
} }