appending one list to another should leave the first list empty, and not require...
[asterisk-bristuff.git] / res / res_monitor.c
blobc8bae0a0c8629cb1282626dc726933a251d466c2
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
19 /*! \file
21 * \brief PBX channel monitoring
23 * \author Mark Spencer <markster@digium.com>
26 #include "asterisk.h"
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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>
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"
51 AST_MUTEX_DEFINE_STATIC(monitorlock);
53 #define LOCK_IF_NEEDED(lock, needed) do { \
54 if (needed) \
55 ast_channel_lock(lock); \
56 } while(0)
58 #define UNLOCK_IF_NEEDED(lock, needed) do { \
59 if (needed) \
60 ast_channel_unlock(lock); \
61 } while (0)
63 static unsigned long seq = 0;
65 static char *monitor_synopsis = "Monitor a channel";
67 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
68 "Used to start monitoring a channel. The channel's input and output\n"
69 "voice packets are logged to files until the channel hangs up or\n"
70 "monitoring is stopped by the StopMonitor application.\n"
71 " file_format optional, if not set, defaults to \"wav\"\n"
72 " fname_base if set, changes the filename used to the one specified.\n"
73 " options:\n"
74 " m - when the recording ends mix the two leg files into one and\n"
75 " delete the two leg files. If the variable MONITOR_EXEC is set, the\n"
76 " application referenced in it will be executed instead of\n"
77 " soxmix and the raw leg files will NOT be deleted automatically.\n"
78 " soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
79 " and a target mixed file name which is the same as the leg file names\n"
80 " only without the in/out designator.\n"
81 " If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
82 " additional arguements to MONITOR_EXEC\n"
83 " Both MONITOR_EXEC and the Mix flag can be set from the\n"
84 " administrator interface\n"
85 "\n"
86 " b - Don't begin recording unless a call is bridged to another channel\n"
87 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
88 "monitored, otherwise 0.\n"
91 static char *stopmonitor_synopsis = "Stop monitoring a channel";
93 static char *stopmonitor_descrip = "StopMonitor\n"
94 "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
96 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
98 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
99 "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
100 "The argument is the new filename base to use for monitoring this channel.\n";
102 static char *pausemonitor_synopsis = "Pause monitoring of a channel";
104 static char *pausemonitor_descrip = "PauseMonitor\n"
105 "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
107 static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
109 static char *unpausemonitor_descrip = "UnpauseMonitor\n"
110 "Unpauses monitoring of a channel on which monitoring had\n"
111 "previously been paused with PauseMonitor.\n";
113 static int ast_monitor_set_state(struct ast_channel *chan, int state)
115 LOCK_IF_NEEDED(chan, 1);
116 if (!chan->monitor) {
117 UNLOCK_IF_NEEDED(chan, 1);
118 return -1;
120 chan->monitor->state = state;
121 UNLOCK_IF_NEEDED(chan, 1);
122 return 0;
125 /* Start monitoring a channel */
126 int ast_monitor_start( struct ast_channel *chan, const char *format_spec,
127 const char *fname_base, int need_lock)
129 int res = 0;
130 char tmp[256];
132 LOCK_IF_NEEDED(chan, need_lock);
134 if (!(chan->monitor)) {
135 struct ast_channel_monitor *monitor;
136 char *channel_name, *p;
138 /* Create monitoring directory if needed */
139 if (mkdir(ast_config_AST_MONITOR_DIR, 0770) < 0) {
140 if (errno != EEXIST) {
141 ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
142 strerror(errno));
146 if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
147 UNLOCK_IF_NEEDED(chan, need_lock);
148 return -1;
151 /* Determine file names */
152 if (!ast_strlen_zero(fname_base)) {
153 int directory = strchr(fname_base, '/') ? 1 : 0;
154 /* try creating the directory just in case it doesn't exist */
155 if (directory) {
156 char *name = strdup(fname_base);
157 snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
158 free(name);
159 ast_safe_system(tmp);
161 snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
162 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
163 snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
164 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
165 ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
166 } else {
167 ast_mutex_lock(&monitorlock);
168 snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
169 ast_config_AST_MONITOR_DIR, seq);
170 snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
171 ast_config_AST_MONITOR_DIR, seq);
172 seq++;
173 ast_mutex_unlock(&monitorlock);
175 channel_name = ast_strdupa(chan->name);
176 while ((p = strchr(channel_name, '/'))) {
177 *p = '-';
179 snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
180 ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
181 monitor->filename_changed = 1;
184 monitor->stop = ast_monitor_stop;
186 /* Determine file format */
187 if (!ast_strlen_zero(format_spec)) {
188 monitor->format = strdup(format_spec);
189 } else {
190 monitor->format = strdup("wav");
193 /* open files */
194 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
195 ast_filedelete(monitor->read_filename, NULL);
197 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
198 monitor->format, NULL,
199 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
200 ast_log(LOG_WARNING, "Could not create file %s\n",
201 monitor->read_filename);
202 free(monitor);
203 UNLOCK_IF_NEEDED(chan, need_lock);
204 return -1;
206 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
207 ast_filedelete(monitor->write_filename, NULL);
209 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
210 monitor->format, NULL,
211 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
212 ast_log(LOG_WARNING, "Could not create file %s\n",
213 monitor->write_filename);
214 ast_closestream(monitor->read_stream);
215 free(monitor);
216 UNLOCK_IF_NEEDED(chan, need_lock);
217 return -1;
219 chan->monitor = monitor;
220 ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
221 /* so we know this call has been monitored in case we need to bill for it or something */
222 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
223 } else {
224 ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
225 chan->name);
226 res = -1;
229 UNLOCK_IF_NEEDED(chan, need_lock);
231 return res;
235 * The file format extensions that Asterisk uses are not all the same as that
236 * which soxmix expects. This function ensures that the format used as the
237 * extension on the filename is something soxmix will understand.
239 static const char *get_soxmix_format(const char *format)
241 const char *res = format;
243 if (!strcasecmp(format,"ulaw"))
244 res = "ul";
245 if (!strcasecmp(format,"alaw"))
246 res = "al";
248 return res;
251 /* Stop monitoring a channel */
252 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
254 int delfiles = 0;
256 LOCK_IF_NEEDED(chan, need_lock);
258 if (chan->monitor) {
259 char filename[ FILENAME_MAX ];
261 if (chan->monitor->read_stream) {
262 ast_closestream(chan->monitor->read_stream);
264 if (chan->monitor->write_stream) {
265 ast_closestream(chan->monitor->write_stream);
268 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
269 if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
270 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
271 if (ast_fileexists(filename, NULL, NULL) > 0) {
272 ast_filedelete(filename, NULL);
274 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
275 } else {
276 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
279 if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
280 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
281 if (ast_fileexists(filename, NULL, NULL) > 0) {
282 ast_filedelete(filename, NULL);
284 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
285 } else {
286 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
290 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
291 char tmp[1024];
292 char tmp2[1024];
293 const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
294 char *name = chan->monitor->filename_base;
295 int directory = strchr(name, '/') ? 1 : 0;
296 char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
297 const char *execute, *execute_args;
299 /* Set the execute application */
300 execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
301 if (ast_strlen_zero(execute)) {
302 execute = "nice -n 19 soxmix";
303 format = get_soxmix_format(format);
304 delfiles = 1;
306 execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
307 if (ast_strlen_zero(execute_args)) {
308 execute_args = "";
311 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);
312 if (delfiles) {
313 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
314 ast_copy_string(tmp, tmp2, sizeof(tmp));
316 ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
317 if (ast_safe_system(tmp) == -1)
318 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
321 free(chan->monitor->format);
322 free(chan->monitor);
323 chan->monitor = NULL;
326 UNLOCK_IF_NEEDED(chan, need_lock);
328 return 0;
332 /* Pause monitoring of a channel */
333 int ast_monitor_pause(struct ast_channel *chan)
335 return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
338 /* Unpause monitoring of a channel */
339 int ast_monitor_unpause(struct ast_channel *chan)
341 return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
344 static int pause_monitor_exec(struct ast_channel *chan, void *data)
346 return ast_monitor_pause(chan);
349 static int unpause_monitor_exec(struct ast_channel *chan, void *data)
351 return ast_monitor_unpause(chan);
354 /* Change monitoring filename of a channel */
355 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
357 char tmp[256];
358 if (ast_strlen_zero(fname_base)) {
359 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name);
360 return -1;
363 LOCK_IF_NEEDED(chan, need_lock);
365 if (chan->monitor) {
366 int directory = strchr(fname_base, '/') ? 1 : 0;
367 /* try creating the directory just in case it doesn't exist */
368 if (directory) {
369 char *name = strdup(fname_base);
370 snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
371 free(name);
372 ast_safe_system(tmp);
375 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
376 chan->monitor->filename_changed = 1;
377 } else {
378 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
381 UNLOCK_IF_NEEDED(chan, need_lock);
383 return 0;
386 static int start_monitor_exec(struct ast_channel *chan, void *data)
388 char *arg = NULL;
389 char *format = NULL;
390 char *fname_base = NULL;
391 char *options = NULL;
392 char *delay = NULL;
393 char *urlprefix = NULL;
394 char tmp[256];
395 int joinfiles = 0;
396 int waitforbridge = 0;
397 int res = 0;
399 /* Parse arguments. */
400 if (!ast_strlen_zero((char*)data)) {
401 arg = ast_strdupa((char*)data);
402 format = arg;
403 fname_base = strchr(arg, '|');
404 if (fname_base) {
405 *fname_base = 0;
406 fname_base++;
407 if ((options = strchr(fname_base, '|'))) {
408 *options = 0;
409 options++;
410 if (strchr(options, 'm'))
411 joinfiles = 1;
412 if (strchr(options, 'b'))
413 waitforbridge = 1;
416 arg = strchr(format,':');
417 if (arg) {
418 *arg++ = 0;
419 urlprefix = arg;
422 if (urlprefix) {
423 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
424 ((strcmp(format,"gsm")) ? "wav" : "gsm"));
425 if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
426 return -1;
427 ast_cdr_setuserfield(chan, tmp);
429 if (waitforbridge) {
430 /* We must remove the "b" option if listed. In principle none of
431 the following could give NULL results, but we check just to
432 be pedantic. Reconstructing with checks for 'm' option does not
433 work if we end up adding more options than 'm' in the future. */
434 delay = ast_strdupa(data);
435 options = strrchr(delay, '|');
436 if (options) {
437 arg = strchr(options, 'b');
438 if (arg) {
439 *arg = 'X';
440 pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
443 return 0;
446 res = ast_monitor_start(chan, format, fname_base, 1);
447 if (res < 0)
448 res = ast_monitor_change_fname(chan, fname_base, 1);
449 ast_monitor_setjoinfiles(chan, joinfiles);
451 return res;
454 static int stop_monitor_exec(struct ast_channel *chan, void *data)
456 return ast_monitor_stop(chan, 1);
459 static int change_monitor_exec(struct ast_channel *chan, void *data)
461 return ast_monitor_change_fname(chan, (const char*)data, 1);
464 static char start_monitor_action_help[] =
465 "Description: The 'Monitor' action may be used to record the audio on a\n"
466 " specified channel. The following parameters may be used to control\n"
467 " this:\n"
468 " Channel - Required. Used to specify the channel to record.\n"
469 " File - Optional. Is the name of the file created in the\n"
470 " monitor spool directory. Defaults to the same name\n"
471 " as the channel (with slashes replaced with dashes).\n"
472 " Format - Optional. Is the audio recording format. Defaults\n"
473 " to \"wav\".\n"
474 " Mix - Optional. Boolean parameter as to whether to mix\n"
475 " the input and output channels together after the\n"
476 " recording is finished.\n";
478 static int start_monitor_action(struct mansession *s, const struct message *m)
480 struct ast_channel *c = NULL;
481 const char *name = astman_get_header(m, "Channel");
482 const char *fname = astman_get_header(m, "File");
483 const char *format = astman_get_header(m, "Format");
484 const char *mix = astman_get_header(m, "Mix");
485 char *d;
487 if (ast_strlen_zero(name)) {
488 astman_send_error(s, m, "No channel specified");
489 return 0;
491 c = ast_get_channel_by_name_locked(name);
492 if (!c) {
493 astman_send_error(s, m, "No such channel");
494 return 0;
497 if (ast_strlen_zero(fname)) {
498 /* No filename base specified, default to channel name as per CLI */
499 if (!(fname = ast_strdup(c->name))) {
500 astman_send_error(s, m, "Could not start monitoring channel");
501 ast_channel_unlock(c);
502 return 0;
504 /* Channels have the format technology/channel_name - have to replace that / */
505 if ((d = strchr(fname, '/')))
506 *d = '-';
509 if (ast_monitor_start(c, format, fname, 1)) {
510 if (ast_monitor_change_fname(c, fname, 1)) {
511 astman_send_error(s, m, "Could not start monitoring channel");
512 ast_channel_unlock(c);
513 return 0;
517 if (ast_true(mix)) {
518 ast_monitor_setjoinfiles(c, 1);
521 ast_channel_unlock(c);
522 astman_send_ack(s, m, "Started monitoring channel");
523 return 0;
526 static char stop_monitor_action_help[] =
527 "Description: The 'StopMonitor' action may be used to end a previously\n"
528 " started 'Monitor' action. The only parameter is 'Channel', the name\n"
529 " of the channel monitored.\n";
531 static int stop_monitor_action(struct mansession *s, const struct message *m)
533 struct ast_channel *c = NULL;
534 const char *name = astman_get_header(m, "Channel");
535 int res;
536 if (ast_strlen_zero(name)) {
537 astman_send_error(s, m, "No channel specified");
538 return 0;
540 c = ast_get_channel_by_name_locked(name);
541 if (!c) {
542 astman_send_error(s, m, "No such channel");
543 return 0;
545 res = ast_monitor_stop(c, 1);
546 ast_channel_unlock(c);
547 if (res) {
548 astman_send_error(s, m, "Could not stop monitoring channel");
549 return 0;
551 astman_send_ack(s, m, "Stopped monitoring channel");
552 return 0;
555 static char change_monitor_action_help[] =
556 "Description: The 'ChangeMonitor' action may be used to change the file\n"
557 " started by a previous 'Monitor' action. The following parameters may\n"
558 " be used to control this:\n"
559 " Channel - Required. Used to specify the channel to record.\n"
560 " File - Required. Is the new name of the file created in the\n"
561 " monitor spool directory.\n";
563 static int change_monitor_action(struct mansession *s, const struct message *m)
565 struct ast_channel *c = NULL;
566 const char *name = astman_get_header(m, "Channel");
567 const char *fname = astman_get_header(m, "File");
568 if (ast_strlen_zero(name)) {
569 astman_send_error(s, m, "No channel specified");
570 return 0;
572 if (ast_strlen_zero(fname)) {
573 astman_send_error(s, m, "No filename specified");
574 return 0;
576 c = ast_get_channel_by_name_locked(name);
577 if (!c) {
578 astman_send_error(s, m, "No such channel");
579 return 0;
581 if (ast_monitor_change_fname(c, fname, 1)) {
582 astman_send_error(s, m, "Could not change monitored filename of channel");
583 ast_channel_unlock(c);
584 return 0;
586 ast_channel_unlock(c);
587 astman_send_ack(s, m, "Changed monitor filename");
588 return 0;
591 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
593 if (chan->monitor)
594 chan->monitor->joinfiles = turnon;
597 #define IS_NULL_STRING(string) ((!(string)) || (ast_strlen_zero((string))))
599 enum MONITOR_PAUSING_ACTION
601 MONITOR_ACTION_PAUSE,
602 MONITOR_ACTION_UNPAUSE
605 static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action)
607 struct ast_channel *c = NULL;
608 const char *name = astman_get_header(m, "Channel");
610 if (IS_NULL_STRING(name)) {
611 astman_send_error(s, m, "No channel specified");
612 return -1;
615 c = ast_get_channel_by_name_locked(name);
616 if (!c) {
617 astman_send_error(s, m, "No such channel");
618 return -1;
621 if (action == MONITOR_ACTION_PAUSE)
622 ast_monitor_pause(c);
623 else
624 ast_monitor_unpause(c);
626 ast_channel_unlock(c);
627 astman_send_ack(s, m, (action == MONITOR_ACTION_PAUSE ? "Paused monitoring of the channel" : "Unpaused monitoring of the channel"));
628 return 0;
631 static char pause_monitor_action_help[] =
632 "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
633 " recording of a channel. The following parameters may\n"
634 " be used to control this:\n"
635 " Channel - Required. Used to specify the channel to record.\n";
637 static int pause_monitor_action(struct mansession *s, const struct message *m)
639 return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
642 static char unpause_monitor_action_help[] =
643 "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
644 " of a channel after calling PauseMonitor. The following parameters may\n"
645 " be used to control this:\n"
646 " Channel - Required. Used to specify the channel to record.\n";
648 static int unpause_monitor_action(struct mansession *s, const struct message *m)
650 return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
654 static int load_module(void)
656 ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
657 ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
658 ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
659 ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
660 ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
661 ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
662 ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
663 ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
664 ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
665 ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
667 return 0;
670 static int unload_module(void)
672 ast_unregister_application("Monitor");
673 ast_unregister_application("StopMonitor");
674 ast_unregister_application("ChangeMonitor");
675 ast_unregister_application("PauseMonitor");
676 ast_unregister_application("UnpauseMonitor");
677 ast_manager_unregister("Monitor");
678 ast_manager_unregister("StopMonitor");
679 ast_manager_unregister("ChangeMonitor");
680 ast_manager_unregister("PauseMonitor");
681 ast_manager_unregister("UnpauseMonitor");
683 return 0;
686 /* usecount semantics need to be defined */
687 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
688 .load = load_module,
689 .unload = unload_module,