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