Oh menuconfig, why do you hate margins?
[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 playing back to */
63         struct stasis_app_control *control;
64
65         /*! Current state of the recording. */
66         enum stasis_app_recording_state state;
67 };
68
69 static int recording_hash(const void *obj, int flags)
70 {
71         const struct stasis_app_recording *recording = obj;
72         const char *id = flags & OBJ_KEY ? obj : recording->options->name;
73         return ast_str_hash(id);
74 }
75
76 static int recording_cmp(void *obj, void *arg, int flags)
77 {
78         struct stasis_app_recording *lhs = obj;
79         struct stasis_app_recording *rhs = arg;
80         const char *rhs_id = flags & OBJ_KEY ? arg : rhs->options->name;
81
82         if (strcmp(lhs->options->name, rhs_id) == 0) {
83                 return CMP_MATCH | CMP_STOP;
84         } else {
85                 return 0;
86         }
87 }
88
89 static const char *state_to_string(enum stasis_app_recording_state state)
90 {
91         switch (state) {
92         case STASIS_APP_RECORDING_STATE_QUEUED:
93                 return "queued";
94         case STASIS_APP_RECORDING_STATE_RECORDING:
95                 return "recording";
96         case STASIS_APP_RECORDING_STATE_PAUSED:
97                 return "paused";
98         case STASIS_APP_RECORDING_STATE_COMPLETE:
99                 return "done";
100         case STASIS_APP_RECORDING_STATE_FAILED:
101                 return "failed";
102         }
103
104         return "?";
105 }
106
107 static void recording_options_dtor(void *obj)
108 {
109         struct stasis_app_recording_options *options = obj;
110
111         ast_string_field_free_memory(options);
112 }
113
114 struct stasis_app_recording_options *stasis_app_recording_options_create(
115         const char *name, const char *format)
116 {
117         RAII_VAR(struct stasis_app_recording_options *, options, NULL,
118                 ao2_cleanup);
119
120         options = ao2_alloc(sizeof(*options), recording_options_dtor);
121
122         if (!options || ast_string_field_init(options, 128)) {
123                 return NULL;
124         }
125         ast_string_field_set(options, name, name);
126         ast_string_field_set(options, format, format);
127
128         ao2_ref(options, +1);
129         return options;
130 }
131
132 char stasis_app_recording_termination_parse(const char *str)
133 {
134         if (ast_strlen_zero(str)) {
135                 return STASIS_APP_RECORDING_TERMINATE_NONE;
136         }
137
138         if (strcasecmp(str, "none") == 0) {
139                 return STASIS_APP_RECORDING_TERMINATE_NONE;
140         }
141
142         if (strcasecmp(str, "any") == 0) {
143                 return STASIS_APP_RECORDING_TERMINATE_ANY;
144         }
145
146         if (strcasecmp(str, "#") == 0) {
147                 return '#';
148         }
149
150         if (strcasecmp(str, "*") == 0) {
151                 return '*';
152         }
153
154         return STASIS_APP_RECORDING_TERMINATE_INVALID;
155 }
156
157 enum ast_record_if_exists stasis_app_recording_if_exists_parse(
158         const char *str)
159 {
160         if (ast_strlen_zero(str)) {
161                 /* Default value */
162                 return AST_RECORD_IF_EXISTS_FAIL;
163         }
164
165         if (strcasecmp(str, "fail") == 0) {
166                 return AST_RECORD_IF_EXISTS_FAIL;
167         }
168
169         if (strcasecmp(str, "overwrite") == 0) {
170                 return AST_RECORD_IF_EXISTS_OVERWRITE;
171         }
172
173         if (strcasecmp(str, "append") == 0) {
174                 return AST_RECORD_IF_EXISTS_APPEND;
175         }
176
177         return -1;
178 }
179
180 static void recording_publish(struct stasis_app_recording *recording)
181 {
182         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
183         RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
184         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
185
186         ast_assert(recording != NULL);
187
188         json = stasis_app_recording_to_json(recording);
189         if (json == NULL) {
190                 return;
191         }
192
193         message = ast_channel_blob_create_from_cache(
194                 stasis_app_control_get_channel_id(recording->control),
195                 stasis_app_recording_snapshot_type(), json);
196         if (message == NULL) {
197                 return;
198         }
199
200         stasis_app_control_publish(recording->control, message);
201 }
202
203 static void recording_fail(struct stasis_app_recording *recording)
204 {
205         SCOPED_AO2LOCK(lock, recording);
206         recording->state = STASIS_APP_RECORDING_STATE_FAILED;
207         recording_publish(recording);
208 }
209
210 static void recording_cleanup(struct stasis_app_recording *recording)
211 {
212         ao2_unlink_flags(recordings, recording,
213                 OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
214 }
215
216 static void *record_file(struct stasis_app_control *control,
217         struct ast_channel *chan, void *data)
218 {
219         RAII_VAR(struct stasis_app_recording *, recording,
220                 NULL, recording_cleanup);
221         char *acceptdtmf;
222         int res;
223         int duration = 0;
224
225         recording = data;
226         ast_assert(recording != NULL);
227
228         ao2_lock(recording);
229         recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
230         recording_publish(recording);
231         ao2_unlock(recording);
232
233         switch (recording->options->terminate_on) {
234         case STASIS_APP_RECORDING_TERMINATE_NONE:
235         case STASIS_APP_RECORDING_TERMINATE_INVALID:
236                 acceptdtmf = "";
237                 break;
238         case STASIS_APP_RECORDING_TERMINATE_ANY:
239                 acceptdtmf = "#*0123456789abcd";
240                 break;
241         default:
242                 acceptdtmf = ast_alloca(2);
243                 acceptdtmf[0] = recording->options->terminate_on;
244                 acceptdtmf[1] = '\0';
245         }
246
247         res = ast_auto_answer(chan);
248         if (res != 0) {
249                 ast_debug(3, "%s: Failed to answer\n",
250                         ast_channel_uniqueid(chan));
251                 recording_fail(recording);
252                 return NULL;
253         }
254
255         ast_play_and_record_full(chan,
256                 recording->options->beep ? "beep" : NULL,
257                 recording->absolute_name,
258                 recording->options->max_duration_seconds,
259                 recording->options->format,
260                 &duration,
261                 NULL, /* sound_duration */
262                 -1, /* silencethreshold */
263                 recording->options->max_silence_seconds * 1000,
264                 NULL, /* path */
265                 acceptdtmf,
266                 NULL, /* canceldtmf */
267                 1, /* skip_confirmation_sound */
268                 recording->options->if_exists);
269
270         ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
271
272         ao2_lock(recording);
273         recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
274         recording_publish(recording);
275         ao2_unlock(recording);
276
277         return NULL;
278 }
279
280 static void recording_dtor(void *obj)
281 {
282         struct stasis_app_recording *recording = obj;
283
284         ao2_cleanup(recording->options);
285 }
286
287 struct stasis_app_recording *stasis_app_control_record(
288         struct stasis_app_control *control,
289         struct stasis_app_recording_options *options)
290 {
291         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
292         char *last_slash;
293
294         errno = 0;
295
296         if (options == NULL ||
297                 ast_strlen_zero(options->name) ||
298                 ast_strlen_zero(options->format) ||
299                 options->max_silence_seconds < 0 ||
300                 options->max_duration_seconds < 0) {
301                 errno = EINVAL;
302                 return NULL;
303         }
304
305         ast_debug(3, "%s: Sending record(%s.%s) command\n",
306                 stasis_app_control_get_channel_id(control), options->name,
307                 options->format);
308
309         recording = ao2_alloc(sizeof(*recording), recording_dtor);
310         if (!recording) {
311                 errno = ENOMEM;
312                 return NULL;
313         }
314
315         ast_asprintf(&recording->absolute_name, "%s/%s",
316                 ast_config_AST_RECORDING_DIR, options->name);
317
318         if (recording->absolute_name == NULL) {
319                 errno = ENOMEM;
320                 return NULL;
321         }
322
323         if ((last_slash = strrchr(recording->absolute_name, '/'))) {
324                 *last_slash = '\0';
325                 if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
326                                 recording->absolute_name, 0777) != 0) {
327                         /* errno set by ast_mkdir */
328                         return NULL;
329                 }
330                 *last_slash = '/';
331         }
332
333         ao2_ref(options, +1);
334         recording->options = options;
335         recording->control = control;
336         recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
337
338         {
339                 RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
340                         ao2_cleanup);
341
342                 SCOPED_AO2LOCK(lock, recordings);
343
344                 old_recording = ao2_find(recordings, options->name,
345                         OBJ_KEY | OBJ_NOLOCK);
346                 if (old_recording) {
347                         ast_log(LOG_WARNING,
348                                 "Recording %s already in progress\n",
349                                 recording->options->name);
350                         errno = EEXIST;
351                         return NULL;
352                 }
353                 ao2_link(recordings, recording);
354         }
355
356         /* A ref is kept in the recordings container; no need to bump */
357         stasis_app_send_command_async(control, record_file, recording);
358
359         /* Although this should be bumped for the caller */
360         ao2_ref(recording, +1);
361         return recording;
362 }
363
364 enum stasis_app_recording_state stasis_app_recording_get_state(
365         struct stasis_app_recording *recording)
366 {
367         return recording->state;
368 }
369
370 const char *stasis_app_recording_get_name(
371         struct stasis_app_recording *recording)
372 {
373         return recording->options->name;
374 }
375
376 struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
377 {
378         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
379
380         recording = ao2_find(recordings, name, OBJ_KEY);
381         if (recording == NULL) {
382                 return NULL;
383         }
384
385         ao2_ref(recording, +1);
386         return recording;
387 }
388
389 struct ast_json *stasis_app_recording_to_json(
390         const struct stasis_app_recording *recording)
391 {
392         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
393
394         if (recording == NULL) {
395                 return NULL;
396         }
397
398         json = ast_json_pack("{s: s, s: s, s: s}",
399                 "name", recording->options->name,
400                 "format", recording->options->format,
401                 "state", state_to_string(recording->state));
402
403         return ast_json_ref(json);
404 }
405
406 enum stasis_app_recording_oper_results stasis_app_recording_operation(
407         struct stasis_app_recording *recording,
408         enum stasis_app_recording_media_operation operation)
409 {
410         ast_assert(0); // TODO
411         return STASIS_APP_RECORDING_OPER_FAILED;
412 }
413
414 static int load_module(void)
415 {
416         int r;
417
418         r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
419         if (r != 0) {
420                 return AST_MODULE_LOAD_FAILURE;
421         }
422
423         recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
424                 recording_cmp);
425         if (!recordings) {
426                 return AST_MODULE_LOAD_FAILURE;
427         }
428         return AST_MODULE_LOAD_SUCCESS;
429 }
430
431 static int unload_module(void)
432 {
433         ao2_cleanup(recordings);
434         recordings = NULL;
435         STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
436         return 0;
437 }
438
439 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application recording support",
440         .load = load_module,
441         .unload = unload_module,
442         .nonoptreq = "res_stasis");