aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sklad/db.go149
-rw-r--r--sklad/main.go143
2 files changed, 154 insertions, 138 deletions
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
//