355 lines
7.3 KiB
Go
355 lines
7.3 KiB
Go
|
package jsondb
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"git.0x0001f346.de/andreas/utils"
|
||
|
)
|
||
|
|
||
|
type Cluster map[string]Database
|
||
|
type Database map[string]DatabaseValue
|
||
|
type DatabaseValue interface{}
|
||
|
|
||
|
var cluster Cluster = Cluster{}
|
||
|
var locationOfCluster string = utils.GetPathToParrentFolderOfThisExecutable()
|
||
|
var nameOfCluster string = "jsondb"
|
||
|
|
||
|
const databaseExtension string = ".json"
|
||
|
|
||
|
// CreateDatabase creates a database or returns nil if it already exists
|
||
|
func CreateDatabase(name string) error {
|
||
|
_, databaseAlreadyExists := cluster[name]
|
||
|
if databaseAlreadyExists {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
err := safeDatabase(name, Database{})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// DeleteDatabase deletes a database
|
||
|
func DeleteDatabase(nameOfDatabase string) error {
|
||
|
err := os.Remove(getFullPathToDatabase(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 {
|
||
|
err := ReloadDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database, err := getDeepCopyOfDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
delete(database, key)
|
||
|
err = safeDatabase(nameOfDatabase, database)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[nameOfDatabase] = database
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// GetDatabase returns a database if it exists
|
||
|
func GetDatabase(name string) (Database, error) {
|
||
|
database, err := getDeepCopyOfDatabase(name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return database, nil
|
||
|
}
|
||
|
|
||
|
// GetKeyFromDatabase returns a key from a database if both exist
|
||
|
func GetKeyFromDatabase(nameOfDatabase string, key string) (DatabaseValue, error) {
|
||
|
database, databaseExists := cluster[nameOfDatabase]
|
||
|
if !databaseExists {
|
||
|
return nil, errors.New("database does not exist")
|
||
|
}
|
||
|
|
||
|
value, keyExists := database[key]
|
||
|
if !keyExists {
|
||
|
return nil, errors.New("key does not exist")
|
||
|
}
|
||
|
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
// GetLocationOfCluster returns locationOfCluster
|
||
|
func GetLocationOfCluster() string {
|
||
|
return locationOfCluster
|
||
|
}
|
||
|
|
||
|
// GetNameOfCluster returns nameOfCluster
|
||
|
func GetNameOfCluster() string {
|
||
|
return nameOfCluster
|
||
|
}
|
||
|
|
||
|
// LoadCluster loads all databases from the current cluster
|
||
|
func LoadCluster() error {
|
||
|
if !utils.DoesFolderExist(getFullPathToCluster()) {
|
||
|
err := createCluster()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, p := range utils.GetAllFilesInFolderByExtension(getFullPathToCluster(), databaseExtension) {
|
||
|
databaseName := strings.TrimSuffix(path.Base(p), filepath.Ext(path.Base(p)))
|
||
|
if !isValidName(databaseName) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
database, err := loadDatabase(databaseName)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[databaseName] = database
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ReloadDatabase reloads a database from file
|
||
|
func ReloadDatabase(name string) error {
|
||
|
_, databaseExists := cluster[name]
|
||
|
if !databaseExists {
|
||
|
return errors.New("database does not exist")
|
||
|
}
|
||
|
|
||
|
database, err := loadDatabase(name)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[name] = database
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RenameDatabase renames a database
|
||
|
func RenameDatabase(nameOfDatabase string, newNameOfDatabase string) error {
|
||
|
err := ReloadDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database, err := getDeepCopyOfDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = safeDatabase(newNameOfDatabase, database)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[newNameOfDatabase] = database
|
||
|
|
||
|
err = DeleteDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RenameKeyInDatabase renames a key in a database and write it to file
|
||
|
func RenameKeyInDatabase(nameOfDatabase string, keyOld string, keyNew string) error {
|
||
|
err := ReloadDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database, err := getDeepCopyOfDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database[keyNew] = database[keyOld]
|
||
|
delete(database, keyOld)
|
||
|
err = safeDatabase(nameOfDatabase, database)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[nameOfDatabase] = database
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetDatabase sets a database and writes it to file
|
||
|
func SetDatabase(name string, database Database) error {
|
||
|
_, databaseExists := cluster[name]
|
||
|
if !databaseExists {
|
||
|
return errors.New("database does not exist")
|
||
|
}
|
||
|
|
||
|
err := safeDatabase(name, database)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[name] = database
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetKeyInDatabase sets a key from in a database and write it to file
|
||
|
func SetKeyInDatabase(nameOfDatabase string, key string, value DatabaseValue) error {
|
||
|
err := ReloadDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database, err := getDeepCopyOfDatabase(nameOfDatabase)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
database[key] = value
|
||
|
err = safeDatabase(nameOfDatabase, database)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cluster[nameOfDatabase] = database
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetLocationOfCluster sets locationOfCluster if p is a valid folder
|
||
|
func SetLocationOfCluster(location string) error {
|
||
|
if !utils.DoesFolderExist(location) {
|
||
|
return errors.New("not a valid cluster location")
|
||
|
}
|
||
|
|
||
|
locationOfCluster = location
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetNameOfCluster sets nameOfCluster if s is a valid cluster name
|
||
|
func SetNameOfCluster(name string) error {
|
||
|
if !isValidName(name) {
|
||
|
return errors.New("not a valid cluster name")
|
||
|
}
|
||
|
|
||
|
nameOfCluster = name
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func getFullPathToCluster() string {
|
||
|
return filepath.Join(
|
||
|
locationOfCluster,
|
||
|
nameOfCluster,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
func getFullPathToDatabase(name string) string {
|
||
|
return filepath.Join(
|
||
|
getFullPathToCluster(),
|
||
|
fmt.Sprintf("%s%s", name, databaseExtension),
|
||
|
)
|
||
|
}
|
||
|
|
||
|
func isValidName(s string) bool {
|
||
|
if len(s) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
whitelist := utils.BuildWhitelistForStringSanitisation(
|
||
|
[]string{
|
||
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||
|
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||
|
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||
|
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
||
|
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||
|
},
|
||
|
)
|
||
|
|
||
|
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) {
|
||
|
dbContentAsString, err := utils.LoadStringFromFile(
|
||
|
getFullPathToDatabase(name),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return Database{}, err
|
||
|
}
|
||
|
|
||
|
var database Database
|
||
|
err = json.Unmarshal([]byte(dbContentAsString), &database)
|
||
|
if err != nil {
|
||
|
return Database{}, err
|
||
|
}
|
||
|
|
||
|
return database, nil
|
||
|
}
|
||
|
|
||
|
func safeDatabase(name string, database Database) error {
|
||
|
databaseAsJSONBytes, err := json.MarshalIndent(database, "", "\t")
|
||
|
if err != nil {
|
||
|
return errors.New("could not marshal database: " + name)
|
||
|
}
|
||
|
|
||
|
err = utils.WriteStringToFile(getFullPathToDatabase(name), string(databaseAsJSONBytes))
|
||
|
if err != nil {
|
||
|
return errors.New("could not write to file: " + getFullPathToDatabase(name))
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|