Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/mutt/notify.c
Line
Count
Source (jump to first uncovered line)
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 "logging2.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 const char *NotifyTypeNames[] = {
40
  "NT_ALL",     "NT_ACCOUNT",  "NT_ALIAS",   "NT_ALTERN", "NT_ATTACH",
41
  "NT_BINDING", "NT_COLOR",    "NT_COMMAND", "NT_CONFIG", "NT_CONTEXT",
42
  "NT_EMAIL",   "NT_ENVELOPE", "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
 */
60
struct Notify *notify_new(void)
61
133k
{
62
133k
  struct Notify *notify = mutt_mem_calloc(1, sizeof(*notify));
63
64
133k
  STAILQ_INIT(&notify->observers);
65
66
133k
  return notify;
67
133k
}
68
69
/**
70
 * notify_free - Free a notification handler
71
 * @param ptr Notification handler to free
72
 */
73
void notify_free(struct Notify **ptr)
74
133k
{
75
133k
  if (!ptr || !*ptr)
76
0
    return;
77
78
133k
  struct Notify *notify = *ptr;
79
  // NOTIFY observers
80
81
133k
  notify_observer_remove_all(notify);
82
83
133k
  FREE(ptr);
84
133k
}
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
 */
93
void notify_set_parent(struct Notify *notify, struct Notify *parent)
94
9.42k
{
95
9.42k
  if (!notify)
96
0
    return;
97
98
9.42k
  notify->parent = parent;
99
9.42k
}
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
 */
118
static bool send(struct Notify *source, struct Notify *current,
119
                 enum NotifyType event_type, int event_subtype, void *event_data)
120
133k
{
121
133k
  if (!source || !current)
122
0
    return false;
123
124
133k
  mutt_debug(LL_NOTIFY, "send: %d, %p\n", event_type, event_data);
125
133k
  struct ObserverNode *np = NULL;
126
133k
  STAILQ_FOREACH(np, &current->observers, entries)
127
1
  {
128
1
    struct Observer *o = np->observer;
129
1
    if (!o)
130
0
      continue;
131
132
1
    if ((o->type == NT_ALL) || (event_type == o->type))
133
1
    {
134
1
      struct NotifyCallback nc = { current, event_type, event_subtype,
135
1
                                   event_data, o->global_data };
136
1
      if (o->callback(&nc) < 0)
137
0
      {
138
0
        mutt_debug(LL_DEBUG1, "failed to send notification: %s/%d, global %p, event %p\n",
139
0
                   NotifyTypeNames[event_type], event_subtype, o->global_data, event_data);
140
0
      }
141
1
    }
142
1
  }
143
144
133k
  if (current->parent)
145
9.42k
    return send(source, current->parent, event_type, event_subtype, event_data);
146
147
  // Garbage collection time
148
124k
  struct ObserverNode *tmp = NULL;
149
124k
  STAILQ_FOREACH_SAFE(np, &current->observers, entries, tmp)
150
1
  {
151
1
    if (np->observer)
152
1
      continue;
153
154
0
    STAILQ_REMOVE(&current->observers, np, ObserverNode, entries);
155
0
    FREE(&np);
156
0
  }
157
158
124k
  return true;
159
133k
}
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
 */
171
bool notify_send(struct Notify *notify, enum NotifyType event_type,
172
                 int event_subtype, void *event_data)
173
124k
{
174
124k
  mutt_debug(LL_NOTIFY, "sending: %s/%d\n", NotifyTypeNames[event_type], event_subtype);
175
124k
  return send(notify, notify, event_type, event_subtype, event_data);
176
124k
}
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
 */
189
bool notify_observer_add(struct Notify *notify, enum NotifyType type,
190
                         observer_t callback, void *global_data)
191
1
{
192
1
  if (!notify || !callback)
193
0
    return false;
194
195
1
  struct ObserverNode *np = NULL;
196
1
  STAILQ_FOREACH(np, &notify->observers, entries)
197
0
  {
198
0
    if (!np->observer)
199
0
      continue;
200
201
0
    if ((np->observer->callback == callback) && (np->observer->global_data == global_data))
202
0
      return true;
203
0
  }
204
205
1
  struct Observer *o = mutt_mem_calloc(1, sizeof(*o));
206
1
  o->type = type;
207
1
  o->callback = callback;
208
1
  o->global_data = global_data;
209
210
1
  np = mutt_mem_calloc(1, sizeof(*np));
211
1
  np->observer = o;
212
1
  STAILQ_INSERT_HEAD(&notify->observers, np, entries);
213
214
1
  return true;
215
1
}
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
 */
228
bool notify_observer_remove(struct Notify *notify, const observer_t callback,
229
                            const void *global_data)
230
0
{
231
0
  if (!notify || !callback)
232
0
    return false;
233
234
0
  struct ObserverNode *np = NULL;
235
0
  STAILQ_FOREACH(np, &notify->observers, entries)
236
0
  {
237
0
    if (!np->observer)
238
0
      continue;
239
240
0
    if ((np->observer->callback == callback) && (np->observer->global_data == global_data))
241
0
    {
242
0
      FREE(&np->observer);
243
0
      return true;
244
0
    }
245
0
  }
246
247
0
  return false;
248
0
}
249
250
/**
251
 * notify_observer_remove_all - Remove all the observers from an object
252
 * @param notify Notification handler
253
 */
254
void notify_observer_remove_all(struct Notify *notify)
255
133k
{
256
133k
  if (!notify)
257
0
    return;
258
259
133k
  struct ObserverNode *np = NULL;
260
133k
  struct ObserverNode *tmp = NULL;
261
133k
  STAILQ_FOREACH_SAFE(np, &notify->observers, entries, tmp)
262
1
  {
263
1
    STAILQ_REMOVE(&notify->observers, np, ObserverNode, entries);
264
1
    FREE(&np->observer);
265
1
    FREE(&np);
266
1
  }
267
133k
}