575ccae1ea9ac695bce055a7ed8fd285cf5068e6
[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         ao2_lock(recording);
235         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
236         recording_publish(recording);
237         ao2_unlock(recording);
238
239         switch (recording->options->terminate_on) {
240         case STASIS_APP_RECORDING_TERMINATE_NONE:
241         case STASIS_APP_RECORDING_TERMINATE_INVALID:
242                 acceptdtmf = "";
243                 break;
244         case STASIS_APP_RECORDING_TERMINATE_ANY:
245                 acceptdtmf = "#*0123456789abcd";
246                 break;
247         default:
248                 acceptdtmf = ast_alloca(2);
249                 acceptdtmf[0] = recording->options->terminate_on;
250                 acceptdtmf[1] = '\0';
251         }
252
253         res = ast_auto_answer(chan);
254         if (res != 0) {
255                 ast_debug(3, "%s: Failed to answer\n",
256                         ast_channel_uniqueid(chan));
257                 recording_fail(recording);
258                 return NULL;
259         }
260
261         ast_play_and_record_full(chan,
262                 NULL, /* playfile */
263                 recording->absolute_name,
264                 recording->options->max_duration_seconds,
265                 recording->options->format,
266                 &duration,
267                 NULL, /* sound_duration */
268                 recording->options->beep,
269                 -1, /* silencethreshold */
270                 recording->options->max_silence_seconds * 1000,
271                 NULL, /* path */
272                 acceptdtmf,
273                 NULL, /* canceldtmf */
274                 1, /* skip_confirmation_sound */
275                 recording->options->if_exists);
276
277         ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
278
279         ao2_lock(recording);
280         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
281         recording_publish(recording);
282         ao2_unlock(recording);
283
284         return NULL;
285 }
286
287 static void recording_dtor(void *obj)
288 {
289         struct stasis_app_recording *recording = obj;
290
291         ao2_cleanup(recording->options);
292 }
293
294 struct stasis_app_recording *stasis_app_control_record(
295         struct stasis_app_control *control,
296         struct stasis_app_recording_options *options)
297 {
298         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
299         char *last_slash;
300
301         errno = 0;
302
303         if (options == NULL ||
304                 ast_strlen_zero(options->name) ||
305                 ast_strlen_zero(options->format) ||
306                 options->max_silence_seconds < 0 ||
307                 options->max_duration_seconds < 0) {
308                 errno = EINVAL;
309                 return NULL;
310         }
311
312         ast_debug(3, "%s: Sending record(%s.%s) command\n",
313                 stasis_app_control_get_channel_id(control), options->name,
314                 options->format);
315
316         recording = ao2_alloc(sizeof(*recording), recording_dtor);
317         if (!recording) {
318                 errno = ENOMEM;
319                 return NULL;
320         }
321
322         ast_asprintf(&recording->absolute_name, "%s/%s",
323                 ast_config_AST_RECORDING_DIR, options->name);
324
325         if (recording->absolute_name == NULL) {
326                 errno = ENOMEM;
327                 return NULL;
328         }
329
330         if ((last_slash = strrchr(recording->absolute_name, '/'))) {
331                 *last_slash = '\0';
332                 if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
333                                 recording->absolute_name, 0777) != 0) {
334                         /* errno set by ast_mkdir */
335                         return NULL;
336                 }
337                 *last_slash = '/';
338         }
339
340         ao2_ref(options, +1);
341         recording->options = options;
342         recording->control = control;
343         recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
344
345         {
346                 RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
347                         ao2_cleanup);
348
349                 SCOPED_AO2LOCK(lock, recordings);
350
351                 old_recording = ao2_find(recordings, options->name,
352                         OBJ_KEY | OBJ_NOLOCK);
353                 if (old_recording) {
354                         ast_log(LOG_WARNING,
355                                 "Recording %s already in progress\n",
356                                 recording->options->name);
357                         errno = EEXIST;
358                         return NULL;
359                 }
360                 ao2_link(recordings, recording);
361         }
362
363         /* A ref is kept in the recordings container; no need to bump */
364         stasis_app_send_command_async(control, record_file, recording);
365
366         /* Although this should be bumped for the caller */
367         ao2_ref(recording, +1);
368         return recording;
369 }
370
371 enum stasis_app_recording_state stasis_app_recording_get_state(
372         struct stasis_app_recording *recording)
373 {
374         return recording->state;
375 }
376
377 const char *stasis_app_recording_get_name(
378         struct stasis_app_recording *recording)
379 {
380         return recording->options->name;
381 }
382
383 struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
384 {
385         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
386
387         recording = ao2_find(recordings, name, OBJ_KEY);
388         if (recording == NULL) {
389                 return NULL;
390         }
391
392         ao2_ref(recording, +1);
393         return recording;
394 }
395
396 struct ast_json *stasis_app_recording_to_json(
397         const struct stasis_app_recording *recording)
398 {
399         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
400
401         if (recording == NULL) {
402                 return NULL;
403         }
404
405         json = ast_json_pack("{s: s, s: s, s: s}",
406                 "name", recording->options->name,
407                 "format", recording->options->format,
408                 "state", state_to_string(recording->state));
409
410         return ast_json_ref(json);
411 }
412
413 typedef int (*recording_operation_cb)(struct stasis_app_recording *recording);
414
415 static int recording_noop(struct stasis_app_recording *recording)
416 {
417         return 0;
418 }
419
420 static int recording_disregard(struct stasis_app_recording *recording)
421 {
422         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
423         return 0;
424 }
425
426 static int recording_cancel(struct stasis_app_recording *recording)
427 {
428         int res = 0;
429         recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
430         res |= stasis_app_control_queue_control(recording->control,
431                 AST_CONTROL_RECORD_CANCEL);
432         res |= ast_filedelete(recording->absolute_name, NULL);
433         return res;
434 }
435
436 static int recording_stop(struct stasis_app_recording *recording)
437 {
438         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
439         return stasis_app_control_queue_control(recording->control,
440                 AST_CONTROL_RECORD_STOP);
441 }
442
443 static int recording_pause(struct stasis_app_recording *recording)
444 {
445         recording->state = STASIS_APP_RECORDING_STATE_PAUSED;
446         return stasis_app_control_queue_control(recording->control,
447                 AST_CONTROL_RECORD_SUSPEND);
448 }
449
450 static int recording_unpause(struct stasis_app_recording *recording)
451 {
452         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
453         return stasis_app_control_queue_control(recording->control,
454                 AST_CONTROL_RECORD_SUSPEND);
455 }
456
457 static int recording_mute(struct stasis_app_recording *recording)
458 {
459         if (recording->muted) {
460                 /* already muted */
461                 return 0;
462         }
463
464         recording->muted = 1;
465         return stasis_app_control_queue_control(recording->control,
466                 AST_CONTROL_RECORD_MUTE);
467 }
468
469 static int recording_unmute(struct stasis_app_recording *recording)
470 {
471         if (!recording->muted) {
472                 /* already unmuted */
473                 return 0;
474         }
475
476         return stasis_app_control_queue_control(recording->control,
477                 AST_CONTROL_RECORD_MUTE);
478 }
479
480 recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = {
481         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard,
482         [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard,
483         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
484         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop,
485         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause,
486         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop,
487         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute,
488         [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
489         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
490         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop,
491         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop,
492         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause,
493         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute,
494         [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
495 };
496
497 enum stasis_app_recording_oper_results stasis_app_recording_operation(
498         struct stasis_app_recording *recording,
499         enum stasis_app_recording_media_operation operation)
500 {
501         recording_operation_cb cb;
502         SCOPED_AO2LOCK(lock, recording);
503
504         if (recording->state < 0 || recording->state >= STASIS_APP_RECORDING_STATE_MAX) {
505                 ast_log(LOG_WARNING, "Invalid recording state %d\n",
506                         recording->state);
507                 return -1;
508         }
509
510         if (operation < 0 || operation >= STASIS_APP_RECORDING_OPER_MAX) {
511                 ast_log(LOG_WARNING, "Invalid recording operation %d\n",
512                         operation);
513                 return -1;
514         }
515
516         cb = operations[recording->state][operation];
517
518         if (!cb) {
519                 if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) {
520                         /* So we can be specific in our error message. */
521                         return STASIS_APP_RECORDING_OPER_NOT_RECORDING;
522                 } else {
523                         /* And, really, all operations should be valid during
524                          * recording */
525                         ast_log(LOG_ERROR,
526                                 "Unhandled operation during recording: %d\n",
527                                 operation);
528                         return STASIS_APP_RECORDING_OPER_FAILED;
529                 }
530         }
531
532         return cb(recording) ?
533                 STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK;
534 }
535
536 static int load_module(void)
537 {
538         int r;
539
540         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
541         if (r != 0) {
542                 return AST_MODULE_LOAD_FAILURE;
543         }
544
545         recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
546                 recording_cmp);
547         if (!recordings) {
548                 return AST_MODULE_LOAD_FAILURE;
549         }
550         return AST_MODULE_LOAD_SUCCESS;
551 }
552
553 static int unload_module(void)
554 {
555         ao2_cleanup(recordings);
556         recordings = NULL;
557         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
558         return 0;
559 }
560
561 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application recording support",
562         .load = load_module,
563         .unload = unload_module,
564         .nonoptreq = "res_stasis");