github.com/safing/portbase@v0.19.5/database/controller.go (about)

     1  package database
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/safing/portbase/database/iterator"
    10  	"github.com/safing/portbase/database/query"
    11  	"github.com/safing/portbase/database/record"
    12  	"github.com/safing/portbase/database/storage"
    13  )
    14  
    15  // A Controller takes care of all the extra database logic.
    16  type Controller struct {
    17  	database     *Database
    18  	storage      storage.Interface
    19  	shadowDelete bool
    20  
    21  	hooksLock sync.RWMutex
    22  	hooks     []*RegisteredHook
    23  
    24  	subscriptionLock sync.RWMutex
    25  	subscriptions    []*Subscription
    26  }
    27  
    28  // newController creates a new controller for a storage.
    29  func newController(database *Database, storageInt storage.Interface, shadowDelete bool) *Controller {
    30  	return &Controller{
    31  		database:     database,
    32  		storage:      storageInt,
    33  		shadowDelete: shadowDelete,
    34  	}
    35  }
    36  
    37  // ReadOnly returns whether the storage is read only.
    38  func (c *Controller) ReadOnly() bool {
    39  	return c.storage.ReadOnly()
    40  }
    41  
    42  // Injected returns whether the storage is injected.
    43  func (c *Controller) Injected() bool {
    44  	return c.storage.Injected()
    45  }
    46  
    47  // Get returns the record with the given key.
    48  func (c *Controller) Get(key string) (record.Record, error) {
    49  	if shuttingDown.IsSet() {
    50  		return nil, ErrShuttingDown
    51  	}
    52  
    53  	if err := c.runPreGetHooks(key); err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	r, err := c.storage.Get(key)
    58  	if err != nil {
    59  		// replace not found error
    60  		if errors.Is(err, storage.ErrNotFound) {
    61  			return nil, ErrNotFound
    62  		}
    63  		return nil, err
    64  	}
    65  
    66  	r.Lock()
    67  	defer r.Unlock()
    68  
    69  	r, err = c.runPostGetHooks(r)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	if !r.Meta().CheckValidity() {
    75  		return nil, ErrNotFound
    76  	}
    77  
    78  	return r, nil
    79  }
    80  
    81  // GetMeta returns the metadata of the record with the given key.
    82  func (c *Controller) GetMeta(key string) (*record.Meta, error) {
    83  	if shuttingDown.IsSet() {
    84  		return nil, ErrShuttingDown
    85  	}
    86  
    87  	var m *record.Meta
    88  	var err error
    89  	if metaDB, ok := c.storage.(storage.MetaHandler); ok {
    90  		m, err = metaDB.GetMeta(key)
    91  		if err != nil {
    92  			// replace not found error
    93  			if errors.Is(err, storage.ErrNotFound) {
    94  				return nil, ErrNotFound
    95  			}
    96  			return nil, err
    97  		}
    98  	} else {
    99  		r, err := c.storage.Get(key)
   100  		if err != nil {
   101  			// replace not found error
   102  			if errors.Is(err, storage.ErrNotFound) {
   103  				return nil, ErrNotFound
   104  			}
   105  			return nil, err
   106  		}
   107  		m = r.Meta()
   108  	}
   109  
   110  	if !m.CheckValidity() {
   111  		return nil, ErrNotFound
   112  	}
   113  
   114  	return m, nil
   115  }
   116  
   117  // Put saves a record in the database, executes any registered
   118  // pre-put hooks and finally send an update to all subscribers.
   119  // The record must be locked and secured from concurrent access
   120  // when calling Put().
   121  func (c *Controller) Put(r record.Record) (err error) {
   122  	if shuttingDown.IsSet() {
   123  		return ErrShuttingDown
   124  	}
   125  
   126  	if c.ReadOnly() {
   127  		return ErrReadOnly
   128  	}
   129  
   130  	r, err = c.runPrePutHooks(r)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	if !c.shadowDelete && r.Meta().IsDeleted() {
   136  		// Immediate delete.
   137  		err = c.storage.Delete(r.DatabaseKey())
   138  	} else {
   139  		// Put or shadow delete.
   140  		r, err = c.storage.Put(r)
   141  	}
   142  
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	if r == nil {
   148  		return errors.New("storage returned nil record after successful put operation")
   149  	}
   150  
   151  	c.notifySubscribers(r)
   152  
   153  	return nil
   154  }
   155  
   156  // PutMany stores many records in the database. It does not
   157  // process any hooks or update subscriptions. Use with care!
   158  func (c *Controller) PutMany() (chan<- record.Record, <-chan error) {
   159  	if shuttingDown.IsSet() {
   160  		errs := make(chan error, 1)
   161  		errs <- ErrShuttingDown
   162  		return make(chan record.Record), errs
   163  	}
   164  
   165  	if c.ReadOnly() {
   166  		errs := make(chan error, 1)
   167  		errs <- ErrReadOnly
   168  		return make(chan record.Record), errs
   169  	}
   170  
   171  	if batcher, ok := c.storage.(storage.Batcher); ok {
   172  		return batcher.PutMany(c.shadowDelete)
   173  	}
   174  
   175  	errs := make(chan error, 1)
   176  	errs <- ErrNotImplemented
   177  	return make(chan record.Record), errs
   178  }
   179  
   180  // Query executes the given query on the database.
   181  func (c *Controller) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
   182  	if shuttingDown.IsSet() {
   183  		return nil, ErrShuttingDown
   184  	}
   185  
   186  	it, err := c.storage.Query(q, local, internal)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	return it, nil
   192  }
   193  
   194  // PushUpdate pushes a record update to subscribers.
   195  // The caller must hold the record's lock when calling
   196  // PushUpdate.
   197  func (c *Controller) PushUpdate(r record.Record) {
   198  	if c != nil {
   199  		if shuttingDown.IsSet() {
   200  			return
   201  		}
   202  
   203  		c.notifySubscribers(r)
   204  	}
   205  }
   206  
   207  func (c *Controller) addSubscription(sub *Subscription) {
   208  	if shuttingDown.IsSet() {
   209  		return
   210  	}
   211  
   212  	c.subscriptionLock.Lock()
   213  	defer c.subscriptionLock.Unlock()
   214  
   215  	c.subscriptions = append(c.subscriptions, sub)
   216  }
   217  
   218  // Maintain runs the Maintain method on the storage.
   219  func (c *Controller) Maintain(ctx context.Context) error {
   220  	if shuttingDown.IsSet() {
   221  		return ErrShuttingDown
   222  	}
   223  
   224  	if maintainer, ok := c.storage.(storage.Maintainer); ok {
   225  		return maintainer.Maintain(ctx)
   226  	}
   227  	return nil
   228  }
   229  
   230  // MaintainThorough runs the MaintainThorough method on the
   231  // storage.
   232  func (c *Controller) MaintainThorough(ctx context.Context) error {
   233  	if shuttingDown.IsSet() {
   234  		return ErrShuttingDown
   235  	}
   236  
   237  	if maintainer, ok := c.storage.(storage.Maintainer); ok {
   238  		return maintainer.MaintainThorough(ctx)
   239  	}
   240  	return nil
   241  }
   242  
   243  // MaintainRecordStates runs the record state lifecycle
   244  // maintenance on the storage.
   245  func (c *Controller) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time) error {
   246  	if shuttingDown.IsSet() {
   247  		return ErrShuttingDown
   248  	}
   249  
   250  	return c.storage.MaintainRecordStates(ctx, purgeDeletedBefore, c.shadowDelete)
   251  }
   252  
   253  // Purge deletes all records that match the given query.
   254  // It returns the number of successful deletes and an error.
   255  func (c *Controller) Purge(ctx context.Context, q *query.Query, local, internal bool) (int, error) {
   256  	if shuttingDown.IsSet() {
   257  		return 0, ErrShuttingDown
   258  	}
   259  
   260  	if purger, ok := c.storage.(storage.Purger); ok {
   261  		return purger.Purge(ctx, q, local, internal, c.shadowDelete)
   262  	}
   263  
   264  	return 0, ErrNotImplemented
   265  }
   266  
   267  // Shutdown shuts down the storage.
   268  func (c *Controller) Shutdown() error {
   269  	return c.storage.Shutdown()
   270  }
   271  
   272  // notifySubscribers notifies all subscribers that are interested
   273  // in r. r must be locked when calling notifySubscribers.
   274  // Any subscriber that is not blocking on it's feed channel will
   275  // be skipped.
   276  func (c *Controller) notifySubscribers(r record.Record) {
   277  	c.subscriptionLock.RLock()
   278  	defer c.subscriptionLock.RUnlock()
   279  
   280  	for _, sub := range c.subscriptions {
   281  		if r.Meta().CheckPermission(sub.local, sub.internal) && sub.q.Matches(r) {
   282  			select {
   283  			case sub.Feed <- r:
   284  			default:
   285  			}
   286  		}
   287  	}
   288  }
   289  
   290  func (c *Controller) runPreGetHooks(key string) error {
   291  	c.hooksLock.RLock()
   292  	defer c.hooksLock.RUnlock()
   293  
   294  	for _, hook := range c.hooks {
   295  		if !hook.h.UsesPreGet() {
   296  			continue
   297  		}
   298  
   299  		if !hook.q.MatchesKey(key) {
   300  			continue
   301  		}
   302  
   303  		if err := hook.h.PreGet(key); err != nil {
   304  			return err
   305  		}
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  func (c *Controller) runPostGetHooks(r record.Record) (record.Record, error) {
   312  	c.hooksLock.RLock()
   313  	defer c.hooksLock.RUnlock()
   314  
   315  	var err error
   316  	for _, hook := range c.hooks {
   317  		if !hook.h.UsesPostGet() {
   318  			continue
   319  		}
   320  
   321  		if !hook.q.Matches(r) {
   322  			continue
   323  		}
   324  
   325  		r, err = hook.h.PostGet(r)
   326  		if err != nil {
   327  			return nil, err
   328  		}
   329  	}
   330  
   331  	return r, nil
   332  }
   333  
   334  func (c *Controller) runPrePutHooks(r record.Record) (record.Record, error) {
   335  	c.hooksLock.RLock()
   336  	defer c.hooksLock.RUnlock()
   337  
   338  	var err error
   339  	for _, hook := range c.hooks {
   340  		if !hook.h.UsesPrePut() {
   341  			continue
   342  		}
   343  
   344  		if !hook.q.Matches(r) {
   345  			continue
   346  		}
   347  
   348  		r, err = hook.h.PrePut(r)
   349  		if err != nil {
   350  			return nil, err
   351  		}
   352  	}
   353  
   354  	return r, nil
   355  }