d9e9599cd4441c8ddd0f414300893566d5fe2235
[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 /*! Container of all current recordings */
53 static struct ao2_container *recordings;
54
55 struct stasis_app_recording {
56         /*! Recording options. */
57         struct stasis_app_recording_options *options;
58         /*! Absolute path (minus extension) of the recording */
59         char *absolute_name;
60         /*! Control object for the channel we're recording */
61         struct stasis_app_control *control;
62         /*! Current state of the recording. */
63         enum stasis_app_recording_state state;
64         /*! Duration calculations */
65         struct {
66                 /*! Total duration */
67                 int total;
68                 /*! Duration minus any silence */
69                 int energy_only;
70         } duration;
71         /*! Indicates whether the recording is currently muted */
72         int muted:1;
73 };
74
75 static struct ast_json *recording_to_json(struct stasis_message *message,
76         const struct stasis_message_sanitizer *sanitize)
77 {
78         struct ast_channel_blob *channel_blob = stasis_message_data(message);
79         struct ast_json *blob = channel_blob->blob;
80         const char *state =
81                 ast_json_string_get(ast_json_object_get(blob, "state"));
82         const char *type;
83
84         if (!strcmp(state, "recording")) {
85                 type = "RecordingStarted";
86         } else if (!strcmp(state, "done") || !strcasecmp(state, "canceled")) {
87                 type = "RecordingFinished";
88         } else if (!strcmp(state, "failed")) {
89                 type = "RecordingFailed";
90         } else {
91                 return NULL;
92         }
93
94         return ast_json_pack("{s: s, s: O}",
95                 "type", type,
96                 "recording", blob);
97 }
98
99 STASIS_MESSAGE_TYPE_DEFN(stasis_app_recording_snapshot_type,
100         .to_json = recording_to_json,
101 );
102
103 static int recording_hash(const void *obj, int flags)
104 {
105         const struct stasis_app_recording *recording = obj;
106         const char *id = flags & OBJ_KEY ? obj : recording->options->name;
107         return ast_str_hash(id);
108 }
109
110 static int recording_cmp(void *obj, void *arg, int flags)
111 {
112         struct stasis_app_recording *lhs = obj;
113         struct stasis_app_recording *rhs = arg;
114         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->options->name;
115
116         if (strcmp(lhs->options->name, rhs_id) == 0) {
117                 return CMP_MATCH | CMP_STOP;
118         } else {
119                 return 0;
120         }
121 }
122
123 static const char *state_to_string(enum stasis_app_recording_state state)
124 {
125         switch (state) {
126         case STASIS_APP_RECORDING_STATE_QUEUED:
127                 return "queued";
128         case STASIS_APP_RECORDING_STATE_RECORDING:
129                 return "recording";
130         case STASIS_APP_RECORDING_STATE_PAUSED:
131                 return "paused";
132         case STASIS_APP_RECORDING_STATE_COMPLETE:
133                 return "done";
134         case STASIS_APP_RECORDING_STATE_FAILED:
135                 return "failed";
136         case STASIS_APP_RECORDING_STATE_CANCELED:
137                 return "canceled";
138         case STASIS_APP_RECORDING_STATE_MAX:
139                 return "?";
140         }
141
142         return "?";
143 }
144
145 static void recording_options_dtor(void *obj)
146 {
147         struct stasis_app_recording_options *options = obj;
148
149         ast_string_field_free_memory(options);
150 }
151
152 struct stasis_app_recording_options *stasis_app_recording_options_create(
153         const char *name, const char *format)
154 {
155         RAII_VAR(struct stasis_app_recording_options *, options, NULL,
156                 ao2_cleanup);
157
158         options = ao2_alloc(sizeof(*options), recording_options_dtor);
159
160         if (!options || ast_string_field_init(options, 128)) {
161                 return NULL;
162         }
163         ast_string_field_set(options, name, name);
164         ast_string_field_set(options, format, format);
165
166         ao2_ref(options, +1);
167         return options;
168 }
169
170 char stasis_app_recording_termination_parse(const char *str)
171 {
172         if (ast_strlen_zero(str)) {
173                 return STASIS_APP_RECORDING_TERMINATE_NONE;
174         }
175
176         if (strcasecmp(str, "none") == 0) {
177                 return STASIS_APP_RECORDING_TERMINATE_NONE;
178         }
179
180         if (strcasecmp(str, "any") == 0) {
181                 return STASIS_APP_RECORDING_TERMINATE_ANY;
182         }
183
184         if (strcasecmp(str, "#") == 0) {
185                 return '#';
186         }
187
188         if (strcasecmp(str, "*") == 0) {
189                 return '*';
190         }
191
192         return STASIS_APP_RECORDING_TERMINATE_INVALID;
193 }
194
195 enum ast_record_if_exists stasis_app_recording_if_exists_parse(
196         const char *str)
197 {
198         if (ast_strlen_zero(str)) {
199                 /* Default value */
200                 return AST_RECORD_IF_EXISTS_FAIL;
201         }
202
203         if (strcasecmp(str, "fail") == 0) {
204                 return AST_RECORD_IF_EXISTS_FAIL;
205         }
206
207         if (strcasecmp(str, "overwrite") == 0) {
208                 return AST_RECORD_IF_EXISTS_OVERWRITE;
209         }
210
211         if (strcasecmp(str, "append") == 0) {
212                 return AST_RECORD_IF_EXISTS_APPEND;
213         }
214
215         return -1;
216 }
217
218 static void recording_publish(struct stasis_app_recording *recording, const char *cause)
219 {
220         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
221         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
222
223         ast_assert(recording != NULL);
224
225         json = stasis_app_recording_to_json(recording);
226         if (json == NULL) {
227                 return;
228         }
229
230         if (!ast_strlen_zero(cause)) {
231                 struct ast_json *failure_cause = ast_json_string_create(cause);
232
233                 if (!failure_cause) {
234                         return;
235                 }
236
237                 if (ast_json_object_set(json, "cause", failure_cause)) {
238                         return;
239                 }
240         }
241
242         message = ast_channel_blob_create_from_cache(
243                 stasis_app_control_get_channel_id(recording->control),
244                 stasis_app_recording_snapshot_type(), json);
245         if (message == NULL) {
246                 return;
247         }
248
249         stasis_app_control_publish(recording->control, message);
250 }
251
252
253 static void recording_set_state(struct stasis_app_recording *recording,
254                                 enum stasis_app_recording_state state,
255                                 const char *cause)
256 {
257         SCOPED_AO2LOCK(lock, recording);
258         recording->state = state;
259         recording_publish(recording, cause);
260 }
261
262 static enum stasis_app_control_channel_result check_rule_recording(
263         const struct stasis_app_control *control)
264 {
265         return STASIS_APP_CHANNEL_RECORDING;
266 }
267
268 struct stasis_app_control_rule rule_recording = {
269         .check_rule = check_rule_recording
270 };
271
272 static void recording_fail(struct stasis_app_control *control,
273                            struct stasis_app_recording *recording,
274                            const char *cause)
275 {
276         stasis_app_control_unregister_add_rule(control, &rule_recording);
277
278         recording_set_state(
279                 recording, STASIS_APP_RECORDING_STATE_FAILED, cause);
280 }
281
282 static void recording_cleanup(struct stasis_app_recording *recording)
283 {
284         ao2_unlink_flags(recordings, recording,
285                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
286 }
287
288 static int record_file(struct stasis_app_control *control,
289         struct ast_channel *chan, void *data)
290 {
291         RAII_VAR(struct stasis_app_recording *, recording,
292                 NULL, recording_cleanup);
293         char *acceptdtmf;
294         int res;
295
296         recording = data;
297         ast_assert(recording != NULL);
298
299         if (stasis_app_get_bridge(control)) {
300                 ast_log(LOG_ERROR, "Cannot record channel while in bridge\n");
301                 recording_fail(control, recording, "Cannot record channel while in bridge");
302                 return -1;
303         }
304
305         switch (recording->options->terminate_on) {
306         case STASIS_APP_RECORDING_TERMINATE_NONE:
307         case STASIS_APP_RECORDING_TERMINATE_INVALID:
308                 acceptdtmf = "";
309                 break;
310         case STASIS_APP_RECORDING_TERMINATE_ANY:
311                 acceptdtmf = "#*0123456789abcd";
312                 break;
313         default:
314                 acceptdtmf = ast_alloca(2);
315                 acceptdtmf[0] = recording->options->terminate_on;
316                 acceptdtmf[1] = '\0';
317         }
318
319         res = ast_auto_answer(chan);
320         if (res != 0) {
321                 ast_debug(3, "%s: Failed to answer\n",
322                         ast_channel_uniqueid(chan));
323                 recording_fail(control, recording, "Failed to answer channel");
324                 return -1;
325         }
326
327         recording_set_state(
328                 recording, STASIS_APP_RECORDING_STATE_RECORDING, NULL);
329         ast_play_and_record_full(chan,
330                 NULL, /* playfile */
331                 recording->absolute_name,
332                 recording->options->max_duration_seconds,
333                 recording->options->format,
334                 &recording->duration.total,
335                 recording->options->max_silence_seconds ? &recording->duration.energy_only : NULL,
336                 recording->options->beep,
337                 -1, /* silencethreshold */
338                 recording->options->max_silence_seconds * 1000,
339                 NULL, /* path */
340                 acceptdtmf,
341                 NULL, /* canceldtmf */
342                 1, /* skip_confirmation_sound */
343                 recording->options->if_exists);
344
345         ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
346
347         recording_set_state(
348                 recording, STASIS_APP_RECORDING_STATE_COMPLETE, NULL);
349
350         stasis_app_control_unregister_add_rule(control, &rule_recording);
351
352         return 0;
353 }
354
355 static void recording_dtor(void *obj)
356 {
357         struct stasis_app_recording *recording = obj;
358
359         ast_free(recording->absolute_name);
360         ao2_cleanup(recording->options);
361 }
362
363 struct stasis_app_recording *stasis_app_control_record(
364         struct stasis_app_control *control,
365         struct stasis_app_recording_options *options)
366 {
367         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
368         char *last_slash;
369
370         errno = 0;
371
372         if (options == NULL ||
373                 ast_strlen_zero(options->name) ||
374                 ast_strlen_zero(options->format) ||
375                 options->max_silence_seconds < 0 ||
376                 options->max_duration_seconds < 0) {
377                 errno = EINVAL;
378                 return NULL;
379         }
380
381         ast_debug(3, "%s: Sending record(%s.%s) command\n",
382                 stasis_app_control_get_channel_id(control), options->name,
383                 options->format);
384
385         recording = ao2_alloc(sizeof(*recording), recording_dtor);
386         if (!recording) {
387                 errno = ENOMEM;
388                 return NULL;
389         }
390         recording->duration.total = -1;
391         recording->duration.energy_only = -1;
392
393         ast_asprintf(&recording->absolute_name, "%s/%s",
394                 ast_config_AST_RECORDING_DIR, options->name);
395
396         if (recording->absolute_name == NULL) {
397                 errno = ENOMEM;
398                 return NULL;
399         }
400
401         if ((last_slash = strrchr(recording->absolute_name, '/'))) {
402                 *last_slash = '\0';
403                 if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
404                                 recording->absolute_name, 0777) != 0) {
405                         /* errno set by ast_mkdir */
406                         return NULL;
407                 }
408                 *last_slash = '/';
409         }
410
411         ao2_ref(options, +1);
412         recording->options = options;
413         recording->control = control;
414         recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
415
416         if ((recording->options->if_exists == AST_RECORD_IF_EXISTS_FAIL) &&
417                         (ast_fileexists(recording->absolute_name, NULL, NULL))) {
418                 ast_log(LOG_WARNING, "Recording file '%s' already exists and ifExists option is failure.\n",
419                         recording->absolute_name);
420                 errno = EEXIST;
421                 return NULL;
422         }
423
424         {
425                 RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
426                         ao2_cleanup);
427
428                 SCOPED_AO2LOCK(lock, recordings);
429
430                 old_recording = ao2_find(recordings, options->name,
431                         OBJ_KEY | OBJ_NOLOCK);
432                 if (old_recording) {
433                         ast_log(LOG_WARNING,
434                                 "Recording %s already in progress\n",
435                                 recording->options->name);
436                         errno = EEXIST;
437                         return NULL;
438                 }
439                 ao2_link(recordings, recording);
440         }
441
442         stasis_app_control_register_add_rule(control, &rule_recording);
443
444         /* A ref is kept in the recordings container; no need to bump */
445         stasis_app_send_command_async(control, record_file, recording);
446
447         /* Although this should be bumped for the caller */
448         ao2_ref(recording, +1);
449         return recording;
450 }
451
452 enum stasis_app_recording_state stasis_app_recording_get_state(
453         struct stasis_app_recording *recording)
454 {
455         return recording->state;
456 }
457
458 const char *stasis_app_recording_get_name(
459         struct stasis_app_recording *recording)
460 {
461         return recording->options->name;
462 }
463
464 struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
465 {
466         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
467
468         recording = ao2_find(recordings, name, OBJ_KEY);
469         if (recording == NULL) {
470                 return NULL;
471         }
472
473         ao2_ref(recording, +1);
474         return recording;
475 }
476
477 struct ast_json *stasis_app_recording_to_json(
478         const struct stasis_app_recording *recording)
479 {
480         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
481
482         if (recording == NULL) {
483                 return NULL;
484         }
485
486         json = ast_json_pack("{s: s, s: s, s: s, s: s}",
487                 "name", recording->options->name,
488                 "format", recording->options->format,
489                 "state", state_to_string(recording->state),
490                 "target_uri", recording->options->target);
491         if (json && recording->duration.total > -1) {
492                 ast_json_object_set(json, "duration",
493                         ast_json_integer_create(recording->duration.total));
494         }
495         if (json && recording->duration.energy_only > -1) {
496                 ast_json_object_set(json, "talking_duration",
497                         ast_json_integer_create(recording->duration.energy_only));
498                 ast_json_object_set(json, "silence_duration",
499                         ast_json_integer_create(recording->duration.total - recording->duration.energy_only));
500         }
501
502         return ast_json_ref(json);
503 }
504
505 typedef int (*recording_operation_cb)(struct stasis_app_recording *recording);
506
507 static int recording_noop(struct stasis_app_recording *recording)
508 {
509         return 0;
510 }
511
512 static int recording_disregard(struct stasis_app_recording *recording)
513 {
514         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
515         return 0;
516 }
517
518 static int recording_cancel(struct stasis_app_recording *recording)
519 {
520         int res = 0;
521         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
522         res |= stasis_app_control_queue_control(recording->control,
523                 AST_CONTROL_RECORD_CANCEL);
524         res |= ast_filedelete(recording->absolute_name, NULL);
525         return res;
526 }
527
528 static int recording_stop(struct stasis_app_recording *recording)
529 {
530         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
531         return stasis_app_control_queue_control(recording->control,
532                 AST_CONTROL_RECORD_STOP);
533 }
534
535 static int recording_pause(struct stasis_app_recording *recording)
536 {
537         recording->state = STASIS_APP_RECORDING_STATE_PAUSED;
538         return stasis_app_control_queue_control(recording->control,
539                 AST_CONTROL_RECORD_SUSPEND);
540 }
541
542 static int recording_unpause(struct stasis_app_recording *recording)
543 {
544         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
545         return stasis_app_control_queue_control(recording->control,
546                 AST_CONTROL_RECORD_SUSPEND);
547 }
548
549 static int recording_mute(struct stasis_app_recording *recording)
550 {
551         if (recording->muted) {
552                 /* already muted */
553                 return 0;
554         }
555
556         recording->muted = 1;
557         return stasis_app_control_queue_control(recording->control,
558                 AST_CONTROL_RECORD_MUTE);
559 }
560
561 static int recording_unmute(struct stasis_app_recording *recording)
562 {
563         if (!recording->muted) {
564                 /* already unmuted */
565                 return 0;
566         }
567
568         return stasis_app_control_queue_control(recording->control,
569                 AST_CONTROL_RECORD_MUTE);
570 }
571
572 recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = {
573         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard,
574         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard,
575         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
576         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop,
577         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause,
578         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop,
579         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute,
580         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
581         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
582         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop,
583         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop,
584         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause,
585         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute,
586         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
587 };
588
589 enum stasis_app_recording_oper_results stasis_app_recording_operation(
590         struct stasis_app_recording *recording,
591         enum stasis_app_recording_media_operation operation)
592 {
593         recording_operation_cb cb;
594         SCOPED_AO2LOCK(lock, recording);
595
596         if (recording->state < 0 || recording->state >= STASIS_APP_RECORDING_STATE_MAX) {
597                 ast_log(LOG_WARNING, "Invalid recording state %u\n",
598                         recording->state);
599                 return -1;
600         }
601
602         if (operation < 0 || operation >= STASIS_APP_RECORDING_OPER_MAX) {
603                 ast_log(LOG_WARNING, "Invalid recording operation %u\n",
604                         operation);
605                 return -1;
606         }
607
608         cb = operations[recording->state][operation];
609
610         if (!cb) {
611                 if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) {
612                         /* So we can be specific in our error message. */
613                         return STASIS_APP_RECORDING_OPER_NOT_RECORDING;
614                 } else {
615                         /* And, really, all operations should be valid during
616                          * recording */
617                         ast_log(LOG_ERROR,
618                                 "Unhandled operation during recording: %u\n",
619                                 operation);
620                         return STASIS_APP_RECORDING_OPER_FAILED;
621                 }
622         }
623
624         return cb(recording) ?
625                 STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK;
626 }
627
628 static int load_module(void)
629 {
630         int r;
631
632         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
633         if (r != 0) {
634                 return AST_MODULE_LOAD_FAILURE;
635         }
636
637         recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
638                 recording_cmp);
639         if (!recordings) {
640                 return AST_MODULE_LOAD_FAILURE;
641         }
642         return AST_MODULE_LOAD_SUCCESS;
643 }
644
645 static int unload_module(void)
646 {
647         ao2_cleanup(recordings);
648         recordings = NULL;
649         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
650         return 0;
651 }
652
653 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis application recording support",
654         .support_level = AST_MODULE_SUPPORT_CORE,
655         .load = load_module,
656         .unload = unload_module,
657         .nonoptreq = "res_stasis",
658         .load_pri = AST_MODPRI_APP_DEPEND);