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(¬ify->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, ¤t->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, ¤t->observers, entries, tmp)
150 {
151 if (np->observer)
152 continue;
153
154 STAILQ_REMOVE(¤t->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, ¬ify->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(¬ify->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, ¬ify->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, ¬ify->observers, entries, tmp)
261 {
262 STAILQ_REMOVE(¬ify->observers, np, ObserverNode, entries);
263 FREE(&np->observer);
264 FREE(&np);
265 }
266 }
267