commit 350379f4e853f5df1c70430a45b685878d1f74b0
parent e2d3e44b4c3921543363e723a3fea156364af542
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Fri, 19 Feb 2021 07:18:38 +0800
feat: unify storage interface
Diffstat:
5 files changed, 91 insertions(+), 54 deletions(-)
diff --git a/cmd/kanban/ui.go b/cmd/kanban/ui.go
@@ -96,6 +96,11 @@ func (ui *UI) Loop() error {
ops op.Ops
events = ui.Window.Events()
)
+ projects, err := ui.Storage.List()
+ if err != nil {
+ return fmt.Errorf("loading projects: %v", err)
+ }
+ ui.Projects = projects
for event := range events {
switch event := (event).(type) {
case system.DestroyEvent:
@@ -155,8 +160,9 @@ func (ui *UI) Update(gtx C) {
}
}
if ui.ProjectForm.Submit.Clicked() {
+ name := ui.ProjectForm.Name.Text()
if err := ui.Storage.Create(kanban.Project{
- Name: ui.ProjectForm.Name.Text(),
+ Name: name,
Stages: []kanban.Stage{
{Name: "Todo"},
{Name: "In Progress"},
@@ -165,21 +171,17 @@ func (ui *UI) Update(gtx C) {
},
}); err != nil {
log.Printf("creating new project: %v", err)
+ } else {
+ // Note: the Storer interface only updates the projects
+ // in the slice given to it. Therefore we add the Project
+ // to the slice here. @cleanup
+ ui.Projects = append(ui.Projects, kanban.Project{Name: name})
}
ui.Clear()
}
if p, ok := ui.Rail.Selected(); ok {
- // if ui.Project != nil {
- // if err := ui.Storage.Save(ui.Project); err != nil {
- // log.Printf("saving project: %v", err)
- // }
- // }
if ui.Project == nil || ui.Project.Name != p {
project, ok := ui.Projects.Find(p)
- // project, ok, err := ui.Storage.Load(p)
- // if err != nil {
- // log.Printf("loading project %q: %v", p, err)
- // }
if ok {
ui.Clear()
ui.Project = project
@@ -520,18 +522,14 @@ func (plist Projects) Find(name string) (*kanban.Project, bool) {
// Load entities from storage.
func (ui *UI) Load() {
- projects, err := ui.Storage.List()
- if err != nil {
+ if err := ui.Storage.Load(ui.Projects); err != nil {
log.Printf("error: loading projects: %v", err)
}
- ui.Projects = projects
}
// Save entities to storage.
func (ui *UI) Save() {
- for _, p := range ui.Projects {
- if err := ui.Storage.Save(p); err != nil {
- log.Printf("error: saving project %q: %v", p.Name, err)
- }
+ if err := ui.Storage.Save(ui.Projects...); err != nil {
+ log.Printf("error: saving projects: %v", err)
}
}
diff --git a/storage/lazy/lazy.go b/storage/lazy/lazy.go
@@ -14,8 +14,7 @@ import (
var _ storage.Storer = (*Storer)(nil)
-// Load/Save to an in-memory cache.
-// Write through to disk when data has changed, and refresh the cache.
+// Storer writes to disk when a change has been detected.
type Storer struct {
Cache *mem.Storer
Disk *storm.Storer
@@ -47,43 +46,39 @@ func (s *Storer) Create(p kanban.Project) error {
}
// Save a project. Only saves to disk if changed.
-//
-// @bug the old data loaded from cache is always up to date with the new
-// data, why?
-// We need to have an old one and a new one in order to detect changes.
-func (s *Storer) Save(p kanban.Project) error {
- old, ok, err := s.Cache.Load(p.Name)
- if err != nil {
- return err
- }
- if !ok {
- return fmt.Errorf("project does not exist: %q", p.Name)
- }
- fmt.Printf("old: %v, p: %v\n", old, p)
- if !reflect.DeepEqual(p, old) {
- fmt.Printf("saving project to disk: %v\n", p)
- if err := s.Disk.Save(p); err != nil {
- return fmt.Errorf("saving to disk: %v", err)
+func (s *Storer) Save(projects ...kanban.Project) error {
+ for _, p := range projects {
+ old, ok, err := s.Cache.Lookup(p.Name)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ return fmt.Errorf("project does not exist: %q", p.Name)
+ }
+ if !reflect.DeepEqual(p, old) {
+ if err := s.Disk.Save(p); err != nil {
+ return fmt.Errorf("saving to disk: %v", err)
+ }
+ return s.Refresh(p.Name)
}
- return s.Refresh(p.Name)
}
return nil
}
// Load a project by name.
// Bool indicates whether a project exists for that name.
-func (s *Storer) Load(name string) (kanban.Project, bool, error) {
- return s.Cache.Load(name)
+func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
+ return s.Cache.Lookup(name)
}
// List projects.
func (s *Storer) List() ([]kanban.Project, error) {
- return s.Cache.List()
+ return s.Disk.List()
}
// Refresh a project entity by loading from disk.
func (s *Storer) Refresh(name string) error {
- p, ok, err := s.Disk.Load(name)
+ p, ok, err := s.Disk.Lookup(name)
if err != nil {
return fmt.Errorf("loading from disk: %v", err)
}
@@ -107,6 +102,10 @@ func (s *Storer) Populate() error {
return nil
}
+func (s *Storer) Load(projects []kanban.Project) error {
+ return s.Disk.Load(projects)
+}
+
func (s *Storer) Close() error {
return s.Disk.DB.Close()
}
diff --git a/storage/mem/mem.go b/storage/mem/mem.go
@@ -14,7 +14,6 @@ var _ storage.Storer = (*Storer)(nil)
type Storer struct {
Data map[string]kanban.Project
Order []string
- Err error
}
func New() *Storer {
@@ -32,16 +31,18 @@ func (s *Storer) Create(p kanban.Project) error {
return nil
}
-func (s *Storer) Save(p kanban.Project) error {
- if _, ok := s.Data[p.Name]; ok {
- s.Data[p.Name] = p
- } else {
- return fmt.Errorf("project %q does not exist", p.Name)
+func (s *Storer) Save(projects ...kanban.Project) error {
+ for _, p := range projects {
+ if _, ok := s.Data[p.Name]; ok {
+ s.Data[p.Name] = p
+ } else {
+ return fmt.Errorf("project %q does not exist", p.Name)
+ }
}
return nil
}
-func (s *Storer) Load(name string) (kanban.Project, bool, error) {
+func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
if p, ok := s.Data[name]; ok {
return p, ok, nil
}
@@ -56,3 +57,11 @@ func (s *Storer) List() (list []kanban.Project, err error) {
}
return list, nil
}
+
+func (s *Storer) Load(projects []kanban.Project) error {
+ for ii := range projects {
+ p := s.Data[projects[ii].Name]
+ projects[ii] = p
+ }
+ return nil
+}
diff --git a/storage/storage.go b/storage/storage.go
@@ -8,8 +8,16 @@ import (
// Storer persists Project entities.
type Storer interface {
- Create(p kanban.Project) error
- Save(p kanban.Project) error
- Load(name string) (kanban.Project, bool, error)
+ // Create a new Project.
+ Create(kanban.Project) error
+ // Save one or more existing Projects, updating the storage device.
+ Save(...kanban.Project) error
+ // Load updates the Projects using data from the storage device.
+ // Allows caller to allocate and control memory.
+ // Avoids copyig.
+ Load([]kanban.Project) error
+ // Lookup a Project by name.
+ Lookup(name string) (kanban.Project, bool, error)
+ // List all existing Projects.
List() ([]kanban.Project, error)
}
diff --git a/storage/storm/storm.go b/storage/storm/storm.go
@@ -44,11 +44,17 @@ func (s *Storer) Create(p kanban.Project) error {
return s.DB.Save(&Schema{ID: p.Name, Project: p})
}
-func (s *Storer) Save(p kanban.Project) error {
- return s.DB.Update(&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) Load(name string) (kanban.Project, bool, error) {
+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) {
@@ -73,3 +79,20 @@ func (s *Storer) List() (list []kanban.Project, err error) {
}
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
+}