mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-15 01:55:35 +00:00
199 lines
4.7 KiB
Go
199 lines
4.7 KiB
Go
package oplog
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"sync"
|
|
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
)
|
|
|
|
type OperationEvent int
|
|
|
|
const (
|
|
OPERATION_ADDED OperationEvent = iota
|
|
OPERATION_UPDATED
|
|
OPERATION_DELETED
|
|
)
|
|
|
|
var (
|
|
ErrStopIteration = errors.New("stop iteration")
|
|
ErrNotExist = errors.New("operation does not exist")
|
|
ErrExist = errors.New("operation already exists")
|
|
|
|
NullOPID = int64(0)
|
|
)
|
|
|
|
type Subscription = func(ops []*v1.Operation, event OperationEvent)
|
|
|
|
type subAndQuery struct {
|
|
f *Subscription
|
|
q Query
|
|
}
|
|
|
|
type OpLog struct {
|
|
store OpStore
|
|
|
|
subscribersMu sync.Mutex
|
|
subscribers []subAndQuery
|
|
}
|
|
|
|
func NewOpLog(store OpStore) (*OpLog, error) {
|
|
o := &OpLog{
|
|
store: store,
|
|
}
|
|
|
|
if err := ApplyMigrations(o); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return o, nil
|
|
}
|
|
|
|
func (o *OpLog) curSubscribers() []subAndQuery {
|
|
o.subscribersMu.Lock()
|
|
defer o.subscribersMu.Unlock()
|
|
return slices.Clone(o.subscribers)
|
|
}
|
|
|
|
func (o *OpLog) notify(ops []*v1.Operation, event OperationEvent) {
|
|
for _, sub := range o.curSubscribers() {
|
|
notifyOps := make([]*v1.Operation, 0, len(ops))
|
|
for _, op := range ops {
|
|
if sub.q.Match(op) {
|
|
notifyOps = append(notifyOps, op)
|
|
}
|
|
}
|
|
if len(notifyOps) > 0 {
|
|
(*sub.f)(notifyOps, event)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *OpLog) Query(q Query, f func(*v1.Operation) error) error {
|
|
return o.store.Query(q, f)
|
|
}
|
|
|
|
func (o *OpLog) QueryMetadata(q Query, f func(OpMetadata) error) error {
|
|
return o.store.QueryMetadata(q, f)
|
|
}
|
|
|
|
func (o *OpLog) Subscribe(q Query, f *Subscription) {
|
|
o.subscribersMu.Lock()
|
|
defer o.subscribersMu.Unlock()
|
|
o.subscribers = append(o.subscribers, subAndQuery{f: f, q: q})
|
|
}
|
|
|
|
func (o *OpLog) Unsubscribe(f *Subscription) error {
|
|
o.subscribersMu.Lock()
|
|
defer o.subscribersMu.Unlock()
|
|
for i, sub := range o.subscribers {
|
|
if sub.f == f {
|
|
o.subscribers = append(o.subscribers[:i], o.subscribers[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return errors.New("subscription not found")
|
|
}
|
|
|
|
func (o *OpLog) Get(opID int64) (*v1.Operation, error) {
|
|
return o.store.Get(opID)
|
|
}
|
|
|
|
func (o *OpLog) Add(ops ...*v1.Operation) error {
|
|
for _, o := range ops {
|
|
if o.Id != 0 {
|
|
return errors.New("operation already has an ID, OpLog.Add is expected to set the ID")
|
|
}
|
|
if o.Modno == 0 {
|
|
o.Modno = NewRandomModno(0)
|
|
}
|
|
}
|
|
|
|
if err := o.store.Add(ops...); err != nil {
|
|
return err
|
|
}
|
|
|
|
o.notify(ops, OPERATION_ADDED)
|
|
return nil
|
|
}
|
|
|
|
func (o *OpLog) Update(ops ...*v1.Operation) error {
|
|
for _, o := range ops {
|
|
if o.Id == 0 {
|
|
return errors.New("operation does not have an ID, OpLog.Update is expected to have an ID")
|
|
}
|
|
o.Modno = NewRandomModno(o.Modno)
|
|
}
|
|
|
|
if err := o.store.Update(ops...); err != nil {
|
|
return err
|
|
}
|
|
|
|
o.notify(ops, OPERATION_UPDATED)
|
|
return nil
|
|
}
|
|
|
|
// Set is an alias for Update that does not increment the modno, provided for use by the syncapi.
|
|
func (o *OpLog) Set(op *v1.Operation) error {
|
|
var err error
|
|
if op.Id == 0 {
|
|
err = o.store.Add(op)
|
|
} else {
|
|
err = o.store.Update(op)
|
|
if errors.Is(err, ErrNotExist) {
|
|
err = o.store.Add(op)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.notify([]*v1.Operation{op}, OPERATION_UPDATED)
|
|
return nil
|
|
}
|
|
|
|
func (o *OpLog) Delete(opID ...int64) error {
|
|
removedOps, err := o.store.Delete(opID...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
o.notify(removedOps, OPERATION_DELETED)
|
|
return nil
|
|
}
|
|
|
|
func (o *OpLog) Transform(q Query, f func(*v1.Operation) (*v1.Operation, error)) error {
|
|
return o.store.Transform(q, f)
|
|
}
|
|
|
|
type OpStore interface {
|
|
// Query returns all operations that match the query.
|
|
Query(q Query, f func(*v1.Operation) error) error
|
|
// QueryMetadata is like Query, but only returns metadata about the operations.
|
|
// this is useful for very high performance scans that don't deserialize the operation itself.
|
|
QueryMetadata(q Query, f func(OpMetadata) error) error
|
|
// Get returns the operation with the given ID.
|
|
Get(opID int64) (*v1.Operation, error)
|
|
// Add adds the given operations to the store.
|
|
Add(op ...*v1.Operation) error
|
|
// Update updates the given operations in the store.
|
|
Update(op ...*v1.Operation) error
|
|
// Delete removes the operations with the given IDs from the store, and returns the removed operations.
|
|
Delete(opID ...int64) ([]*v1.Operation, error)
|
|
// Transform applies the given function to each operation that matches the query.
|
|
Transform(q Query, f func(*v1.Operation) (*v1.Operation, error)) error
|
|
// Version returns the current data version
|
|
Version() (int64, error)
|
|
// SetVersion sets the data version
|
|
SetVersion(version int64) error
|
|
}
|
|
|
|
// OpMetadata is a struct that contains metadata about an operation without fetching the operation itself.
|
|
type OpMetadata struct {
|
|
ID int64
|
|
FlowID int64
|
|
Modno int64
|
|
OriginalID int64
|
|
OriginalFlowID int64
|
|
}
|