From 8c3aaa8261c9799a01baaafb17d14d88fa3b3457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 13 Apr 2019 05:43:33 +0200 Subject: [PATCH] sklad: move the database into its own file --- sklad/db.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++ sklad/main.go | 143 ++---------------------------------------------- 2 files changed, 154 insertions(+), 138 deletions(-) create mode 100644 sklad/db.go diff --git a/sklad/db.go b/sklad/db.go new file mode 100644 index 0000000..c036cb8 --- /dev/null +++ b/sklad/db.go @@ -0,0 +1,149 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" +) + +type Series struct { + Prefix string // PK: prefix + Description string // what kind of containers this is for +} + +type Container struct { + Series string // PK: what series does this belong to + Number uint // PK: order within the series + Parent ContainerId // the container we're in, if any, otherwise "" + Description string // description and/or contents of this container +} + +type ContainerId string + +func (c *Container) Id() ContainerId { + return ContainerId(fmt.Sprintf("%s%s%d", db.Prefix, c.Series, c.Number)) +} + +type Database struct { + Password string // password for web users + Prefix string // prefix for all container IDs + Series []*Series // all known series + Containers []*Container // all known containers +} + +var ( + dbPath string + db Database + dbLast Database + dbLog io.Writer + + indexSeries = map[string]*Series{} + indexContainer = map[ContainerId]*Container{} + indexChildren = map[ContainerId][]*Container{} +) + +// TODO: Some functions to add, remove and change things in the database. +// Indexes must be kept valid, just like any invariants. + +// TODO: A function for fulltext search in series (1. Prefix, 2. Description). + +// TODO: A function for fulltext search in containers (1. Id, 2. Description). + +func dbCommit() error { + // Back up the current database contents. + e := json.NewEncoder(dbLog) + e.SetIndent("", " ") + if err := e.Encode(&dbLast); err != nil { + return err + } + + // Atomically replace the current database file. + tempPath := dbPath + ".new" + temp, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer temp.Close() + + e = json.NewEncoder(temp) + e.SetIndent("", " ") + if err := e.Encode(&db); err != nil { + return err + } + + if err := os.Rename(tempPath, dbPath); err != nil { + return err + } + + dbLast = db + return nil +} + +// loadDatabase loads the database from a simple JSON file. We do not use +// any SQL stuff or even external KV storage because there is no real need +// for our trivial use case, with our general amount of data. +func loadDatabase() error { + dbFile, err := os.Open(dbPath) + if err != nil { + return err + } + if err := json.NewDecoder(dbFile).Decode(&db); err != nil { + return err + } + + // Further validate the database. + if db.Prefix == "" { + return errors.New("misconfigured prefix") + } + + // Construct indexes for primary keys, validate against duplicates. + for _, pv := range db.Series { + if _, ok := indexSeries[pv.Prefix]; ok { + return fmt.Errorf("duplicate series: %s", pv.Prefix) + } + indexSeries[pv.Prefix] = pv + } + for _, pv := range db.Containers { + id := pv.Id() + if _, ok := indexContainer[id]; ok { + return fmt.Errorf("duplicate container: %s", id) + } + indexContainer[id] = pv + } + + // Construct an index that goes from parent containers to their children. + for _, pv := range db.Containers { + if pv.Parent == "" { + continue + } + if _, ok := indexContainer[pv.Parent]; !ok { + return fmt.Errorf("container %s has a nonexistent parent %s", + pv.Id(), pv.Parent) + } + indexChildren[pv.Parent] = append(indexChildren[pv.Parent], pv) + } + + // Validate that no container is a parent of itself on any level. + for _, pv := range db.Containers { + parents := map[ContainerId]bool{pv.Id(): true} + for pv.Parent != "" { + if parents[pv.Parent] { + return fmt.Errorf("%s contains itself", pv.Parent) + } + parents[pv.Parent] = true + pv = indexContainer[pv.Parent] + } + } + + // Open database log file for appending. + if dbLog, err = os.OpenFile(dbPath+".log", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil { + return err + } + + // Remember the current state of the database. + dbLast = db + return nil +} diff --git a/sklad/main.go b/sklad/main.go index 28fb258..c1eaa99 100644 --- a/sklad/main.go +++ b/sklad/main.go @@ -1,155 +1,19 @@ package main import ( - "encoding/json" - "errors" - "fmt" "html/template" - "io" "log" "net/http" "os" ) -type Series struct { - Prefix string // PK: prefix - Description string // what kind of containers this is for -} - -type Container struct { - Series string // PK: what series does this belong to - Number uint // PK: order within the series - Parent ContainerId // the container we're in, if any, otherwise "" - Description string // description and/or contents of this container -} - -type ContainerId string - -func (c *Container) Id() ContainerId { - return ContainerId(fmt.Sprintf("%s%s%d", db.Prefix, c.Series, c.Number)) -} - -type Database struct { - Password string // password for web users - Prefix string // prefix for all container IDs - Series []*Series // all known series - Containers []*Container // all known containers -} - var ( templates *template.Template - // TODO: Some kind of session storage, somewhere. - dbPath string - db Database - dbLast Database - dbLog io.Writer - - indexSeries = map[string]*Series{} - indexContainer = map[ContainerId]*Container{} - indexChildren = map[ContainerId][]*Container{} + // session storage: UUID -> net.SplitHostPort(http.Server.RemoteAddr)[0] + sessions = map[string]string{} ) -// TODO: Some functions to add, remove and change things in the database. -// Indexes must be kept valid, just like any invariants. - -func dbCommit() error { - // Back up the current database contents. - e := json.NewEncoder(dbLog) - e.SetIndent("", " ") - if err := e.Encode(&dbLast); err != nil { - return err - } - - // Atomically replace the current database file. - tempPath := dbPath + ".new" - temp, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return err - } - defer temp.Close() - - e = json.NewEncoder(temp) - e.SetIndent("", " ") - if err := e.Encode(&db); err != nil { - return err - } - - if err := os.Rename(tempPath, dbPath); err != nil { - return err - } - - dbLast = db - return nil -} - -// loadDatabase loads the database from a simple JSON file. We do not use -// any SQL stuff or even external KV storage because there is no real need -// for our trivial use case, with our general amount of data. -func loadDatabase() error { - dbFile, err := os.Open(dbPath) - if err != nil { - return err - } - if err := json.NewDecoder(dbFile).Decode(&db); err != nil { - return err - } - - // Further validate the database. - if db.Prefix == "" { - return errors.New("misconfigured prefix") - } - - // Construct indexes for primary keys, validate against duplicates. - for _, pv := range db.Series { - if _, ok := indexSeries[pv.Prefix]; ok { - return fmt.Errorf("duplicate series: %s", pv.Prefix) - } - indexSeries[pv.Prefix] = pv - } - for _, pv := range db.Containers { - id := pv.Id() - if _, ok := indexContainer[id]; ok { - return fmt.Errorf("duplicate container: %s", id) - } - indexContainer[id] = pv - } - - // Construct an index that goes from parent containers to their children. - for _, pv := range db.Containers { - if pv.Parent == "" { - continue - } - if _, ok := indexContainer[pv.Parent]; !ok { - return fmt.Errorf("container %s has a nonexistent parent %s", - pv.Id(), pv.Parent) - } - indexChildren[pv.Parent] = append(indexChildren[pv.Parent], pv) - } - - // Validate that no container is a parent of itself on any level. - for _, pv := range db.Containers { - parents := map[ContainerId]bool{pv.Id(): true} - for pv.Parent != "" { - if parents[pv.Parent] { - return fmt.Errorf("%s contains itself", pv.Parent) - } - parents[pv.Parent] = true - pv = indexContainer[pv.Parent] - } - } - - // Open database log file for appending. - if dbLog, err = os.OpenFile(dbPath+".log", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil { - return err - } - - // Remember the current state of the database. - dbLast = db - return nil -} - func main() { if len(os.Args) != 3 { log.Fatalf("usage: %s ADDRESS DATABASE\n", os.Args[0]) @@ -170,6 +34,9 @@ func main() { log.Fatalln(err) } + // TODO: Eventually we will need to load a font file for label printing. + // - The path might be part of configuration, or implicit by filename. + // TODO: Some routing, don't forget about sessions. // - https://stackoverflow.com/a/33880971/76313 //