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 }