Merged revisions 73316 via svnmerge from
[asterisk/asterisk.git] / res / res_monitor.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@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 PBX channel monitoring
22  *
23  * \author Mark Spencer <markster@digium.com>
24  */
25  
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <libgen.h>
37
38 #include "asterisk/lock.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/file.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/manager.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/monitor.h"
47 #include "asterisk/app.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/config.h"
50 #include "asterisk/options.h"
51
52 AST_MUTEX_DEFINE_STATIC(monitorlock);
53
54 #define LOCK_IF_NEEDED(lock, needed) do { \
55         if (needed) \
56                 ast_channel_lock(lock); \
57         } while(0)
58
59 #define UNLOCK_IF_NEEDED(lock, needed) do { \
60         if (needed) \
61                 ast_channel_unlock(lock); \
62         } while (0)
63
64 static unsigned long seq = 0;
65
66 static char *monitor_synopsis = "Monitor a channel";
67
68 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
69 "Used to start monitoring a channel. The channel's input and output\n"
70 "voice packets are logged to files until the channel hangs up or\n"
71 "monitoring is stopped by the StopMonitor application.\n"
72 "  file_format          optional, if not set, defaults to \"wav\"\n"
73 "  fname_base           if set, changes the filename used to the one specified.\n"
74 "  options:\n"
75 "    m   - when the recording ends mix the two leg files into one and\n"
76 "          delete the two leg files.  If the variable MONITOR_EXEC is set, the\n"
77 "          application referenced in it will be executed instead of\n"
78 "          soxmix and the raw leg files will NOT be deleted automatically.\n"
79 "          soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
80 "          and a target mixed file name which is the same as the leg file names\n"
81 "          only without the in/out designator.\n"
82 "          If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
83 "          additional arguements to MONITOR_EXEC\n"
84 "          Both MONITOR_EXEC and the Mix flag can be set from the\n"
85 "          administrator interface\n"
86 "\n"
87 "    b   - Don't begin recording unless a call is bridged to another channel\n"
88 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
89 "monitored, otherwise 0.\n"
90 ;
91
92 static char *stopmonitor_synopsis = "Stop monitoring a channel";
93
94 static char *stopmonitor_descrip = "StopMonitor\n"
95         "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
96
97 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
98
99 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
100         "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
101         "The argument is the new filename base to use for monitoring this channel.\n";
102
103 static char *pausemonitor_synopsis = "Pause monitoring of a channel";
104
105 static char *pausemonitor_descrip = "PauseMonitor\n"
106         "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
107
108 static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
109
110 static char *unpausemonitor_descrip = "UnpauseMonitor\n"
111         "Unpauses monitoring of a channel on which monitoring had\n"
112         "previously been paused with PauseMonitor.\n";
113
114 static int ast_monitor_set_state(struct ast_channel *chan, int state)
115 {
116         LOCK_IF_NEEDED(chan, 1);
117         if (!chan->monitor) {
118                 UNLOCK_IF_NEEDED(chan, 1);
119                 return -1;
120         }
121         chan->monitor->state = state;
122         UNLOCK_IF_NEEDED(chan, 1);
123         return 0;
124 }
125
126 /* Start monitoring a channel */
127 int ast_monitor_start(  struct ast_channel *chan, const char *format_spec,
128                 const char *fname_base, int need_lock)
129 {
130         int res = 0;
131
132         LOCK_IF_NEEDED(chan, need_lock);
133
134         if (!(chan->monitor)) {
135                 struct ast_channel_monitor *monitor;
136                 char *channel_name, *p;
137
138                 /* Create monitoring directory if needed */
139                 ast_mkdir(ast_config_AST_MONITOR_DIR, 0777);
140
141                 if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
142                         UNLOCK_IF_NEEDED(chan, need_lock);
143                         return -1;
144                 }
145
146                 /* Determine file names */
147                 if (!ast_strlen_zero(fname_base)) {
148                         int directory = strchr(fname_base, '/') ? 1 : 0;
149                         /* try creating the directory just in case it doesn't exist */
150                         if (directory) {
151                                 char *name = ast_strdupa(fname_base);
152                                 ast_mkdir(dirname(name), 0777);
153                         }
154                         snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
155                                                 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
156                         snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
157                                                 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
158                         ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
159                 } else {
160                         ast_mutex_lock(&monitorlock);
161                         snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
162                                                 ast_config_AST_MONITOR_DIR, seq);
163                         snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
164                                                 ast_config_AST_MONITOR_DIR, seq);
165                         seq++;
166                         ast_mutex_unlock(&monitorlock);
167
168                         channel_name = ast_strdupa(chan->name);
169                         while ((p = strchr(channel_name, '/'))) {
170                                 *p = '-';
171                         }
172                         snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
173                                          ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
174                         monitor->filename_changed = 1;
175                 }
176
177                 monitor->stop = ast_monitor_stop;
178
179                 /* Determine file format */
180                 if (!ast_strlen_zero(format_spec)) {
181                         monitor->format = ast_strdup(format_spec);
182                 } else {
183                         monitor->format = ast_strdup("wav");
184                 }
185                 
186                 /* open files */
187                 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
188                         ast_filedelete(monitor->read_filename, NULL);
189                 }
190                 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
191                                                 monitor->format, NULL,
192                                                 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
193                         ast_log(LOG_WARNING, "Could not create file %s\n",
194                                                 monitor->read_filename);
195                         ast_free(monitor);
196                         UNLOCK_IF_NEEDED(chan, need_lock);
197                         return -1;
198                 }
199                 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
200                         ast_filedelete(monitor->write_filename, NULL);
201                 }
202                 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
203                                                 monitor->format, NULL,
204                                                 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
205                         ast_log(LOG_WARNING, "Could not create file %s\n",
206                                                 monitor->write_filename);
207                         ast_closestream(monitor->read_stream);
208                         ast_free(monitor);
209                         UNLOCK_IF_NEEDED(chan, need_lock);
210                         return -1;
211                 }
212                 chan->monitor = monitor;
213                 ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
214                 /* so we know this call has been monitored in case we need to bill for it or something */
215                 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
216         } else {
217                 ast_debug(1,"Cannot start monitoring %s, already monitored\n", chan->name);
218                 res = -1;
219         }
220
221         UNLOCK_IF_NEEDED(chan, need_lock);
222
223         return res;
224 }
225
226 /*
227  * The file format extensions that Asterisk uses are not all the same as that
228  * which soxmix expects.  This function ensures that the format used as the
229  * extension on the filename is something soxmix will understand.
230  */
231 static const char *get_soxmix_format(const char *format)
232 {
233         const char *res = format;
234
235         if (!strcasecmp(format,"ulaw"))
236                 res = "ul";
237         if (!strcasecmp(format,"alaw"))
238                 res = "al";
239         
240         return res;
241 }
242
243 /* Stop monitoring a channel */
244 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
245 {
246         int delfiles = 0;
247
248         LOCK_IF_NEEDED(chan, need_lock);
249
250         if (chan->monitor) {
251                 char filename[ FILENAME_MAX ];
252
253                 if (chan->monitor->read_stream) {
254                         ast_closestream(chan->monitor->read_stream);
255                 }
256                 if (chan->monitor->write_stream) {
257                         ast_closestream(chan->monitor->write_stream);
258                 }
259
260                 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
261                         if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
262                                 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
263                                 if (ast_fileexists(filename, NULL, NULL) > 0) {
264                                         ast_filedelete(filename, NULL);
265                                 }
266                                 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
267                         } else {
268                                 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
269                         }
270
271                         if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
272                                 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
273                                 if (ast_fileexists(filename, NULL, NULL) > 0) {
274                                         ast_filedelete(filename, NULL);
275                                 }
276                                 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
277                         } else {
278                                 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
279                         }
280                 }
281
282                 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
283                         char tmp[1024];
284                         char tmp2[1024];
285                         const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
286                         char *name = chan->monitor->filename_base;
287                         int directory = strchr(name, '/') ? 1 : 0;
288                         char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
289                         const char *execute, *execute_args;
290
291                         /* Set the execute application */
292                         execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
293                         if (ast_strlen_zero(execute)) { 
294                                 execute = "nice -n 19 soxmix";
295                                 format = get_soxmix_format(format);
296                                 delfiles = 1;
297                         } 
298                         execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
299                         if (ast_strlen_zero(execute_args)) {
300                                 execute_args = "";
301                         }
302                         
303                         snprintf(tmp, sizeof(tmp), "%s \"%s/%s-in.%s\" \"%s/%s-out.%s\" \"%s/%s.%s\" %s &", execute, dir, name, format, dir, name, format, dir, name, format,execute_args);
304                         if (delfiles) {
305                                 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
306                                 ast_copy_string(tmp, tmp2, sizeof(tmp));
307                         }
308                         ast_debug(1,"monitor executing %s\n",tmp);
309                         if (ast_safe_system(tmp) == -1)
310                                 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
311                 }
312                 
313                 ast_free(chan->monitor->format);
314                 ast_free(chan->monitor);
315                 chan->monitor = NULL;
316         }
317
318         UNLOCK_IF_NEEDED(chan, need_lock);
319
320         return 0;
321 }
322
323
324 /* Pause monitoring of a channel */
325 int ast_monitor_pause(struct ast_channel *chan)
326 {
327         return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
328 }
329
330 /* Unpause monitoring of a channel */
331 int ast_monitor_unpause(struct ast_channel *chan)
332 {
333         return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
334 }
335
336 static int pause_monitor_exec(struct ast_channel *chan, void *data)
337 {
338         return ast_monitor_pause(chan);
339 }
340
341 static int unpause_monitor_exec(struct ast_channel *chan, void *data)
342 {
343         return ast_monitor_unpause(chan);
344 }
345
346 /* Change monitoring filename of a channel */
347 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
348 {
349         if (ast_strlen_zero(fname_base)) {
350                 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name);
351                 return -1;
352         }
353
354         LOCK_IF_NEEDED(chan, need_lock);
355
356         if (chan->monitor) {
357                 int directory = strchr(fname_base, '/') ? 1 : 0;
358                 /* try creating the directory just in case it doesn't exist */
359                 if (directory) {
360                         char *name = ast_strdupa(fname_base);
361                         ast_mkdir(dirname(name), 0777);
362                 }
363
364                 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
365                 chan->monitor->filename_changed = 1;
366         } else {
367                 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
368         }
369
370         UNLOCK_IF_NEEDED(chan, need_lock);
371
372         return 0;
373 }
374
375 static int start_monitor_exec(struct ast_channel *chan, void *data)
376 {
377         char *arg = NULL;
378         char *format = NULL;
379         char *fname_base = NULL;
380         char *options = NULL;
381         char *delay = NULL;
382         char *urlprefix = NULL;
383         char tmp[256];
384         int joinfiles = 0;
385         int waitforbridge = 0;
386         int res = 0;
387         
388         /* Parse arguments. */
389         if (!ast_strlen_zero((char*)data)) {
390                 arg = ast_strdupa((char*)data);
391                 format = arg;
392                 fname_base = strchr(arg, '|');
393                 if (fname_base) {
394                         *fname_base = 0;
395                         fname_base++;
396                         if ((options = strchr(fname_base, '|'))) {
397                                 *options = 0;
398                                 options++;
399                                 if (strchr(options, 'm'))
400                                         joinfiles = 1;
401                                 if (strchr(options, 'b'))
402                                         waitforbridge = 1;
403                         }
404                 }
405                 arg = strchr(format,':');
406                 if (arg) {
407                         *arg++ = 0;
408                         urlprefix = arg;
409                 }
410         }
411         if (urlprefix) {
412                 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
413                         ((strcmp(format,"gsm")) ? "wav" : "gsm"));
414                 if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
415                         return -1;
416                 ast_cdr_setuserfield(chan, tmp);
417         }
418         if (waitforbridge) {
419                 /* We must remove the "b" option if listed.  In principle none of
420                    the following could give NULL results, but we check just to
421                    be pedantic. Reconstructing with checks for 'm' option does not
422                    work if we end up adding more options than 'm' in the future. */
423                 delay = ast_strdupa(data);
424                 options = strrchr(delay, '|');
425                 if (options) {
426                         arg = strchr(options, 'b');
427                         if (arg) {
428                                 *arg = 'X';
429                                 pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
430                         }
431                 }
432                 return 0;
433         }
434
435         res = ast_monitor_start(chan, format, fname_base, 1);
436         if (res < 0)
437                 res = ast_monitor_change_fname(chan, fname_base, 1);
438         ast_monitor_setjoinfiles(chan, joinfiles);
439
440         return res;
441 }
442
443 static int stop_monitor_exec(struct ast_channel *chan, void *data)
444 {
445         return ast_monitor_stop(chan, 1);
446 }
447
448 static int change_monitor_exec(struct ast_channel *chan, void *data)
449 {
450         return ast_monitor_change_fname(chan, (const char*)data, 1);
451 }
452
453 static char start_monitor_action_help[] =
454 "Description: The 'Monitor' action may be used to record the audio on a\n"
455 "  specified channel.  The following parameters may be used to control\n"
456 "  this:\n"
457 "  Channel     - Required.  Used to specify the channel to record.\n"
458 "  File        - Optional.  Is the name of the file created in the\n"
459 "                monitor spool directory.  Defaults to the same name\n"
460 "                as the channel (with slashes replaced with dashes).\n"
461 "  Format      - Optional.  Is the audio recording format.  Defaults\n"
462 "                to \"wav\".\n"
463 "  Mix         - Optional.  Boolean parameter as to whether to mix\n"
464 "                the input and output channels together after the\n"
465 "                recording is finished.\n";
466
467 static int start_monitor_action(struct mansession *s, const struct message *m)
468 {
469         struct ast_channel *c = NULL;
470         const char *name = astman_get_header(m, "Channel");
471         const char *fname = astman_get_header(m, "File");
472         const char *format = astman_get_header(m, "Format");
473         const char *mix = astman_get_header(m, "Mix");
474         char *d;
475         
476         if (ast_strlen_zero(name)) {
477                 astman_send_error(s, m, "No channel specified");
478                 return 0;
479         }
480         c = ast_get_channel_by_name_locked(name);
481         if (!c) {
482                 astman_send_error(s, m, "No such channel");
483                 return 0;
484         }
485
486         if (ast_strlen_zero(fname)) {
487                 /* No filename base specified, default to channel name as per CLI */            
488                 if (!(fname = ast_strdup(c->name))) {
489                         astman_send_error(s, m, "Could not start monitoring channel");
490                         ast_channel_unlock(c);
491                         return 0;
492                 }
493                 /* Channels have the format technology/channel_name - have to replace that /  */
494                 if ((d = strchr(fname, '/'))) 
495                         *d = '-';
496         }
497         
498         if (ast_monitor_start(c, format, fname, 1)) {
499                 if (ast_monitor_change_fname(c, fname, 1)) {
500                         astman_send_error(s, m, "Could not start monitoring channel");
501                         ast_channel_unlock(c);
502                         return 0;
503                 }
504         }
505
506         if (ast_true(mix)) {
507                 ast_monitor_setjoinfiles(c, 1);
508         }
509
510         ast_channel_unlock(c);
511         astman_send_ack(s, m, "Started monitoring channel");
512         return 0;
513 }
514
515 static char stop_monitor_action_help[] =
516 "Description: The 'StopMonitor' action may be used to end a previously\n"
517 "  started 'Monitor' action.  The only parameter is 'Channel', the name\n"
518 "  of the channel monitored.\n";
519
520 static int stop_monitor_action(struct mansession *s, const struct message *m)
521 {
522         struct ast_channel *c = NULL;
523         const char *name = astman_get_header(m, "Channel");
524         int res;
525         if (ast_strlen_zero(name)) {
526                 astman_send_error(s, m, "No channel specified");
527                 return 0;
528         }
529         c = ast_get_channel_by_name_locked(name);
530         if (!c) {
531                 astman_send_error(s, m, "No such channel");
532                 return 0;
533         }
534         res = ast_monitor_stop(c, 1);
535         ast_channel_unlock(c);
536         if (res) {
537                 astman_send_error(s, m, "Could not stop monitoring channel");
538                 return 0;
539         }
540         astman_send_ack(s, m, "Stopped monitoring channel");
541         return 0;
542 }
543
544 static char change_monitor_action_help[] =
545 "Description: The 'ChangeMonitor' action may be used to change the file\n"
546 "  started by a previous 'Monitor' action.  The following parameters may\n"
547 "  be used to control this:\n"
548 "  Channel     - Required.  Used to specify the channel to record.\n"
549 "  File        - Required.  Is the new name of the file created in the\n"
550 "                monitor spool directory.\n";
551
552 static int change_monitor_action(struct mansession *s, const struct message *m)
553 {
554         struct ast_channel *c = NULL;
555         const char *name = astman_get_header(m, "Channel");
556         const char *fname = astman_get_header(m, "File");
557         if (ast_strlen_zero(name)) {
558                 astman_send_error(s, m, "No channel specified");
559                 return 0;
560         }
561         if (ast_strlen_zero(fname)) {
562                 astman_send_error(s, m, "No filename specified");
563                 return 0;
564         }
565         c = ast_get_channel_by_name_locked(name);
566         if (!c) {
567                 astman_send_error(s, m, "No such channel");
568                 return 0;
569         }
570         if (ast_monitor_change_fname(c, fname, 1)) {
571                 astman_send_error(s, m, "Could not change monitored filename of channel");
572                 ast_channel_unlock(c);
573                 return 0;
574         }
575         ast_channel_unlock(c);
576         astman_send_ack(s, m, "Changed monitor filename");
577         return 0;
578 }
579
580 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
581 {
582         if (chan->monitor)
583                 chan->monitor->joinfiles = turnon;
584 }
585
586 enum MONITOR_PAUSING_ACTION
587 {
588         MONITOR_ACTION_PAUSE,
589         MONITOR_ACTION_UNPAUSE
590 };
591           
592 static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action)
593 {
594         struct ast_channel *c = NULL;
595         const char *name = astman_get_header(m, "Channel");
596         
597         if (ast_strlen_zero(name)) {
598                 astman_send_error(s, m, "No channel specified");
599                 return -1;
600         }
601         
602         c = ast_get_channel_by_name_locked(name);
603         if (!c) {
604                 astman_send_error(s, m, "No such channel");
605                 return -1;
606         }
607
608         if (action == MONITOR_ACTION_PAUSE)
609                 ast_monitor_pause(c);
610         else
611                 ast_monitor_unpause(c);
612         
613         ast_channel_unlock(c);
614         astman_send_ack(s, m, (action == MONITOR_ACTION_PAUSE ? "Paused monitoring of the channel" : "Unpaused monitoring of the channel"));
615         return 0;       
616 }
617
618 static char pause_monitor_action_help[] =
619         "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
620         " recording of a channel.  The following parameters may\n"
621         " be used to control this:\n"
622         "  Channel     - Required.  Used to specify the channel to record.\n";
623
624 static int pause_monitor_action(struct mansession *s, const struct message *m)
625 {
626         return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
627 }
628
629 static char unpause_monitor_action_help[] =
630         "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
631         "  of a channel after calling PauseMonitor.  The following parameters may\n"
632         "  be used to control this:\n"
633         "  Channel     - Required.  Used to specify the channel to record.\n";
634
635 static int unpause_monitor_action(struct mansession *s, const struct message *m)
636 {
637         return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
638 }
639         
640
641 static int load_module(void)
642 {
643         ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
644         ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
645         ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
646         ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
647         ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
648         ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
649         ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
650         ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
651         ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
652         ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
653
654         return 0;
655 }
656
657 static int unload_module(void)
658 {
659         ast_unregister_application("Monitor");
660         ast_unregister_application("StopMonitor");
661         ast_unregister_application("ChangeMonitor");
662         ast_unregister_application("PauseMonitor");
663         ast_unregister_application("UnpauseMonitor");
664         ast_manager_unregister("Monitor");
665         ast_manager_unregister("StopMonitor");
666         ast_manager_unregister("ChangeMonitor");
667         ast_manager_unregister("PauseMonitor");
668         ast_manager_unregister("UnpauseMonitor");
669
670         return 0;
671 }
672
673 /* usecount semantics need to be defined */
674 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
675                 .load = load_module,
676                 .unload = unload_module,
677                 );