ARI: allow other operations to happen while bridged
[asterisk/asterisk.git] / res / res_stasis_recording.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@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 res_stasis recording support.
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 /*** MODULEINFO
27         <depend type="module">res_stasis</depend>
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/dsp.h"
36 #include "asterisk/file.h"
37 #include "asterisk/module.h"
38 #include "asterisk/paths.h"
39 #include "asterisk/stasis_app_impl.h"
40 #include "asterisk/stasis_app_recording.h"
41 #include "asterisk/stasis_channels.h"
42
43 /*! Number of hash buckets for recording container. Keep it prime! */
44 #define RECORDING_BUCKETS 127
45
46 /*! Comment is ignored by most formats, so we will ignore it, too. */
47 #define RECORDING_COMMENT NULL
48
49 /*! Recording check is unimplemented. le sigh */
50 #define RECORDING_CHECK 0
51
52 STASIS_MESSAGE_TYPE_DEFN(stasis_app_recording_snapshot_type);
53
54 /*! Container of all current recordings */
55 static struct ao2_container *recordings;
56
57 struct stasis_app_recording {
58         /*! Recording options. */
59         struct stasis_app_recording_options *options;
60         /*! Absolute path (minus extension) of the recording */
61         char *absolute_name;
62         /*! Control object for the channel we're recording */
63         struct stasis_app_control *control;
64
65         /*! Current state of the recording. */
66         enum stasis_app_recording_state state;
67         /*! Indicates whether the recording is currently muted */
68         int muted:1;
69 };
70
71 static int recording_hash(const void *obj, int flags)
72 {
73         const struct stasis_app_recording *recording = obj;
74         const char *id = flags & OBJ_KEY ? obj : recording->options->name;
75         return ast_str_hash(id);
76 }
77
78 static int recording_cmp(void *obj, void *arg, int flags)
79 {
80         struct stasis_app_recording *lhs = obj;
81         struct stasis_app_recording *rhs = arg;
82         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->options->name;
83
84         if (strcmp(lhs->options->name, rhs_id) == 0) {
85                 return CMP_MATCH | CMP_STOP;
86         } else {
87                 return 0;
88         }
89 }
90
91 static const char *state_to_string(enum stasis_app_recording_state state)
92 {
93         switch (state) {
94         case STASIS_APP_RECORDING_STATE_QUEUED:
95                 return "queued";
96         case STASIS_APP_RECORDING_STATE_RECORDING:
97                 return "recording";
98         case STASIS_APP_RECORDING_STATE_PAUSED:
99                 return "paused";
100         case STASIS_APP_RECORDING_STATE_COMPLETE:
101                 return "done";
102         case STASIS_APP_RECORDING_STATE_FAILED:
103                 return "failed";
104         case STASIS_APP_RECORDING_STATE_CANCELED:
105                 return "canceled";
106         case STASIS_APP_RECORDING_STATE_MAX:
107                 return "?";
108         }
109
110         return "?";
111 }
112
113 static void recording_options_dtor(void *obj)
114 {
115         struct stasis_app_recording_options *options = obj;
116
117         ast_string_field_free_memory(options);
118 }
119
120 struct stasis_app_recording_options *stasis_app_recording_options_create(
121         const char *name, const char *format)
122 {
123         RAII_VAR(struct stasis_app_recording_options *, options, NULL,
124                 ao2_cleanup);
125
126         options = ao2_alloc(sizeof(*options), recording_options_dtor);
127
128         if (!options || ast_string_field_init(options, 128)) {
129                 return NULL;
130         }
131         ast_string_field_set(options, name, name);
132         ast_string_field_set(options, format, format);
133
134         ao2_ref(options, +1);
135         return options;
136 }
137
138 char stasis_app_recording_termination_parse(const char *str)
139 {
140         if (ast_strlen_zero(str)) {
141                 return STASIS_APP_RECORDING_TERMINATE_NONE;
142         }
143
144         if (strcasecmp(str, "none") == 0) {
145                 return STASIS_APP_RECORDING_TERMINATE_NONE;
146         }
147
148         if (strcasecmp(str, "any") == 0) {
149                 return STASIS_APP_RECORDING_TERMINATE_ANY;
150         }
151
152         if (strcasecmp(str, "#") == 0) {
153                 return '#';
154         }
155
156         if (strcasecmp(str, "*") == 0) {
157                 return '*';
158         }
159
160         return STASIS_APP_RECORDING_TERMINATE_INVALID;
161 }
162
163 enum ast_record_if_exists stasis_app_recording_if_exists_parse(
164         const char *str)
165 {
166         if (ast_strlen_zero(str)) {
167                 /* Default value */
168                 return AST_RECORD_IF_EXISTS_FAIL;
169         }
170
171         if (strcasecmp(str, "fail") == 0) {
172                 return AST_RECORD_IF_EXISTS_FAIL;
173         }
174
175         if (strcasecmp(str, "overwrite") == 0) {
176                 return AST_RECORD_IF_EXISTS_OVERWRITE;
177         }
178
179         if (strcasecmp(str, "append") == 0) {
180                 return AST_RECORD_IF_EXISTS_APPEND;
181         }
182
183         return -1;
184 }
185
186 static void recording_publish(struct stasis_app_recording *recording)
187 {
188         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
189         RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
190         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
191
192         ast_assert(recording != NULL);
193
194         json = stasis_app_recording_to_json(recording);
195         if (json == NULL) {
196                 return;
197         }
198
199         message = ast_channel_blob_create_from_cache(
200                 stasis_app_control_get_channel_id(recording->control),
201                 stasis_app_recording_snapshot_type(), json);
202         if (message == NULL) {
203                 return;
204         }
205
206         stasis_app_control_publish(recording->control, message);
207 }
208
209 static void recording_fail(struct stasis_app_recording *recording)
210 {
211         SCOPED_AO2LOCK(lock, recording);
212         recording->state = STASIS_APP_RECORDING_STATE_FAILED;
213         recording_publish(recording);
214 }
215
216 static void recording_cleanup(struct stasis_app_recording *recording)
217 {
218         ao2_unlink_flags(recordings, recording,
219                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
220 }
221
222 static void *record_file(struct stasis_app_control *control,
223         struct ast_channel *chan, void *data)
224 {
225         RAII_VAR(struct stasis_app_recording *, recording,
226                 NULL, recording_cleanup);
227         char *acceptdtmf;
228         int res;
229         int duration = 0;
230
231         recording = data;
232         ast_assert(recording != NULL);
233
234         if (stasis_app_get_bridge(control)) {
235                 ast_log(LOG_ERROR, "Cannot record channel while in bridge\n");
236                 recording_fail(recording);
237                 return NULL;
238         }
239
240         switch (recording->options->terminate_on) {
241         case STASIS_APP_RECORDING_TERMINATE_NONE:
242         case STASIS_APP_RECORDING_TERMINATE_INVALID:
243                 acceptdtmf = "";
244                 break;
245         case STASIS_APP_RECORDING_TERMINATE_ANY:
246                 acceptdtmf = "#*0123456789abcd";
247                 break;
248         default:
249                 acceptdtmf = ast_alloca(2);
250                 acceptdtmf[0] = recording->options->terminate_on;
251                 acceptdtmf[1] = '\0';
252         }
253
254         res = ast_auto_answer(chan);
255         if (res != 0) {
256                 ast_debug(3, "%s: Failed to answer\n",
257                         ast_channel_uniqueid(chan));
258                 recording_fail(recording);
259                 return NULL;
260         }
261
262         ao2_lock(recording);
263         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
264         recording_publish(recording);
265         ao2_unlock(recording);
266
267         ast_play_and_record_full(chan,
268                 NULL, /* playfile */
269                 recording->absolute_name,
270                 recording->options->max_duration_seconds,
271                 recording->options->format,
272                 &duration,
273                 NULL, /* sound_duration */
274                 recording->options->beep,
275                 -1, /* silencethreshold */
276                 recording->options->max_silence_seconds * 1000,
277                 NULL, /* path */
278                 acceptdtmf,
279                 NULL, /* canceldtmf */
280                 1, /* skip_confirmation_sound */
281                 recording->options->if_exists);
282
283         ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
284
285         ao2_lock(recording);
286         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
287         recording_publish(recording);
288         ao2_unlock(recording);
289
290         return NULL;
291 }
292
293 static void recording_dtor(void *obj)
294 {
295         struct stasis_app_recording *recording = obj;
296
297         ao2_cleanup(recording->options);
298 }
299
300 struct stasis_app_recording *stasis_app_control_record(
301         struct stasis_app_control *control,
302         struct stasis_app_recording_options *options)
303 {
304         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
305         char *last_slash;
306
307         errno = 0;
308
309         if (options == NULL ||
310                 ast_strlen_zero(options->name) ||
311                 ast_strlen_zero(options->format) ||
312                 options->max_silence_seconds < 0 ||
313                 options->max_duration_seconds < 0) {
314                 errno = EINVAL;
315                 return NULL;
316         }
317
318         ast_debug(3, "%s: Sending record(%s.%s) command\n",
319                 stasis_app_control_get_channel_id(control), options->name,
320                 options->format);
321
322         recording = ao2_alloc(sizeof(*recording), recording_dtor);
323         if (!recording) {
324                 errno = ENOMEM;
325                 return NULL;
326         }
327
328         ast_asprintf(&recording->absolute_name, "%s/%s",
329                 ast_config_AST_RECORDING_DIR, options->name);
330
331         if (recording->absolute_name == NULL) {
332                 errno = ENOMEM;
333                 return NULL;
334         }
335
336         if ((last_slash = strrchr(recording->absolute_name, '/'))) {
337                 *last_slash = '\0';
338                 if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
339                                 recording->absolute_name, 0777) != 0) {
340                         /* errno set by ast_mkdir */
341                         return NULL;
342                 }
343                 *last_slash = '/';
344         }
345
346         ao2_ref(options, +1);
347         recording->options = options;
348         recording->control = control;
349         recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
350
351         {
352                 RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
353                         ao2_cleanup);
354
355                 SCOPED_AO2LOCK(lock, recordings);
356
357                 old_recording = ao2_find(recordings, options->name,
358                         OBJ_KEY | OBJ_NOLOCK);
359                 if (old_recording) {
360                         ast_log(LOG_WARNING,
361                                 "Recording %s already in progress\n",
362                                 recording->options->name);
363                         errno = EEXIST;
364                         return NULL;
365                 }
366                 ao2_link(recordings, recording);
367         }
368
369         /* A ref is kept in the recordings container; no need to bump */
370         stasis_app_send_command_async(control, record_file, recording);
371
372         /* Although this should be bumped for the caller */
373         ao2_ref(recording, +1);
374         return recording;
375 }
376
377 enum stasis_app_recording_state stasis_app_recording_get_state(
378         struct stasis_app_recording *recording)
379 {
380         return recording->state;
381 }
382
383 const char *stasis_app_recording_get_name(
384         struct stasis_app_recording *recording)
385 {
386         return recording->options->name;
387 }
388
389 struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
390 {
391         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
392
393         recording = ao2_find(recordings, name, OBJ_KEY);
394         if (recording == NULL) {
395                 return NULL;
396         }
397
398         ao2_ref(recording, +1);
399         return recording;
400 }
401
402 struct ast_json *stasis_app_recording_to_json(
403         const struct stasis_app_recording *recording)
404 {
405         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
406
407         if (recording == NULL) {
408                 return NULL;
409         }
410
411         json = ast_json_pack("{s: s, s: s, s: s}",
412                 "name", recording->options->name,
413                 "format", recording->options->format,
414                 "state", state_to_string(recording->state));
415
416         return ast_json_ref(json);
417 }
418
419 typedef int (*recording_operation_cb)(struct stasis_app_recording *recording);
420
421 static int recording_noop(struct stasis_app_recording *recording)
422 {
423         return 0;
424 }
425
426 static int recording_disregard(struct stasis_app_recording *recording)
427 {
428         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
429         return 0;
430 }
431
432 static int recording_cancel(struct stasis_app_recording *recording)
433 {
434         int res = 0;
435         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
436         res |= stasis_app_control_queue_control(recording->control,
437                 AST_CONTROL_RECORD_CANCEL);
438         res |= ast_filedelete(recording->absolute_name, NULL);
439         return res;
440 }
441
442 static int recording_stop(struct stasis_app_recording *recording)
443 {
444         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
445         return stasis_app_control_queue_control(recording->control,
446                 AST_CONTROL_RECORD_STOP);
447 }
448
449 static int recording_pause(struct stasis_app_recording *recording)
450 {
451         recording->state = STASIS_APP_RECORDING_STATE_PAUSED;
452         return stasis_app_control_queue_control(recording->control,
453                 AST_CONTROL_RECORD_SUSPEND);
454 }
455
456 static int recording_unpause(struct stasis_app_recording *recording)
457 {
458         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
459         return stasis_app_control_queue_control(recording->control,
460                 AST_CONTROL_RECORD_SUSPEND);
461 }
462
463 static int recording_mute(struct stasis_app_recording *recording)
464 {
465         if (recording->muted) {
466                 /* already muted */
467                 return 0;
468         }
469
470         recording->muted = 1;
471         return stasis_app_control_queue_control(recording->control,
472                 AST_CONTROL_RECORD_MUTE);
473 }
474
475 static int recording_unmute(struct stasis_app_recording *recording)
476 {
477         if (!recording->muted) {
478                 /* already unmuted */
479                 return 0;
480         }
481
482         return stasis_app_control_queue_control(recording->control,
483                 AST_CONTROL_RECORD_MUTE);
484 }
485
486 recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = {
487         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard,
488         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard,
489         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
490         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop,
491         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause,
492         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop,
493         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute,
494         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
495         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
496         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop,
497         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop,
498         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause,
499         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute,
500         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
501 };
502
503 enum stasis_app_recording_oper_results stasis_app_recording_operation(
504         struct stasis_app_recording *recording,
505         enum stasis_app_recording_media_operation operation)
506 {
507         recording_operation_cb cb;
508         SCOPED_AO2LOCK(lock, recording);
509
510         if (recording->state < 0 || recording->state >= STASIS_APP_RECORDING_STATE_MAX) {
511                 ast_log(LOG_WARNING, "Invalid recording state %d\n",
512                         recording->state);
513                 return -1;
514         }
515
516         if (operation < 0 || operation >= STASIS_APP_RECORDING_OPER_MAX) {
517                 ast_log(LOG_WARNING, "Invalid recording operation %d\n",
518                         operation);
519                 return -1;
520         }
521
522         cb = operations[recording->state][operation];
523
524         if (!cb) {
525                 if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) {
526                         /* So we can be specific in our error message. */
527                         return STASIS_APP_RECORDING_OPER_NOT_RECORDING;
528                 } else {
529                         /* And, really, all operations should be valid during
530                          * recording */
531                         ast_log(LOG_ERROR,
532                                 "Unhandled operation during recording: %d\n",
533                                 operation);
534                         return STASIS_APP_RECORDING_OPER_FAILED;
535                 }
536         }
537
538         return cb(recording) ?
539                 STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK;
540 }
541
542 static int load_module(void)
543 {
544         int r;
545
546         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
547         if (r != 0) {
548                 return AST_MODULE_LOAD_FAILURE;
549         }
550
551         recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
552                 recording_cmp);
553         if (!recordings) {
554                 return AST_MODULE_LOAD_FAILURE;
555         }
556         return AST_MODULE_LOAD_SUCCESS;
557 }
558
559 static int unload_module(void)
560 {
561         ao2_cleanup(recordings);
562         recordings = NULL;
563         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
564         return 0;
565 }
566
567 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application recording support",
568         .load = load_module,
569         .unload = unload_module,
570         .nonoptreq = "res_stasis");