kanban

Kanban client in Gio
Log | Files | Refs | README | LICENSE

commit 350379f4e853f5df1c70430a45b685878d1f74b0
parent e2d3e44b4c3921543363e723a3fea156364af542
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Fri, 19 Feb 2021 07:18:38 +0800

feat: unify storage interface

Diffstat:
Mcmd/kanban/ui.go | 32+++++++++++++++-----------------
Mstorage/lazy/lazy.go | 47+++++++++++++++++++++++------------------------
Mstorage/mem/mem.go | 23++++++++++++++++-------
Mstorage/storage.go | 14+++++++++++---
Mstorage/storm/storm.go | 29++++++++++++++++++++++++++---
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 +}