github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/cli/store/snapshot/snapshot.go (about) 1 package snapshot 2 3 import ( 4 "encoding/gob" 5 "net/url" 6 "os" 7 "sync" 8 "time" 9 10 "github.com/tickoalcantara12/micro/v3/service/store" 11 "github.com/pkg/errors" 12 ) 13 14 // Snapshot creates snapshots of a go-micro store 15 type Snapshot interface { 16 // Init validates the Snapshot options and returns an error if they are invalid. 17 // Init must be called before the Snapshot is used 18 Init(opts ...SnapshotOption) error 19 // Start opens a channel that receives *store.Record, adding any incoming records to a backup 20 // close() the channel to commit the results. 21 Start() (chan<- *store.Record, error) 22 // Wait waits for any operations to be committed to underlying storage 23 Wait() 24 } 25 26 // SnapshotOptions configure a snapshotter 27 type SnapshotOptions struct { 28 Destination string 29 } 30 31 // SnapshotOption is an individual option 32 type SnapshotOption func(s *SnapshotOptions) 33 34 // Destination is the URL to snapshot to, e.g. file:///path/to/file 35 func Destination(dest string) SnapshotOption { 36 return func(s *SnapshotOptions) { 37 s.Destination = dest 38 } 39 } 40 41 // FileSnapshot backs up incoming records to a File 42 type FileSnapshot struct { 43 Options SnapshotOptions 44 45 records chan *store.Record 46 path string 47 encoder *gob.Encoder 48 file *os.File 49 wg *sync.WaitGroup 50 } 51 52 // NewFileSnapshot returns a FileSnapshot 53 func NewFileSnapshot(opts ...SnapshotOption) Snapshot { 54 f := &FileSnapshot{wg: &sync.WaitGroup{}} 55 for _, o := range opts { 56 o(&f.Options) 57 } 58 return f 59 } 60 61 // Init validates the options 62 func (f *FileSnapshot) Init(opts ...SnapshotOption) error { 63 for _, o := range opts { 64 o(&f.Options) 65 } 66 u, err := url.Parse(f.Options.Destination) 67 if err != nil { 68 return errors.Wrap(err, "destination is invalid") 69 } 70 if u.Scheme != "file" { 71 return errors.Errorf("unsupported scheme %s (wanted file)", u.Scheme) 72 } 73 if f.wg == nil { 74 f.wg = &sync.WaitGroup{} 75 } 76 f.path = u.Path 77 return nil 78 } 79 80 // Start opens a channel which receives *store.Record and writes them to storage 81 func (f *FileSnapshot) Start() (chan<- *store.Record, error) { 82 if f.records != nil || f.encoder != nil || f.file != nil { 83 return nil, errors.New("Snapshot is already in use") 84 } 85 fi, err := os.OpenFile(f.path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o600) 86 if err != nil { 87 return nil, errors.Wrapf(err, "couldn't open file %s", f.path) 88 } 89 f.encoder = gob.NewEncoder(fi) 90 f.file = fi 91 f.records = make(chan *store.Record) 92 go f.receiveRecords(f.records) 93 return f.records, nil 94 } 95 96 // Wait waits for the snapshotter to commit the backups to persistent storage 97 func (f *FileSnapshot) Wait() { 98 f.wg.Wait() 99 } 100 101 func (f *FileSnapshot) receiveRecords(rec <-chan *store.Record) { 102 f.wg.Add(1) 103 for { 104 r, more := <-rec 105 if !more { 106 println("Stopping FileSnapshot") 107 f.file.Close() 108 f.encoder = nil 109 f.file = nil 110 f.records = nil 111 break 112 } 113 ir := record{ 114 Key: r.Key, 115 } 116 if r.Expiry != 0 { 117 ir.ExpiresAt = time.Now().Add(r.Expiry) 118 } 119 ir.Value = make([]byte, len(r.Value)) 120 copy(ir.Value, r.Value) 121 if err := f.encoder.Encode(ir); err != nil { 122 // only thing to do here is panic 123 panic(errors.Wrap(err, "couldn't write to file")) 124 } 125 println("encoded", ir.Key) 126 } 127 f.wg.Done() 128 } 129 130 // record is a store.Record when serialised to persistent storage. 131 type record struct { 132 Key string 133 Value []byte 134 ExpiresAt time.Time 135 }