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

     1  package notifications
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/safing/portbase/database"
    10  	"github.com/safing/portbase/database/iterator"
    11  	"github.com/safing/portbase/database/query"
    12  	"github.com/safing/portbase/database/record"
    13  	"github.com/safing/portbase/database/storage"
    14  	"github.com/safing/portbase/log"
    15  )
    16  
    17  var (
    18  	nots     = make(map[string]*Notification)
    19  	notsLock sync.RWMutex
    20  
    21  	dbController *database.Controller
    22  )
    23  
    24  // Storage interface errors.
    25  var (
    26  	ErrInvalidData = errors.New("invalid data, must be a notification object")
    27  	ErrInvalidPath = errors.New("invalid path")
    28  	ErrNoDelete    = errors.New("notifications may not be deleted, they must be handled")
    29  )
    30  
    31  // StorageInterface provices a storage.Interface to the configuration manager.
    32  type StorageInterface struct {
    33  	storage.InjectBase
    34  }
    35  
    36  func registerAsDatabase() error {
    37  	_, err := database.Register(&database.Database{
    38  		Name:        "notifications",
    39  		Description: "Notifications",
    40  		StorageType: "injected",
    41  	})
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	controller, err := database.InjectDatabase("notifications", &StorageInterface{})
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	dbController = controller
    52  	return nil
    53  }
    54  
    55  // Get returns a database record.
    56  func (s *StorageInterface) Get(key string) (record.Record, error) {
    57  	// Get EventID from key.
    58  	if !strings.HasPrefix(key, "all/") {
    59  		return nil, storage.ErrNotFound
    60  	}
    61  	key = strings.TrimPrefix(key, "all/")
    62  
    63  	// Get notification from storage.
    64  	n, ok := getNotification(key)
    65  	if !ok {
    66  		return nil, storage.ErrNotFound
    67  	}
    68  
    69  	return n, nil
    70  }
    71  
    72  func getNotification(eventID string) (n *Notification, ok bool) {
    73  	notsLock.RLock()
    74  	defer notsLock.RUnlock()
    75  
    76  	n, ok = nots[eventID]
    77  	return
    78  }
    79  
    80  // Query returns a an iterator for the supplied query.
    81  func (s *StorageInterface) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
    82  	it := iterator.New()
    83  	go s.processQuery(q, it)
    84  	// TODO: check local and internal
    85  
    86  	return it, nil
    87  }
    88  
    89  func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
    90  	// Get a copy of the notification map.
    91  	notsCopy := getNotsCopy()
    92  
    93  	// send all notifications
    94  	for _, n := range notsCopy {
    95  		if inQuery(n, q) {
    96  			select {
    97  			case it.Next <- n:
    98  			case <-it.Done:
    99  				// make sure we don't leak this goroutine if the iterator get's cancelled
   100  				return
   101  			}
   102  		}
   103  	}
   104  
   105  	it.Finish(nil)
   106  }
   107  
   108  func inQuery(n *Notification, q *query.Query) bool {
   109  	n.lock.Lock()
   110  	defer n.lock.Unlock()
   111  
   112  	switch {
   113  	case n.Meta().IsDeleted():
   114  		return false
   115  	case !q.MatchesKey(n.DatabaseKey()):
   116  		return false
   117  	case !q.MatchesRecord(n):
   118  		return false
   119  	}
   120  
   121  	return true
   122  }
   123  
   124  // Put stores a record in the database.
   125  func (s *StorageInterface) Put(r record.Record) (record.Record, error) {
   126  	// record is already locked!
   127  	key := r.DatabaseKey()
   128  	n, err := EnsureNotification(r)
   129  	if err != nil {
   130  		return nil, ErrInvalidData
   131  	}
   132  
   133  	// transform key
   134  	if strings.HasPrefix(key, "all/") {
   135  		key = strings.TrimPrefix(key, "all/")
   136  	} else {
   137  		return nil, ErrInvalidPath
   138  	}
   139  
   140  	return applyUpdate(n, key)
   141  }
   142  
   143  func applyUpdate(n *Notification, key string) (*Notification, error) {
   144  	// separate goroutine in order to correctly lock notsLock
   145  	existing, ok := getNotification(key)
   146  
   147  	// ignore if already deleted
   148  	if !ok || existing.Meta().IsDeleted() {
   149  		// this is a completely new notification
   150  		// we pass pushUpdate==false because the storage
   151  		// controller will push an update on put anyway.
   152  		n.save(false)
   153  		return n, nil
   154  	}
   155  
   156  	// Save when we're finished, if needed.
   157  	save := false
   158  	defer func() {
   159  		if save {
   160  			existing.save(false)
   161  		}
   162  	}()
   163  
   164  	existing.Lock()
   165  	defer existing.Unlock()
   166  
   167  	if existing.State == Executed {
   168  		return existing, fmt.Errorf("action already executed")
   169  	}
   170  
   171  	// check if the notification has been marked as
   172  	// "executed externally".
   173  	if n.State == Executed {
   174  		log.Tracef("notifications: action for %s executed externally", n.EventID)
   175  		existing.State = Executed
   176  		save = true
   177  
   178  		// in case the action has been executed immediately by the
   179  		// sender we may need to update the SelectedActionID.
   180  		// Though, we guard the assignments with value check
   181  		// so partial updates that only change the
   182  		// State property do not overwrite existing values.
   183  		if n.SelectedActionID != "" {
   184  			existing.SelectedActionID = n.SelectedActionID
   185  		}
   186  	}
   187  
   188  	if n.SelectedActionID != "" && existing.State == Active {
   189  		log.Tracef("notifications: selected action for %s: %s", n.EventID, n.SelectedActionID)
   190  		existing.selectAndExecuteAction(n.SelectedActionID)
   191  		save = true
   192  	}
   193  
   194  	return existing, nil
   195  }
   196  
   197  // Delete deletes a record from the database.
   198  func (s *StorageInterface) Delete(key string) error {
   199  	// Get EventID from key.
   200  	if !strings.HasPrefix(key, "all/") {
   201  		return storage.ErrNotFound
   202  	}
   203  	key = strings.TrimPrefix(key, "all/")
   204  
   205  	// Get notification from storage.
   206  	n, ok := getNotification(key)
   207  	if !ok {
   208  		return storage.ErrNotFound
   209  	}
   210  
   211  	n.delete(true)
   212  	return nil
   213  }
   214  
   215  // ReadOnly returns whether the database is read only.
   216  func (s *StorageInterface) ReadOnly() bool {
   217  	return false
   218  }
   219  
   220  // EnsureNotification ensures that the given record is a Notification and returns it.
   221  func EnsureNotification(r record.Record) (*Notification, error) {
   222  	// unwrap
   223  	if r.IsWrapped() {
   224  		// only allocate a new struct, if we need it
   225  		n := &Notification{}
   226  		err := record.Unwrap(r, n)
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		return n, nil
   231  	}
   232  
   233  	// or adjust type
   234  	n, ok := r.(*Notification)
   235  	if !ok {
   236  		return nil, fmt.Errorf("record not of type *Notification, but %T", r)
   237  	}
   238  	return n, nil
   239  }