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  }