Fix documentation replication issues
[asterisk/asterisk.git] / res / parking / parking_manager.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Jonathan Rose <jrose@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 Call Parking Manager Actions and Events
22  *
23  * \author Jonathan Rose <jrose@digium.com>
24  */
25
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
29
30 #include "res_parking.h"
31 #include "asterisk/config.h"
32 #include "asterisk/config_options.h"
33 #include "asterisk/event.h"
34 #include "asterisk/utils.h"
35 #include "asterisk/module.h"
36 #include "asterisk/cli.h"
37 #include "asterisk/astobj2.h"
38 #include "asterisk/features.h"
39 #include "asterisk/manager.h"
40 #include "asterisk/bridge.h"
41
42 /*** DOCUMENTATION
43         <manager name="Parkinglots" language="en_US">
44                 <synopsis>
45                         Get a list of parking lots
46                 </synopsis>
47                 <syntax>
48                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
49                 </syntax>
50                 <description>
51                         <para>List all parking lots as a series of AMI events</para>
52                 </description>
53         </manager>
54         <manager name="ParkedCalls" language="en_US">
55                 <synopsis>
56                         List parked calls.
57                 </synopsis>
58                 <syntax>
59                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
60                         <parameter name="ParkingLot">
61                                 <para>If specified, only show parked calls from the parking lot with this name.</para>
62                         </parameter>
63                 </syntax>
64                 <description>
65                         <para>List parked calls.</para>
66                 </description>
67         </manager>
68         <manager name="Park" language="en_US">
69                 <synopsis>
70                         Park a channel.
71                 </synopsis>
72                 <syntax>
73                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
74                         <parameter name="Channel" required="true">
75                                 <para>Channel name to park.</para>
76                         </parameter>
77                         <parameter name="TimeoutChannel" required="false">
78                                 <para>Channel name to use when constructing the dial string that will be dialed if the parked channel times out.</para>
79                         </parameter>
80                         <parameter name="Timeout" required="false">
81                                 <para>Overrides the timeout of the parking lot for this park action. Specified in milliseconds, but will be converted to
82                                         seconds. Use a value of 0 to nullify the timeout.
83                                 </para>
84                         </parameter>
85                         <parameter name="Parkinglot" required="false">
86                                 <para>The parking lot to use when parking the channel</para>
87                         </parameter>
88                 </syntax>
89                 <description>
90                         <para>Park an arbitrary channel with optional arguments for specifying the parking lot used, how long
91                                 the channel should remain parked, and what dial string to use as the parker if the call times out.
92                         </para>
93                 </description>
94         </manager>
95         <managerEvent language="en_US" name="ParkedCall">
96                 <managerEventInstance class="EVENT_FLAG_CALL">
97                         <synopsis>Raised when a channel is parked.</synopsis>
98                         <syntax>
99                                 <channel_snapshot prefix="Parkee"/>
100                                 <parameter name="ParkerDialString">
101                                         <para>Dial String that can be used to call back the parker on ParkingTimeout.</para>
102                                 </parameter>
103                                 <parameter name="Parkinglot">
104                                         <para>Name of the parking lot that the parkee is parked in</para>
105                                 </parameter>
106                                 <parameter name="ParkingSpace">
107                                         <para>Parking Space that the parkee is parked in</para>
108                                 </parameter>
109                                 <parameter name="ParkingTimeout">
110                                 <para>Time remaining until the parkee is forcefully removed from parking in seconds</para>
111                                 </parameter>
112                                 <parameter name="ParkingDuration">
113                                         <para>Time the parkee has been in the parking bridge (in seconds)</para>
114                                 </parameter>
115                         </syntax>
116                 </managerEventInstance>
117         </managerEvent>
118         <managerEvent language="en_US" name="ParkedCallTimeOut">
119                 <managerEventInstance class="EVENT_FLAG_CALL">
120                         <synopsis>Raised when a channel leaves a parking lot due to reaching the time limit of being parked.</synopsis>
121                         <syntax>
122                                 <channel_snapshot prefix="Parkee"/>
123                                 <channel_snapshot prefix="Parker"/>
124                                 <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
125                         </syntax>
126                 </managerEventInstance>
127         </managerEvent>
128         <managerEvent language="en_US" name="ParkedCallGiveUp">
129                 <managerEventInstance class="EVENT_FLAG_CALL">
130                         <synopsis>Raised when a channel leaves a parking lot because it hung up without being answered.</synopsis>
131                         <syntax>
132                                 <channel_snapshot prefix="Parkee"/>
133                                 <channel_snapshot prefix="Parker"/>
134                                 <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
135                         </syntax>
136                 </managerEventInstance>
137         </managerEvent>
138         <managerEvent language="en_US" name="UnParkedCall">
139                 <managerEventInstance class="EVENT_FLAG_CALL">
140                         <synopsis>Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected.</synopsis>
141                         <syntax>
142                                 <channel_snapshot prefix="Parkee"/>
143                                 <channel_snapshot prefix="Parker"/>
144                                 <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
145                                 <channel_snapshot prefix="Retriever"/>
146                         </syntax>
147                 </managerEventInstance>
148         </managerEvent>
149  ***/
150
151 /*! \brief subscription to the parking lot topic */
152 static struct stasis_subscription *parking_sub;
153
154 static struct ast_parked_call_payload *parked_call_payload_from_failure(struct ast_channel *chan)
155 {
156         RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
157         RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
158
159         parkee_snapshot = ast_channel_snapshot_create(chan);
160         if (!parkee_snapshot) {
161                 return NULL;
162         }
163
164         return ast_parked_call_payload_create(PARKED_CALL_FAILED, parkee_snapshot, NULL, NULL, NULL, 0, 0, 0);
165 }
166
167 static struct ast_parked_call_payload *parked_call_payload_from_parked_user(struct parked_user *pu, enum ast_parked_call_event_type event_type)
168 {
169         RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
170         RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
171         long int timeout;
172         long int duration;
173         struct timeval now = ast_tvnow();
174         const char *lot_name = pu->lot->name;
175
176         parkee_snapshot = ast_channel_snapshot_create(pu->chan);
177
178         if (!parkee_snapshot) {
179                 return NULL;
180         }
181
182         timeout = pu->start.tv_sec + (long) pu->time_limit - now.tv_sec;
183         duration = now.tv_sec - pu->start.tv_sec;
184
185         return ast_parked_call_payload_create(event_type, parkee_snapshot, pu->parker_dial_string, pu->retriever, lot_name, pu->parking_space, timeout, duration);
186
187 }
188
189 /*! \brief Builds a manager string based on the contents of a parked call payload */
190 static struct ast_str *manager_build_parked_call_string(const struct ast_parked_call_payload *payload)
191 {
192         struct ast_str *out = ast_str_create(1024);
193         RAII_VAR(struct ast_str *, parkee_string, NULL, ast_free);
194         RAII_VAR(struct ast_str *, retriever_string, NULL, ast_free);
195
196         if (!out) {
197                 return NULL;
198         }
199
200         parkee_string = ast_manager_build_channel_state_string_prefix(payload->parkee, "Parkee");
201         if (!parkee_string) {
202                 return NULL;
203         }
204
205         if (payload->retriever) {
206                 retriever_string = ast_manager_build_channel_state_string_prefix(payload->retriever, "Retriever");
207         }
208
209         ast_str_set(&out, 0,
210                 "%s" /* parkee channel state */
211                 "%s" /* retriever channel state (when available) */
212                 "ParkerDialString: %s\r\n"
213                 "Parkinglot: %s\r\n"
214                 "ParkingSpace: %u\r\n"
215                 "ParkingTimeout: %lu\r\n"
216                 "ParkingDuration: %lu\r\n",
217
218                 ast_str_buffer(parkee_string),
219                 retriever_string ? ast_str_buffer(retriever_string) : "",
220                 payload->parker_dial_string,
221                 payload->parkinglot,
222                 payload->parkingspace,
223                 payload->timeout,
224                 payload->duration);
225
226         return out;
227 }
228
229 static int manager_parking_status_single_lot(struct mansession *s, const struct message *m, const char *id_text, const char *lot_name)
230 {
231         RAII_VAR(struct parking_lot *, curlot, NULL, ao2_cleanup);
232         struct parked_user *curuser;
233         struct ao2_iterator iter_users;
234         int total = 0;
235
236         curlot = parking_lot_find_by_name(lot_name);
237
238         if (!curlot) {
239                 astman_send_error(s, m, "Requested parking lot could not be found.");
240                 return RESULT_SUCCESS;
241         }
242
243         astman_send_ack(s, m, "Parked calls will follow");
244
245         iter_users = ao2_iterator_init(curlot->parked_users, 0);
246         while ((curuser = ao2_iterator_next(&iter_users))) {
247                 RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
248                 RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
249
250                 payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
251                 if (!payload) {
252                         astman_send_error(s, m, "Failed to retrieve parking data about a parked user.");
253                         return RESULT_FAILURE;
254                 }
255
256                 parked_call_string = manager_build_parked_call_string(payload);
257                 if (!parked_call_string) {
258                         astman_send_error(s, m, "Failed to retrieve parkingd ata about a parked user.");
259                         return RESULT_FAILURE;
260                 }
261
262                 total++;
263
264                 astman_append(s, "Event: ParkedCall\r\n"
265                         "%s" /* The parked call string */
266                         "%s" /* The action ID */
267                         "\r\n",
268                         ast_str_buffer(parked_call_string),
269                         id_text);
270
271                 ao2_ref(curuser, -1);
272         }
273
274         ao2_iterator_destroy(&iter_users);
275
276         astman_append(s,
277                 "Event: ParkedCallsComplete\r\n"
278                 "Total: %d\r\n"
279                 "%s"
280                 "\r\n",
281                 total, id_text);
282
283         return RESULT_SUCCESS;
284 }
285
286 static int manager_parking_status_all_lots(struct mansession *s, const struct message *m, const char *id_text)
287 {
288         struct parked_user *curuser;
289         struct ao2_container *lot_container;
290         struct ao2_iterator iter_lots;
291         struct ao2_iterator iter_users;
292         struct parking_lot *curlot;
293         int total = 0;
294
295         lot_container = get_parking_lot_container();
296
297         if (!lot_container) {
298                 ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
299                 astman_send_error(s, m, "Could not create parking lot list");
300                 return RESULT_SUCCESS;
301         }
302
303         iter_lots = ao2_iterator_init(lot_container, 0);
304
305         astman_send_ack(s, m, "Parked calls will follow");
306
307         while ((curlot = ao2_iterator_next(&iter_lots))) {
308                 iter_users = ao2_iterator_init(curlot->parked_users, 0);
309                 while ((curuser = ao2_iterator_next(&iter_users))) {
310                         RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
311                         RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
312
313                         payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
314                         if (!payload) {
315                                 return RESULT_FAILURE;
316                         }
317
318                         parked_call_string = manager_build_parked_call_string(payload);
319                         if (!payload) {
320                                 return RESULT_FAILURE;
321                         }
322
323                         total++;
324
325                         astman_append(s, "Event: ParkedCall\r\n"
326                                 "%s" /* The parked call string */
327                                 "%s" /* The action ID */
328                                 "\r\n",
329                                 ast_str_buffer(parked_call_string),
330                                 id_text);
331
332                         ao2_ref(curuser, -1);
333                 }
334                 ao2_iterator_destroy(&iter_users);
335                 ao2_ref(curlot, -1);
336         }
337
338         ao2_iterator_destroy(&iter_lots);
339
340         astman_append(s,
341                 "Event: ParkedCallsComplete\r\n"
342                 "Total: %d\r\n"
343                 "%s"
344                 "\r\n",
345                 total, id_text);
346
347         return RESULT_SUCCESS;
348 }
349
350 static int manager_parking_status(struct mansession *s, const struct message *m)
351 {
352         const char *id = astman_get_header(m, "ActionID");
353         const char *lot_name = astman_get_header(m, "ParkingLot");
354         char id_text[256] = "";
355
356         if (!ast_strlen_zero(id)) {
357                 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
358         }
359
360         if (!ast_strlen_zero(lot_name)) {
361                 return manager_parking_status_single_lot(s, m, id_text, lot_name);
362         }
363
364         return manager_parking_status_all_lots(s, m, id_text);
365
366 }
367
368 static int manager_append_event_parking_lot_data_cb(void *obj, void *arg, void *data, int flags)
369 {
370         struct parking_lot *curlot = obj;
371         struct mansession *s = arg;
372         char *id_text = data;
373
374         astman_append(s, "Event: Parkinglot\r\n"
375                 "Name: %s\r\n"
376                 "StartSpace: %d\r\n"
377                 "StopSpace: %d\r\n"
378                 "Timeout: %d\r\n"
379                 "%s" /* The Action ID */
380                 "\r\n",
381                 curlot->name,
382                 curlot->cfg->parking_start,
383                 curlot->cfg->parking_stop,
384                 curlot->cfg->parkingtime,
385                 id_text);
386
387         return 0;
388 }
389
390 static int manager_parking_lot_list(struct mansession *s, const struct message *m)
391 {
392         const char *id = astman_get_header(m, "ActionID");
393         char id_text[256] = "";
394         struct ao2_container *lot_container;
395
396         if (!ast_strlen_zero(id)) {
397                 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
398         }
399
400         lot_container = get_parking_lot_container();
401
402         if (!lot_container) {
403                 ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
404                 astman_send_error(s, m, "Could not create parking lot list");
405                 return -1;
406         }
407
408         astman_send_ack(s, m, "Parking lots will follow");
409
410         ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA, manager_append_event_parking_lot_data_cb, s, id_text);
411
412         astman_append(s,
413                 "Event: ParkinglotsComplete\r\n"
414                 "%s"
415                 "\r\n",id_text);
416
417         return RESULT_SUCCESS;
418 }
419
420 static int manager_park(struct mansession *s, const struct message *m)
421 {
422         const char *channel = astman_get_header(m, "Channel");
423         const char *timeout_channel = S_OR(astman_get_header(m, "TimeoutChannel"), astman_get_header(m, "Channel2"));
424         const char *timeout = astman_get_header(m, "Timeout");
425         const char *parkinglot = astman_get_header(m, "Parkinglot");
426         char buf[BUFSIZ];
427         int timeout_override = -1;
428
429         RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
430         RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
431
432         if (ast_strlen_zero(channel)) {
433                 astman_send_error(s, m, "Channel not specified");
434                 return 0;
435         }
436
437         if (!ast_strlen_zero(timeout)) {
438                 if (sscanf(timeout, "%30d", &timeout_override) != 1 || timeout < 0) {
439                         astman_send_error(s, m, "Invalid Timeout value.");
440                         return 0;
441                 }
442
443                 if (timeout_override > 0) {
444                         /* If greater than zero, convert to seconds for internal use. Must be >= 1 second. */
445                         timeout_override = MAX(1, timeout_override / 1000);
446                 }
447         }
448
449         if (!(chan = ast_channel_get_by_name(channel))) {
450                 snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
451                 astman_send_error(s, m, buf);
452                 return 0;
453         }
454
455         ast_channel_lock(chan);
456         if (!ast_strlen_zero(timeout_channel)) {
457                 pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", timeout_channel);
458         }
459         ast_channel_unlock(chan);
460
461         if (!(parking_bridge = park_common_setup(chan, chan, parkinglot, NULL, 0, 0, timeout_override, 0))) {
462                 astman_send_error(s, m, "Park action failed\n");
463                 return 0;
464         }
465
466         if (ast_bridge_add_channel(parking_bridge, chan, NULL, 0, NULL)) {
467                 astman_send_error(s, m, "Park action failed\n");
468                 return 0;
469         }
470
471         astman_send_ack(s, m, "Park successful\n");
472         return 0;
473 }
474
475 void publish_parked_call_failure(struct ast_channel *parkee)
476 {
477         RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
478         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
479
480         payload = parked_call_payload_from_failure(parkee);
481         if (!payload) {
482                 return;
483         }
484
485         msg = stasis_message_create(ast_parked_call_type(), payload);
486         if (!msg) {
487                 return;
488         }
489
490         stasis_publish(ast_parking_topic(), msg);
491 }
492
493 void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
494 {
495         RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
496         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
497
498         payload = parked_call_payload_from_parked_user(pu, event_type);
499         if (!payload) {
500                 return;
501         }
502
503         msg = stasis_message_create(ast_parked_call_type(), payload);
504         if (!msg) {
505                 return;
506         }
507
508         stasis_publish(ast_parking_topic(), msg);
509 }
510
511 static void parked_call_message_response(struct ast_parked_call_payload *parked_call)
512 {
513         char *event_type = "";
514         RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
515
516         switch (parked_call->event_type) {
517         case PARKED_CALL:
518                 event_type = "ParkedCall";
519                 break;
520         case PARKED_CALL_TIMEOUT:
521                 event_type = "ParkedCallTimeOut";
522                 break;
523         case PARKED_CALL_GIVEUP:
524                 event_type = "ParkedCallGiveUp";
525                 break;
526         case PARKED_CALL_UNPARKED:
527                 event_type = "UnParkedCall";
528                 break;
529         case PARKED_CALL_FAILED:
530                 /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */
531                 return;
532         }
533
534         parked_call_string = manager_build_parked_call_string(parked_call);
535         if (!parked_call_string) {
536                 ast_log(LOG_ERROR, "Failed to issue an AMI event of '%s' in response to a stasis message.\n", event_type);
537                 return;
538         }
539
540         manager_event(EVENT_FLAG_CALL, event_type,
541                         "%s",
542                         ast_str_buffer(parked_call_string)
543                 );
544 }
545
546 static void parking_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
547 {
548         if (stasis_message_type(message) == ast_parked_call_type()) {
549                 struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
550                 parked_call_message_response(parked_call_message);
551         }
552 }
553
554 static void parking_manager_enable_stasis(void)
555 {
556         if (!parking_sub) {
557                 parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
558         }
559 }
560
561 int load_parking_manager(void)
562 {
563         int res;
564
565         res = ast_manager_register_xml_core("Parkinglots", EVENT_FLAG_CALL, manager_parking_lot_list);
566         res |= ast_manager_register_xml_core("ParkedCalls", EVENT_FLAG_CALL, manager_parking_status);
567         res |= ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
568         parking_manager_enable_stasis();
569         return res ? -1 : 0;
570 }
571
572 static void parking_manager_disable_stasis(void)
573 {
574         parking_sub = stasis_unsubscribe(parking_sub);
575 }
576
577 void unload_parking_manager(void)
578 {
579         ast_manager_unregister("Parkinglots");
580         ast_manager_unregister("ParkedCalls");
581         parking_manager_disable_stasis();
582 }