commit 5ebb59e9776b3fa17de96f8778dea5d42d3a2557
parent e1dc94831c592dd1ac5f5038020e743b969a42fd
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Fri, 9 Apr 2021 21:08:42 +0800
fix: use raw boltdb as underlying database tech to simplify stack
Diffstat:
9 files changed, 218 insertions(+), 154 deletions(-)
diff --git a/cmd/kanban/main.go b/cmd/kanban/main.go
@@ -6,14 +6,13 @@ import (
"os"
"path/filepath"
- "git.sr.ht/~jackmordaunt/kanban/storage/lazy"
-
"github.com/spf13/pflag"
"gioui.org/font/gofont"
"gioui.org/unit"
"gioui.org/widget/material"
"git.sr.ht/~jackmordaunt/kanban/storage"
+ "git.sr.ht/~jackmordaunt/kanban/storage/lazy"
"git.sr.ht/~jackmordaunt/kanban/storage/mem"
"gioui.org/app"
diff --git a/cmd/kanban/ui.go b/cmd/kanban/ui.go
@@ -98,18 +98,21 @@ type UI struct {
// Loop runs the event loop until terminated.
func (ui *UI) Loop() error {
- var (
- ops op.Ops
- events = ui.Window.Events()
- )
- projects, err := ui.Storage.List()
+ count, err := ui.Storage.Count()
if err != nil {
- return fmt.Errorf("loading projects: %v", err)
+ return fmt.Errorf("counting projects: %w", err)
+ }
+ ui.Projects = make(Projects, count)
+ if err := ui.Storage.Load(ui.Projects); err != nil {
+ return fmt.Errorf("loading projects: %w", err)
}
- ui.Projects = projects
if len(ui.Projects) > 0 {
ui.Project = &ui.Projects[0]
}
+ var (
+ ops op.Ops
+ events = ui.Window.Events()
+ )
for event := range events {
switch event := (event).(type) {
case system.DestroyEvent:
@@ -136,7 +139,7 @@ func (ui *UI) Shutdown() error {
// Update state based on events.
//
-// TODO: investigate best way to handle errors. Some errors are for the user
+// TODO(jfm): investigate best way to handle errors. Some errors are for the user
// and others are for the devs.
// Currently errors are just printed; not great for windowed applications.
func (ui *UI) Update(gtx C) {
@@ -294,11 +297,7 @@ func (ui *UI) layoutRail(gtx C) D {
var (
rc []control.RailChild
)
- projects, err := ui.Storage.List()
- if err != nil {
- log.Printf("error: loading projects: %v", err)
- }
- for _, p := range projects {
+ for _, p := range ui.Projects {
p := p
rc = append(rc, control.Destination(p.Name, func(gtx C) D {
return layout.Stack{
@@ -401,6 +400,9 @@ func (ui *UI) layoutContent(gtx C) D {
}.Layout(
gtx,
func() (panels []layout.FlexChild) {
+ if len(ui.Panels) == 0 {
+ return panels
+ }
// @decouple this iteration relies on the coincidence that panels are ordered the same.
for ii, stage := range ui.Project.Stages {
stage := stage
diff --git a/go.mod b/go.mod
@@ -5,10 +5,9 @@ go 1.15
require (
gioui.org v0.0.0-20210210205757-7286b075e2fb
gioui.org/x v0.0.0-20210120222453-b55819bc712b
- github.com/asdine/storm/v3 v3.2.1
+ github.com/boltdb/bolt v1.3.1
github.com/google/uuid v1.2.0
github.com/spf13/pflag v1.0.5
- go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/exp v0.0.0-20210212053707-62dc52270d37
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
diff --git a/go.sum b/go.sum
@@ -6,39 +6,14 @@ gioui.org v0.0.0-20210210205757-7286b075e2fb/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B
gioui.org/x v0.0.0-20210120222453-b55819bc712b h1:C7/1fHOBkcypkgWopz8Mf+eDJggTWQWlL315KjhDVDQ=
gioui.org/x v0.0.0-20210120222453-b55819bc712b/go.mod h1:+CDNGkslHDIe1EK2Q4E0G98qRllCaQrL6qtjBhBjSAo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
-github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
-github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
-github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
-github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
-github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
+github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
-github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
-go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
-go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -63,21 +38,16 @@ golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hM
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191105084925-a882066a44e0 h1:QPlSTtPE2k6PZPasQUbzuK3p9JbS+vMXYVto8g/yrsg=
-golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -89,7 +59,3 @@ golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/storage/bolt/bolt.go b/storage/bolt/bolt.go
@@ -0,0 +1,166 @@
+package bolt
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "git.sr.ht/~jackmordaunt/kanban"
+ "git.sr.ht/~jackmordaunt/kanban/storage"
+ "github.com/boltdb/bolt"
+ "github.com/google/uuid"
+)
+
+type Bucket []byte
+
+func (b Bucket) String() string {
+ return string(b)
+}
+
+var (
+ BucketProject Bucket = Bucket("Project")
+)
+
+var _ storage.Storer = (*Storer)(nil)
+
+type Storer struct {
+ *bolt.DB
+}
+
+func Open(path string) (*Storer, error) {
+ db, err := bolt.Open(path, 0660, nil)
+ if err != nil {
+ return nil, fmt.Errorf("opening database file: %w", err)
+ }
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucketIfNotExists(BucketProject); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, fmt.Errorf("initializing buckets: %w", err)
+ }
+ return &Storer{DB: db}, nil
+}
+
+func (db *Storer) Create(p kanban.Project) error {
+ id, err := p.ID.MarshalBinary()
+ if err != nil {
+ return fmt.Errorf("serializing project ID: %w", err)
+ }
+ v, err := json.Marshal(p)
+ if err != nil {
+ return fmt.Errorf("serializing project: %w", err)
+ }
+ return db.Update(func(tx *bolt.Tx) error {
+ b := tx.Bucket(BucketProject)
+ if b == nil {
+ return fmt.Errorf("bucket not initialized: %s", BucketProject)
+ }
+ if b.Get(id) != nil {
+ return fmt.Errorf("project already exists for ID %q", p.ID)
+ }
+ return b.Put(id, v)
+ })
+}
+
+func (db *Storer) Save(projects ...kanban.Project) error {
+ return db.Update(func(tx *bolt.Tx) error {
+ for _, p := range projects {
+ if p.ID == uuid.Nil {
+ continue
+ }
+ id, err := p.ID.MarshalBinary()
+ if err != nil {
+ return fmt.Errorf("serializing project ID: %w", err)
+ }
+ v, err := json.Marshal(p)
+ if err != nil {
+ return fmt.Errorf("serializing project: %w", err)
+ }
+ if err := tx.Bucket(BucketProject).Put(id, v); err != nil {
+ return fmt.Errorf("updating project: %w", err)
+ }
+ }
+ return nil
+ })
+}
+
+func (db *Storer) Lookup(name string) (p kanban.Project, ok bool, err error) {
+ // @enhance: index by name
+ return p, ok, db.View(func(tx *bolt.Tx) error {
+ c := tx.Bucket(BucketProject).Cursor()
+ for k, v := c.First(); k != nil; k, v = c.Next() {
+ if err := json.Unmarshal(v, &p); err != nil {
+ return fmt.Errorf("deserializing project: %w", err)
+ }
+ if p.Name == name {
+ ok = true
+ break
+ }
+ }
+ return nil
+ })
+}
+
+func (db *Storer) Find(id uuid.UUID) (p kanban.Project, ok bool, err error) {
+ key, err := id.MarshalBinary()
+ if err != nil {
+ return p, false, fmt.Errorf("serializing id: %w", err)
+ }
+ return p, ok, db.View(func(tx *bolt.Tx) error {
+ if err := json.Unmarshal(tx.Bucket(BucketProject).Get(key), &p); err != nil {
+ return fmt.Errorf("deserializing project: %w", err)
+ }
+ ok = true
+ return nil
+ })
+}
+
+func (db *Storer) List() (list []kanban.Project, err error) {
+ return list, db.View(func(tx *bolt.Tx) error {
+ c := tx.Bucket(BucketProject).Cursor()
+ for k, v := c.First(); k != nil; k, v = c.Next() {
+ var p kanban.Project
+ if err := json.Unmarshal(v, &p); err != nil {
+ return fmt.Errorf("deserializing project: %w", err)
+ }
+ list = append(list, p)
+ }
+ return nil
+ })
+}
+
+func (db *Storer) Load(projects []kanban.Project) error {
+ if len(projects) > 0 {
+ if projects[0].ID == uuid.Nil {
+ list, err := db.List()
+ if err != nil {
+ return err
+ }
+ copy(projects, list)
+ return nil
+ }
+ }
+ return db.View(func(tx *bolt.Tx) error {
+ for ii, p := range projects {
+ id, err := p.ID.MarshalBinary()
+ if err != nil {
+ return fmt.Errorf("serializing project ID: %w", err)
+ }
+ if err := json.Unmarshal(tx.Bucket(BucketProject).Get(id), &projects[ii]); err != nil {
+ return fmt.Errorf("deserializing project: %w", err)
+ }
+ }
+ return nil
+ })
+}
+
+func (db *Storer) Count() (count int, err error) {
+ return count, db.View(func(tx *bolt.Tx) error {
+ c := tx.Bucket(BucketProject).Cursor()
+ for k, _ := c.First(); k != nil; k, _ = c.Next() {
+ count++
+ }
+ return nil
+ })
+}
diff --git a/storage/lazy/lazy.go b/storage/lazy/lazy.go
@@ -8,8 +8,9 @@ import (
"git.sr.ht/~jackmordaunt/kanban"
"git.sr.ht/~jackmordaunt/kanban/storage"
+ "git.sr.ht/~jackmordaunt/kanban/storage/bolt"
"git.sr.ht/~jackmordaunt/kanban/storage/mem"
- "git.sr.ht/~jackmordaunt/kanban/storage/storm"
+ "github.com/google/uuid"
)
var _ storage.Storer = (*Storer)(nil)
@@ -17,13 +18,13 @@ var _ storage.Storer = (*Storer)(nil)
// Storer writes to disk when a change has been detected.
type Storer struct {
Cache *mem.Storer
- Disk *storm.Storer
+ Disk *bolt.Storer
}
// Open a lazy storer, initializing the underlying database at the path
// specified.
func Open(path string) (*Storer, error) {
- disk, err := storm.Open(path)
+ disk, err := bolt.Open(path)
if err != nil {
return nil, err
}
@@ -71,6 +72,12 @@ func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
return s.Cache.Lookup(name)
}
+// Load a project by ID.
+// Bool indicates whether a project exists for that ID.
+func (s *Storer) Find(id uuid.UUID) (kanban.Project, bool, error) {
+ return s.Cache.Find(id)
+}
+
// List projects.
func (s *Storer) List() ([]kanban.Project, error) {
return s.Disk.List()
@@ -109,3 +116,7 @@ func (s *Storer) Load(projects []kanban.Project) error {
func (s *Storer) Close() error {
return s.Disk.DB.Close()
}
+
+func (s *Storer) Count() (int, error) {
+ return s.Cache.Count()
+}
diff --git a/storage/mem/mem.go b/storage/mem/mem.go
@@ -4,6 +4,7 @@ import (
"fmt"
"git.sr.ht/~jackmordaunt/kanban/storage"
+ "github.com/google/uuid"
"git.sr.ht/~jackmordaunt/kanban"
)
@@ -49,6 +50,19 @@ func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
return kanban.Project{}, false, nil
}
+func (s *Storer) Find(id uuid.UUID) (kanban.Project, bool, error) {
+ for _, p := range s.Data {
+ if p.ID == id {
+ return s.Data[p.Name], true, nil
+ }
+ }
+ return kanban.Project{}, false, nil
+}
+
+func (s *Storer) Count() (int, error) {
+ return len(s.Data), nil
+}
+
func (s *Storer) List() (list []kanban.Project, err error) {
for _, name := range s.Order {
if p, ok := s.Data[name]; ok {
diff --git a/storage/storage.go b/storage/storage.go
@@ -4,6 +4,7 @@ package storage
import (
"git.sr.ht/~jackmordaunt/kanban"
+ "github.com/google/uuid"
)
// Storer persists Project entities.
@@ -18,6 +19,10 @@ type Storer interface {
Load([]kanban.Project) error
// Lookup a Project by name.
Lookup(name string) (kanban.Project, bool, error)
+ // Find a Project by ID.
+ Find(id uuid.UUID) (kanban.Project, bool, error)
// List all existing Projects.
List() ([]kanban.Project, error)
+ // Count returns the number of projects that exist in the store.
+ Count() (int, error)
}
diff --git a/storage/storm/storm.go b/storage/storm/storm.go
@@ -1,98 +0,0 @@
-package storm
-
-import (
- "errors"
- "fmt"
- "log"
-
- "git.sr.ht/~jackmordaunt/kanban"
- "git.sr.ht/~jackmordaunt/kanban/storage"
-
- "github.com/asdine/storm/v3"
-)
-
-var _ storage.Storer = (*Storer)(nil)
-
-// Storer implements Project storage using storm db.
-type Storer struct {
- DB *storm.DB
-}
-
-// Schema is a database representation of a kanban.Project.
-type Schema struct {
- ID string `storm:"id"`
- Project kanban.Project
-}
-
-// Open a database handle using the file specified by path.
-func Open(path string) (*Storer, error) {
- db, err := storm.Open(path)
- if err != nil {
- return nil, fmt.Errorf("opening data file: %w", err)
- }
- if err := db.Init(&Schema{}); err != nil {
- return nil, fmt.Errorf("initialising schema: %v", err)
- }
- if err != nil {
- log.Fatalf("error: initializing data: %v", err)
- }
- return &Storer{DB: db}, nil
-
-}
-
-func (s *Storer) Create(p kanban.Project) error {
- return s.DB.Save(&Schema{ID: p.Name, Project: p})
-}
-
-func (s *Storer) Save(projects ...kanban.Project) error {
- for _, p := range projects {
- p := p
- if err := s.DB.Update(&Schema{ID: p.Name, Project: p}); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
- var schema Schema
- if err := s.DB.One("ID", name, &schema); err != nil {
- if errors.Is(err, storm.ErrNotFound) {
- return schema.Project, false, nil
- } else {
- return schema.Project, false, err
- }
- }
- return schema.Project, true, nil
-}
-
-func (s *Storer) List() (list []kanban.Project, err error) {
- var (
- projects []Schema
- )
- if err := s.DB.All(&projects); err != nil {
- return nil, fmt.Errorf("loading projects: %v", err)
- }
- for _, p := range projects {
- p := p
- list = append(list, p.Project)
- }
- return list, nil
-}
-
-func (s *Storer) Load(projects []kanban.Project) error {
- for ii := range projects {
- var (
- name = projects[ii].Name
- schema = Schema{
- ID: name,
- Project: projects[ii],
- }
- )
- if err := s.DB.One("ID", name, &schema); err != nil {
- return err
- }
- projects[ii] = schema.Project
- }
- return nil
-}