1 /**
2  * @file
3  * Notification API
4  *
5  * @authors
6  * Copyright (C) 2019 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page mutt_notify Notification API
25  *
26  * Notification API
27  */
28 
29 #include "config.h"
30 #include <stddef.h>
31 #include <stdbool.h>
32 #include "notify.h"
33 #include "logging.h"
34 #include "memory.h"
35 #include "queue.h"
36 
37 /// Lookup table for NotifyType
38 /// Must be the same size and order as #NotifyType
39 static char *NotifyTypeNames[] = {
40   "NT_ALL",     "NT_ACCOUNT", "NT_ALIAS",   "NT_ALTERN",  "NT_ATTACH",
41   "NT_BINDING", "NT_COLOR",   "NT_COMMAND", "NT_COMPOSE", "NT_CONFIG",
42   "NT_CONTEXT", "NT_EMAIL",   "NT_GLOBAL",  "NT_HEADER",  "NT_INDEX",
43   "NT_MAILBOX", "NT_MENU",    "NT_PAGER",   "NT_SCORE",   "NT_SUBJRX",
44   "NT_WINDOW",
45 };
46 
47 /**
48  * struct Notify - Notification API
49  */
50 struct Notify
51 {
52   struct Notify *parent;         ///< Parent of the notification object
53   struct ObserverList observers; ///< List of observers of this object
54 };
55 
56 /**
57  * notify_new - Create a new notifications handler
58  * @retval ptr New notification handler
59  */
notify_new(void)60 struct Notify *notify_new(void)
61 {
62   struct Notify *notify = mutt_mem_calloc(1, sizeof(*notify));
63 
64   STAILQ_INIT(&notify->observers);
65 
66   return notify;
67 }
68 
69 /**
70  * notify_free - Free a notification handler
71  * @param ptr Notification handler to free
72  */
notify_free(struct Notify ** ptr)73 void notify_free(struct Notify **ptr)
74 {
75   if (!ptr || !*ptr)
76     return;
77 
78   struct Notify *notify = *ptr;
79   // NOTIFY observers
80 
81   notify_observer_remove_all(notify);
82 
83   FREE(ptr);
84 }
85 
86 /**
87  * notify_set_parent - Set the parent notification handler
88  * @param notify Notification handler to alter
89  * @param parent Parent notification handler
90  *
91  * Notifications are passed up the tree of handlers.
92  */
notify_set_parent(struct Notify * notify,struct Notify * parent)93 void notify_set_parent(struct Notify *notify, struct Notify *parent)
94 {
95   if (!notify)
96     return;
97 
98   notify->parent = parent;
99 }
100 
101 /**
102  * send - Send out a notification message
103  * @param source        Source of the event, e.g. #Account
104  * @param current       Current handler, e.g. #NeoMutt
105  * @param event_type    Type of event, e.g. #NT_ACCOUNT
106  * @param event_subtype Subtype, e.g. #NT_ACCOUNT_ADD
107  * @param event_data    Private data associated with the event type
108  * @retval true Successfully sent
109  *
110  * Notifications are sent to all observers of the object, then propagated up
111  * the handler tree.  For example a "new email" notification would be sent to
112  * the Mailbox that owns it, the Account (owning the Mailbox) and finally the
113  * NeoMutt object.
114  *
115  * @note If Observers call `notify_observer_remove()`, then we garbage-collect
116  *       any dead list entries after we've finished.
117  */
send(struct Notify * source,struct Notify * current,enum NotifyType event_type,int event_subtype,void * event_data)118 static bool send(struct Notify *source, struct Notify *current,
119                  enum NotifyType event_type, int event_subtype, void *event_data)
120 {
121   if (!source || !current)
122     return false;
123 
124   mutt_debug(LL_NOTIFY, "send: %d, %ld\n", event_type, event_data);
125   struct ObserverNode *np = NULL;
126   STAILQ_FOREACH(np, &current->observers, entries)
127   {
128     struct Observer *o = np->observer;
129     if (!o)
130       continue;
131 
132     if ((o->type == NT_ALL) || (event_type == o->type))
133     {
134       struct NotifyCallback nc = { current, event_type, event_subtype,
135                                    event_data, o->global_data };
136       if (o->callback(&nc) < 0)
137       {
138         mutt_debug(LL_DEBUG1, "failed to send notification: %s/%d, global %p, event %p\n",
139                    NotifyTypeNames[event_type], event_subtype, o->global_data, event_data);
140       }
141     }
142   }
143 
144   if (current->parent)
145     return send(source, current->parent, event_type, event_subtype, event_data);
146 
147   // Garbage collection time
148   struct ObserverNode *tmp = NULL;
149   STAILQ_FOREACH_SAFE(np, &current->observers, entries, tmp)
150   {
151     if (np->observer)
152       continue;
153 
154     STAILQ_REMOVE(&current->observers, np, ObserverNode, entries);
155     FREE(&np);
156   }
157 
158   return true;
159 }
160 
161 /**
162  * notify_send - Send out a notification message
163  * @param notify        Notification handler
164  * @param event_type    Type of event, e.g. #NT_ACCOUNT
165  * @param event_subtype Subtype, e.g. #NT_ACCOUNT_ADD
166  * @param event_data    Private data associated with the event
167  * @retval true Successfully sent
168  *
169  * See send() for more details.
170  */
notify_send(struct Notify * notify,enum NotifyType event_type,int event_subtype,void * event_data)171 bool notify_send(struct Notify *notify, enum NotifyType event_type,
172                  int event_subtype, void *event_data)
173 {
174   mutt_debug(LL_NOTIFY, "sending: %s/%d\n", NotifyTypeNames[event_type], event_subtype);
175   return send(notify, notify, event_type, event_subtype, event_data);
176 }
177 
178 /**
179  * notify_observer_add - Add an observer to an object
180  * @param notify      Notification handler
181  * @param type        Notification type to observe, e.g. #NT_WINDOW
182  * @param callback    Function to call on a matching event, see ::observer_t
183  * @param global_data Private data associated with the observer
184  * @retval true Successful
185  *
186  * New observers are added to the front of the list, giving them higher
187  * priority than existing observers.
188  */
notify_observer_add(struct Notify * notify,enum NotifyType type,observer_t callback,void * global_data)189 bool notify_observer_add(struct Notify *notify, enum NotifyType type,
190                          observer_t callback, void *global_data)
191 {
192   if (!notify || !callback)
193     return false;
194 
195   struct ObserverNode *np = NULL;
196   STAILQ_FOREACH(np, &notify->observers, entries)
197   {
198     if (!np->observer)
199       continue;
200 
201     if ((np->observer->callback == callback) && (np->observer->global_data == global_data))
202       return true;
203   }
204 
205   struct Observer *o = mutt_mem_calloc(1, sizeof(*o));
206   o->type = type;
207   o->callback = callback;
208   o->global_data = global_data;
209 
210   np = mutt_mem_calloc(1, sizeof(*np));
211   np->observer = o;
212   STAILQ_INSERT_HEAD(&notify->observers, np, entries);
213 
214   return true;
215 }
216 
217 /**
218  * notify_observer_remove - Remove an observer from an object
219  * @param notify      Notification handler
220  * @param callback    Function to call on a matching event, see ::observer_t
221  * @param global_data Private data to match specific callback
222  * @retval true Successful
223  *
224  * @note This function frees the Observer, but doesn't free the ObserverNode.
225  *       If `send()` is present higher up the call stack,
226  *       removing multiple entries from the list will cause it to crash.
227  */
notify_observer_remove(struct Notify * notify,observer_t callback,void * global_data)228 bool notify_observer_remove(struct Notify *notify, observer_t callback, void *global_data)
229 {
230   if (!notify || !callback)
231     return false;
232 
233   struct ObserverNode *np = NULL;
234   STAILQ_FOREACH(np, &notify->observers, entries)
235   {
236     if (!np->observer)
237       continue;
238 
239     if ((np->observer->callback == callback) && (np->observer->global_data == global_data))
240     {
241       FREE(&np->observer);
242       return true;
243     }
244   }
245 
246   return false;
247 }
248 
249 /**
250  * notify_observer_remove_all - Remove all the observers from an object
251  * @param notify Notification handler
252  */
notify_observer_remove_all(struct Notify * notify)253 void notify_observer_remove_all(struct Notify *notify)
254 {
255   if (!notify)
256     return;
257 
258   struct ObserverNode *np = NULL;
259   struct ObserverNode *tmp = NULL;
260   STAILQ_FOREACH_SAFE(np, &notify->observers, entries, tmp)
261   {
262     STAILQ_REMOVE(&notify->observers, np, ObserverNode, entries);
263     FREE(&np->observer);
264     FREE(&np);
265   }
266 }
267