Merge changes dealing with support for Digium phones.
[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 #include "asterisk.h"
24
25 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
26
27 #include "asterisk/_private.h"
28 #include "asterisk/utils.h"
29 #include "asterisk/lock.h"
30 #include "asterisk/linkedlists.h"
31 #include "asterisk/presencestate.h"
32 #include "asterisk/pbx.h"
33 #include "asterisk/app.h"
34 #include "asterisk/event.h"
35
36 /*! \brief Device state strings for printing */
37 static const struct {
38         const char *string;
39         enum ast_presence_state state;
40
41 } state2string[] = {
42         { "not_set", AST_PRESENCE_NOT_SET},
43         { "unavailable", AST_PRESENCE_UNAVAILABLE },
44         { "available", AST_PRESENCE_AVAILABLE},
45         { "away", AST_PRESENCE_AWAY},
46         { "xa", AST_PRESENCE_XA},
47         { "chat", AST_PRESENCE_CHAT},
48         { "dnd", AST_PRESENCE_DND},
49 };
50
51 /*! \brief Flag for the queue */
52 static ast_cond_t change_pending;
53
54 struct state_change {
55         AST_LIST_ENTRY(state_change) list;
56         char provider[1];
57 };
58
59 /*! \brief  A presence state provider */
60 struct presence_state_provider {
61         char label[40];
62         ast_presence_state_prov_cb_type callback;
63         AST_RWLIST_ENTRY(presence_state_provider) list;
64 };
65
66 /*! \brief A list of providers */
67 static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider);
68
69 /*! \brief The state change queue. State changes are queued
70         for processing by a separate thread */
71 static AST_LIST_HEAD_STATIC(state_changes, state_change);
72
73 /*! \brief The presence state change notification thread */
74 static pthread_t change_thread = AST_PTHREADT_NULL;
75
76 const char *ast_presence_state2str(enum ast_presence_state state)
77 {
78         int i;
79         for (i = 0; i < ARRAY_LEN(state2string); i++) {
80                 if (state == state2string[i].state) {
81                         return state2string[i].string;
82                 }
83         }
84         return "";
85 }
86
87 enum ast_presence_state ast_presence_state_val(const char *val)
88 {
89         int i;
90         for (i = 0; i < ARRAY_LEN(state2string); i++) {
91                 if (!strcasecmp(val, state2string[i].string)) {
92                         return state2string[i].state;
93                 }
94         }
95         return AST_PRESENCE_INVALID;
96 }
97
98 static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
99 {
100         enum ast_presence_state res = AST_PRESENCE_INVALID;
101         struct ast_event *event;
102         const char *_subtype;
103         const char *_message;
104
105         event = ast_event_get_cached(AST_EVENT_PRESENCE_STATE,
106                 AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, presence_provider,
107                 AST_EVENT_IE_END);
108
109         if (!event) {
110                 return res;
111         }
112
113         res = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
114         _subtype = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE);
115         _message = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE);
116
117         *subtype = !ast_strlen_zero(_subtype) ? ast_strdup(_subtype) : NULL;
118         *message = !ast_strlen_zero(_message) ? ast_strdup(_message) : NULL;
119         ast_event_destroy(event);
120
121         return res;
122 }
123
124 static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
125 {
126         struct presence_state_provider *provider;
127         char *address;
128         char *label = ast_strdupa(presence_provider);
129         int res = AST_PRESENCE_INVALID;
130
131         if (check_cache) {
132                 res = presence_state_cached(presence_provider, subtype, message);
133                 if (res != AST_PRESENCE_INVALID) {
134                         return res;
135                 }
136         }
137
138         if ((address = strchr(label, ':'))) {
139                 *address = '\0';
140                 address++;
141         } else {
142                 ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", presence_provider);
143                 return res;
144         }
145
146         AST_RWLIST_RDLOCK(&presence_state_providers);
147         AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
148                 ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
149
150                 if (!strcasecmp(provider->label, label)) {
151                         res = provider->callback(address, subtype, message);
152                         break;
153                 }
154         }
155         AST_RWLIST_UNLOCK(&presence_state_providers);
156
157
158         return res;
159 }
160
161 enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
162 {
163         return ast_presence_state_helper(presence_provider, subtype, message, 1);
164 }
165
166 enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
167 {
168         return ast_presence_state_helper(presence_provider, subtype, message, 0);
169 }
170
171 int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
172 {
173         struct presence_state_provider *provider;
174
175         if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
176                 return -1;
177         }
178
179         provider->callback = callback;
180         ast_copy_string(provider->label, label, sizeof(provider->label));
181
182         AST_RWLIST_WRLOCK(&presence_state_providers);
183         AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
184         AST_RWLIST_UNLOCK(&presence_state_providers);
185
186         return 0;
187 }
188 int ast_presence_state_prov_del(const char *label)
189 {
190         struct presence_state_provider *provider;
191         int res = -1;
192
193         AST_RWLIST_WRLOCK(&presence_state_providers);
194         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
195                 if (!strcasecmp(provider->label, label)) {
196                         AST_RWLIST_REMOVE_CURRENT(list);
197                         ast_free(provider);
198                         res = 0;
199                         break;
200                 }
201         }
202         AST_RWLIST_TRAVERSE_SAFE_END;
203         AST_RWLIST_UNLOCK(&presence_state_providers);
204
205         return res;
206 }
207
208 static void presence_state_event(const char *provider,
209                 enum ast_presence_state state,
210                 const char *subtype,
211                 const char *message)
212 {
213         struct ast_event *event;
214
215         if (!(event = ast_event_new(AST_EVENT_PRESENCE_STATE,
216                         AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, provider,
217                         AST_EVENT_IE_PRESENCE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
218                         AST_EVENT_IE_PRESENCE_SUBTYPE, AST_EVENT_IE_PLTYPE_STR, S_OR(subtype, ""),
219                         AST_EVENT_IE_PRESENCE_MESSAGE, AST_EVENT_IE_PLTYPE_STR, S_OR(message, ""),
220                         AST_EVENT_IE_END))) {
221                 return;
222         }
223
224         ast_event_queue_and_cache(event);
225 }
226
227 static void do_presence_state_change(const char *provider)
228 {
229         char *subtype = NULL;
230         char *message = NULL;
231         enum ast_presence_state state;
232
233         state = ast_presence_state_helper(provider, &subtype, &message, 0);
234
235         if (state < 0) {
236                 return;
237         }
238
239         presence_state_event(provider, state, subtype, message);
240         ast_free(subtype);
241         ast_free(message);
242 }
243
244 int ast_presence_state_changed_literal(enum ast_presence_state state,
245                 const char *subtype,
246                 const char *message,
247                 const char *presence_provider)
248 {
249         struct state_change *change;
250
251         if (state != AST_PRESENCE_NOT_SET) {
252                 presence_state_event(presence_provider, state, subtype, message);
253         } else if ((change_thread == AST_PTHREADT_NULL) ||
254                 !(change = ast_calloc(1, sizeof(*change) + strlen(presence_provider)))) {
255                 do_presence_state_change(presence_provider);
256         } else {
257                 strcpy(change->provider, presence_provider);
258                 AST_LIST_LOCK(&state_changes);
259                 AST_LIST_INSERT_TAIL(&state_changes, change, list);
260                 ast_cond_signal(&change_pending);
261                 AST_LIST_UNLOCK(&state_changes);
262         }
263
264         return 0;
265 }
266
267 int ast_presence_state_changed(enum ast_presence_state state,
268                 const char *subtype,
269                 const char *message,
270                 const char *fmt, ...)
271 {
272         char buf[AST_MAX_EXTENSION];
273         va_list ap;
274
275         va_start(ap, fmt);
276         vsnprintf(buf, sizeof(buf), fmt, ap);
277         va_end(ap);
278
279         return ast_presence_state_changed_literal(state, subtype, message, buf);
280 }
281
282 /*! \brief Go through the presence state change queue and update changes in the presence state thread */
283 static void *do_presence_changes(void *data)
284 {
285         struct state_change *next, *current;
286
287         for (;;) {
288                 /* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */
289                 AST_LIST_LOCK(&state_changes);
290                 if (AST_LIST_EMPTY(&state_changes))
291                         ast_cond_wait(&change_pending, &state_changes.lock);
292                 next = AST_LIST_FIRST(&state_changes);
293                 AST_LIST_HEAD_INIT_NOLOCK(&state_changes);
294                 AST_LIST_UNLOCK(&state_changes);
295
296                 /* Process each state change */
297                 while ((current = next)) {
298                         next = AST_LIST_NEXT(current, list);
299                         do_presence_state_change(current->provider);
300                         ast_free(current);
301                 }
302         }
303
304         return NULL;
305 }
306
307 int ast_presence_state_engine_init(void)
308 {
309         ast_cond_init(&change_pending, NULL);
310         if (ast_pthread_create_background(&change_thread, NULL, do_presence_changes, NULL) < 0) {
311                 ast_log(LOG_ERROR, "Unable to start presence state change thread.\n");
312                 return -1;
313         }
314
315         return 0;
316 }
317