kanban

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

lazy.go (2299B)


      1 // Package lazy implements a lazy storage that only touches the
      2 // disk when necessary.
      3 package lazy
      4 
      5 import (
      6 	"fmt"
      7 
      8 	"git.sr.ht/~jackmordaunt/kanban"
      9 	"git.sr.ht/~jackmordaunt/kanban/storage"
     10 	"git.sr.ht/~jackmordaunt/kanban/storage/bolt"
     11 	"git.sr.ht/~jackmordaunt/kanban/storage/mem"
     12 	"github.com/google/uuid"
     13 )
     14 
     15 var _ storage.Storer = (*Storer)(nil)
     16 
     17 // Storer writes to disk when a change has been detected.
     18 //
     19 // Reads come directly from disk because it is memory mapped.
     20 // Cache simply holds old values so that we can detect for changes
     21 // to know when to write data.
     22 //
     23 // Writes get flushed to disk, hence that is the work being minimized.
     24 type Storer struct {
     25 	Cache *mem.Storer
     26 	*bolt.Storer
     27 }
     28 
     29 // Open a lazy storer, initializing the underlying database at the path
     30 // specified.
     31 func Open(path string) (*Storer, error) {
     32 	disk, err := bolt.Open(path)
     33 	if err != nil {
     34 		return nil, err
     35 	}
     36 	s := Storer{
     37 		Cache:  mem.New(),
     38 		Storer: disk,
     39 	}
     40 	if err := s.Populate(); err != nil {
     41 		return nil, err
     42 	}
     43 	return &s, nil
     44 }
     45 
     46 // Save a project. Only saves to disk if changed.
     47 func (s *Storer) Save(projects ...kanban.Project) error {
     48 	var save []kanban.Project
     49 	for _, p := range projects {
     50 		old, ok, err := s.Cache.Find(p.ID)
     51 		if err != nil {
     52 			return err
     53 		}
     54 		if !ok {
     55 			s.Cache.Active.Add(p)
     56 			continue
     57 		}
     58 		if !p.Eq(&old) {
     59 			save = append(save, p)
     60 		}
     61 	}
     62 	if len(save) > 0 {
     63 		if err := s.Storer.Save(save...); err != nil {
     64 			return fmt.Errorf("saving to disk: %w", err)
     65 		}
     66 		return s.Populate()
     67 	}
     68 	return nil
     69 }
     70 
     71 // Refresh a project entity by loading from disk.
     72 func (s *Storer) Refresh(id uuid.UUID) error {
     73 	p, ok, err := s.Storer.Find(id)
     74 	if err != nil {
     75 		return fmt.Errorf("loading from disk: %w", err)
     76 	}
     77 	if !ok {
     78 		return fmt.Errorf("project does not exist: %v", id)
     79 	}
     80 	s.Cache.Active.Add(p)
     81 	return nil
     82 }
     83 
     84 // Populate cache from disk.
     85 func (s *Storer) Populate() error {
     86 	s.Cache.Clear()
     87 	projects, err := s.Storer.List()
     88 	if err != nil {
     89 		return fmt.Errorf("loading projects from disk: %w", err)
     90 	}
     91 	for _, p := range projects {
     92 		s.Cache.Active.Add(p)
     93 	}
     94 	archived, err := s.Storer.ListArchived()
     95 	if err != nil {
     96 		return fmt.Errorf("loading archived projects from disk: %w", err)
     97 	}
     98 	for _, p := range archived {
     99 		s.Cache.Archived.Add(p)
    100 	}
    101 	return nil
    102 }