Add DeviceStateChanged and PresenceStateChanged AMI events.
[asterisk/asterisk.git] / main / presencestate.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2011-2012, Digium, Inc.
5  *
6  * David Vossel <dvossel@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Presence state management
22  */
23
24 /*** MODULEINFO
25         <support_level>core</support_level>
26  ***/
27
28 /*** DOCUMENTATION
29         <managerEvent language="en_US" name="PresenceStateChange">
30                 <managerEventInstance class="EVENT_FLAG_CALL">
31                         <synopsis>Raised when a presence state changes</synopsis>
32                         <syntax>
33                                 <parameter name="Presentity">
34                                         <para>The entity whose presence state has changed</para>
35                                 </parameter>
36                                 <parameter name="Status">
37                                         <para>The new status of the presentity</para>
38                                 </parameter>
39                                 <parameter name="Subtype">
40                                         <para>The new subtype of the presentity</para>
41                                 </parameter>
42                                 <parameter name="Message">
43                                         <para>The new message of the presentity</para>
44                                 </parameter>
45                         </syntax>
46                         <description>
47                                 <para>This differs from the <literal>PresenceStatus</literal>
48                                 event because this event is raised for all presence state changes,
49                                 not only for changes that affect dialplan hints.</para>
50                         </description>
51                         <see-also>
52                                 <ref type="managerEvent">PresenceStatus</ref>
53                         </see-also>
54                 </managerEventInstance>
55         </managerEvent>
56 ***/
57
58 #include "asterisk.h"
59
60 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
61
62 #include "asterisk/_private.h"
63 #include "asterisk/utils.h"
64 #include "asterisk/lock.h"
65 #include "asterisk/linkedlists.h"
66 #include "asterisk/presencestate.h"
67 #include "asterisk/pbx.h"
68 #include "asterisk/app.h"
69
70 /*! \brief Device state strings for printing */
71 static const struct {
72         const char *string;
73         enum ast_presence_state state;
74
75 } state2string[] = {
76         { "not_set", AST_PRESENCE_NOT_SET},
77         { "unavailable", AST_PRESENCE_UNAVAILABLE },
78         { "available", AST_PRESENCE_AVAILABLE},
79         { "away", AST_PRESENCE_AWAY},
80         { "xa", AST_PRESENCE_XA},
81         { "chat", AST_PRESENCE_CHAT},
82         { "dnd", AST_PRESENCE_DND},
83 };
84
85 static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg);
86
87 STASIS_MESSAGE_TYPE_DEFN(ast_presence_state_message_type,
88         .to_ami = presence_state_to_ami,
89 );
90 struct stasis_topic *presence_state_topic_all;
91 struct stasis_cache *presence_state_cache;
92 struct stasis_caching_topic *presence_state_topic_cached;
93
94 /*! \brief  A presence state provider */
95 struct presence_state_provider {
96         char label[40];
97         ast_presence_state_prov_cb_type callback;
98         AST_RWLIST_ENTRY(presence_state_provider) list;
99 };
100
101 /*! \brief A list of providers */
102 static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider);
103
104 const char *ast_presence_state2str(enum ast_presence_state state)
105 {
106         int i;
107         for (i = 0; i < ARRAY_LEN(state2string); i++) {
108                 if (state == state2string[i].state) {
109                         return state2string[i].string;
110                 }
111         }
112         return "";
113 }
114
115 enum ast_presence_state ast_presence_state_val(const char *val)
116 {
117         int i;
118         for (i = 0; i < ARRAY_LEN(state2string); i++) {
119                 if (!strcasecmp(val, state2string[i].string)) {
120                         return state2string[i].state;
121                 }
122         }
123         return AST_PRESENCE_INVALID;
124 }
125
126 static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
127 {
128         enum ast_presence_state res = AST_PRESENCE_INVALID;
129         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
130         struct ast_presence_state_message *presence_state;
131
132         msg = stasis_cache_get(ast_presence_state_cache(), ast_presence_state_message_type(), presence_provider);
133
134         if (!msg) {
135                 return res;
136         }
137
138         presence_state = stasis_message_data(msg);
139         res = presence_state->state;
140
141         *subtype = !ast_strlen_zero(presence_state->subtype) ? ast_strdup(presence_state->subtype) : NULL;
142         *message = !ast_strlen_zero(presence_state->message) ? ast_strdup(presence_state->message) : NULL;
143
144         return res;
145 }
146
147 static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
148 {
149         struct presence_state_provider *provider;
150         char *address;
151         char *label = ast_strdupa(presence_provider);
152         int res = AST_PRESENCE_INVALID;
153
154         if (check_cache) {
155                 res = presence_state_cached(presence_provider, subtype, message);
156                 if (res != AST_PRESENCE_INVALID) {
157                         return res;
158                 }
159         }
160
161         if ((address = strchr(label, ':'))) {
162                 *address = '\0';
163                 address++;
164         } else {
165                 ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", presence_provider);
166                 return res;
167         }
168
169         AST_RWLIST_RDLOCK(&presence_state_providers);
170         AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
171                 ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
172
173                 if (!strcasecmp(provider->label, label)) {
174                         res = provider->callback(address, subtype, message);
175                         break;
176                 }
177         }
178         AST_RWLIST_UNLOCK(&presence_state_providers);
179
180         if (!provider) {
181                 ast_log(LOG_WARNING, "No provider found for label %s\n", label);
182         }
183
184         return res;
185 }
186
187 enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
188 {
189         return ast_presence_state_helper(presence_provider, subtype, message, 1);
190 }
191
192 enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
193 {
194         return ast_presence_state_helper(presence_provider, subtype, message, 0);
195 }
196
197 int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
198 {
199         struct presence_state_provider *provider;
200
201         if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
202                 return -1;
203         }
204
205         provider->callback = callback;
206         ast_copy_string(provider->label, label, sizeof(provider->label));
207
208         AST_RWLIST_WRLOCK(&presence_state_providers);
209         AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
210         AST_RWLIST_UNLOCK(&presence_state_providers);
211
212         return 0;
213 }
214 int ast_presence_state_prov_del(const char *label)
215 {
216         struct presence_state_provider *provider;
217         int res = -1;
218
219         AST_RWLIST_WRLOCK(&presence_state_providers);
220         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
221                 if (!strcasecmp(provider->label, label)) {
222                         AST_RWLIST_REMOVE_CURRENT(list);
223                         ast_free(provider);
224                         res = 0;
225                         break;
226                 }
227         }
228         AST_RWLIST_TRAVERSE_SAFE_END;
229         AST_RWLIST_UNLOCK(&presence_state_providers);
230
231         return res;
232 }
233
234 static void presence_state_dtor(void *obj)
235 {
236         struct ast_presence_state_message *presence_state = obj;
237         ast_string_field_free_memory(presence_state);
238 }
239
240 static struct ast_presence_state_message *presence_state_alloc(const char *provider,
241                 enum ast_presence_state state,
242                 const char *subtype,
243                 const char *message)
244 {
245         RAII_VAR(struct ast_presence_state_message *, presence_state, ao2_alloc(sizeof(*presence_state), presence_state_dtor), ao2_cleanup);
246
247         if (!presence_state || ast_string_field_init(presence_state, 256)) {
248                 return NULL;
249         }
250
251         presence_state->state = state;
252         ast_string_field_set(presence_state, provider, provider);
253         ast_string_field_set(presence_state, subtype, S_OR(subtype, ""));
254         ast_string_field_set(presence_state, message, S_OR(message, ""));
255
256         ao2_ref(presence_state, +1);
257         return presence_state;
258 }
259
260 static void presence_state_event(const char *provider,
261                 enum ast_presence_state state,
262                 const char *subtype,
263                 const char *message)
264 {
265         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
266         RAII_VAR(struct ast_presence_state_message *, presence_state, presence_state_alloc(provider, state, subtype, message), ao2_cleanup);
267
268         if (!presence_state) {
269                 return;
270         }
271
272         msg = stasis_message_create(ast_presence_state_message_type(), presence_state);
273         if (!msg) {
274                 return;
275         }
276
277         stasis_publish(ast_presence_state_topic_all(), msg);
278 }
279
280 static void do_presence_state_change(const char *provider)
281 {
282         char *subtype = NULL;
283         char *message = NULL;
284         enum ast_presence_state state;
285
286         state = ast_presence_state_helper(provider, &subtype, &message, 0);
287
288         if (state < 0) {
289                 return;
290         }
291
292         presence_state_event(provider, state, subtype, message);
293         ast_free(subtype);
294         ast_free(message);
295 }
296
297 int ast_presence_state_changed_literal(enum ast_presence_state state,
298                 const char *subtype,
299                 const char *message,
300                 const char *presence_provider)
301 {
302         if (state == AST_PRESENCE_NOT_SET) {
303                 do_presence_state_change(presence_provider);
304         } else {
305                 presence_state_event(presence_provider, state, subtype, message);
306         }
307
308         return 0;
309 }
310
311 int ast_presence_state_changed(enum ast_presence_state state,
312                 const char *subtype,
313                 const char *message,
314                 const char *fmt, ...)
315 {
316         char buf[AST_MAX_EXTENSION];
317         va_list ap;
318
319         va_start(ap, fmt);
320         vsnprintf(buf, sizeof(buf), fmt, ap);
321         va_end(ap);
322
323         return ast_presence_state_changed_literal(state, subtype, message, buf);
324 }
325
326 struct stasis_topic *ast_presence_state_topic_all(void)
327 {
328         return presence_state_topic_all;
329 }
330
331 struct stasis_cache *ast_presence_state_cache(void)
332 {
333         return presence_state_cache;
334 }
335
336 struct stasis_topic *ast_presence_state_topic_cached(void)
337 {
338         return stasis_caching_get_topic(presence_state_topic_cached);
339 }
340
341 static const char *presence_state_get_id(struct stasis_message *msg)
342 {
343         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
344
345         if (stasis_message_type(msg) != ast_presence_state_message_type()) {
346                 return NULL;
347         }
348
349         return presence_state->provider;
350 }
351
352 static void presence_state_engine_cleanup(void)
353 {
354         ao2_cleanup(presence_state_topic_all);
355         presence_state_topic_all = NULL;
356         ao2_cleanup(presence_state_cache);
357         presence_state_cache = NULL;
358         presence_state_topic_cached = stasis_caching_unsubscribe_and_join(presence_state_topic_cached);
359         STASIS_MESSAGE_TYPE_CLEANUP(ast_presence_state_message_type);
360 }
361
362 int ast_presence_state_engine_init(void)
363 {
364         ast_register_cleanup(presence_state_engine_cleanup);
365
366         if (STASIS_MESSAGE_TYPE_INIT(ast_presence_state_message_type) != 0) {
367                 return -1;
368         }
369
370         presence_state_topic_all = stasis_topic_create("ast_presence_state_topic_all");
371         if (!presence_state_topic_all) {
372                 return -1;
373         }
374
375         presence_state_cache = stasis_cache_create(presence_state_get_id);
376         if (!presence_state_cache) {
377                 return -1;
378         }
379
380         presence_state_topic_cached = stasis_caching_topic_create(presence_state_topic_all, presence_state_cache);
381         if (!presence_state_topic_cached) {
382                 return -1;
383         }
384
385         return 0;
386 }
387
388 static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg)
389 {
390         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
391
392         return ast_manager_event_blob_create(EVENT_FLAG_CALL, "PresenceStateChange",
393                 "Presentity: %s\r\n"
394                 "Status: %s\r\n"
395                 "Subtype: %s\r\n"
396                 "Message: %s\r\n",
397                 presence_state->provider,
398                 ast_presence_state2str(presence_state->state),
399                 presence_state->subtype,
400                 presence_state->message);
401 }