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 }