Sat Apr 26 2014 22:01:46

Asterisk developer's documentation


app_followme.c File Reference

Find-Me Follow-Me application. More...

#include "asterisk.h"
#include <signal.h>
#include "asterisk/paths.h"
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/translate.h"
#include "asterisk/say.h"
#include "asterisk/features.h"
#include "asterisk/musiconhold.h"
#include "asterisk/cli.h"
#include "asterisk/manager.h"
#include "asterisk/config.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/astdb.h"
#include "asterisk/dsp.h"
#include "asterisk/app.h"
Include dependency graph for app_followme.c:

Go to the source code of this file.

Data Structures

struct  call_followme::blnumbers
struct  call_followme
 Data structure for followme scripts. More...
struct  fm_args::cnumbers
struct  findme_user
struct  findme_user_listptr
struct  fm_args
struct  followmes
struct  number
 Number structure. More...
struct  call_followme::numbers
struct  call_followme::wlnumbers

Defines

#define MAX_YN_STRING   20

Enumerations

enum  {
  FOLLOWMEFLAG_STATUSMSG = (1 << 0), FOLLOWMEFLAG_RECORDNAME = (1 << 1), FOLLOWMEFLAG_UNREACHABLEMSG = (1 << 2), FOLLOWMEFLAG_DISABLEHOLDPROMPT = (1 << 3),
  FOLLOWMEFLAG_NOANSWER = (1 << 4), FOLLOWMEFLAG_DISABLEOPTIMIZATION = (1 << 5), FOLLOWMEFLAG_IGNORE_CONNECTEDLINE = (1 << 6), FOLLOWMEFLAG_PREDIAL_CALLER = (1 << 7),
  FOLLOWMEFLAG_PREDIAL_CALLEE = (1 << 8)
}
enum  { FOLLOWMEFLAG_ARG_PREDIAL_CALLER, FOLLOWMEFLAG_ARG_PREDIAL_CALLEE, FOLLOWMEFLAG_ARG_ARRAY_SIZE }

Functions

static void __reg_module (void)
static void __unreg_module (void)
static struct call_followmealloc_profile (const char *fmname)
 Allocate and initialize followme profile.
static int app_exec (struct ast_channel *chan, const char *data)
static void clear_caller (struct findme_user *tmpuser)
static void clear_unanswered_calls (struct findme_user_listptr *findme_user_list)
static struct numbercreate_followme_number (const char *number, int timeout, int numorder)
 Add a new number.
static void destroy_calling_node (struct findme_user *node)
static void destroy_calling_tree (struct findme_user_listptr *findme_user_list)
static void end_bridge_callback (void *data)
static void end_bridge_callback_data_fixup (struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator)
static struct call_followmefind_realtime (const char *name)
static struct ast_channelfindmeexec (struct fm_args *tpargs, struct ast_channel *caller)
static void free_numbers (struct call_followme *f)
static void init_profile (struct call_followme *f)
static int load_module (void)
static void profile_set_param (struct call_followme *f, const char *param, const char *val, int linenum, int failunknown)
 Set parameter in profile from configuration file.
static int reload (void)
static int reload_followme (int reload)
 Reload followme application module.
static int unload_module (void)
static struct ast_channelwait_for_winner (struct findme_user_listptr *findme_user_list, struct number *nm, struct ast_channel *caller, struct fm_args *tpargs)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Find-Me/Follow-Me Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, }
static char * app = "FollowMe"
static struct ast_module_infoast_module_info = &__mod_info
static char callfromprompt [PATH_MAX] = "followme/call-from"
static const char * defaultmoh = "default"
static int featuredigittimeout = 5000
static const char * featuredigittostr
static struct ast_app_option followme_opts [128] = { [ 'a' ] = { .flag = FOLLOWMEFLAG_RECORDNAME }, [ 'B' ] = { .flag = FOLLOWMEFLAG_PREDIAL_CALLER , .arg_index = FOLLOWMEFLAG_ARG_PREDIAL_CALLER + 1 }, [ 'b' ] = { .flag = FOLLOWMEFLAG_PREDIAL_CALLEE , .arg_index = FOLLOWMEFLAG_ARG_PREDIAL_CALLEE + 1 }, [ 'd' ] = { .flag = FOLLOWMEFLAG_DISABLEHOLDPROMPT }, [ 'I' ] = { .flag = FOLLOWMEFLAG_IGNORE_CONNECTEDLINE }, [ 'l' ] = { .flag = FOLLOWMEFLAG_DISABLEOPTIMIZATION }, [ 'N' ] = { .flag = FOLLOWMEFLAG_NOANSWER }, [ 'n' ] = { .flag = FOLLOWMEFLAG_UNREACHABLEMSG }, [ 's' ] = { .flag = FOLLOWMEFLAG_STATUSMSG },}
static struct followmes followmes
static char nextindp [MAX_YN_STRING] = "2"
static char norecordingprompt [PATH_MAX] = "followme/no-recording"
static char optionsprompt [PATH_MAX] = "followme/options"
static char plsholdprompt [PATH_MAX] = "followme/pls-hold-while-try"
static char sorryprompt [PATH_MAX] = "followme/sorry"
static char statusprompt [PATH_MAX] = "followme/status"
static char takecall [MAX_YN_STRING] = "1"

Detailed Description

Find-Me Follow-Me application.

Author:
BJ Weschke <bweschke@btwtech.com>

Definition in file app_followme.c.


Define Documentation

#define MAX_YN_STRING   20

Maximum accept/decline DTMF string plus terminator.

Definition at line 143 of file app_followme.c.


Enumeration Type Documentation

anonymous enum
Enumerator:
FOLLOWMEFLAG_STATUSMSG 
FOLLOWMEFLAG_RECORDNAME 
FOLLOWMEFLAG_UNREACHABLEMSG 
FOLLOWMEFLAG_DISABLEHOLDPROMPT 
FOLLOWMEFLAG_NOANSWER 
FOLLOWMEFLAG_DISABLEOPTIMIZATION 
FOLLOWMEFLAG_IGNORE_CONNECTEDLINE 
FOLLOWMEFLAG_PREDIAL_CALLER 
FOLLOWMEFLAG_PREDIAL_CALLEE 

Definition at line 223 of file app_followme.c.

anonymous enum
Enumerator:
FOLLOWMEFLAG_ARG_PREDIAL_CALLER 
FOLLOWMEFLAG_ARG_PREDIAL_CALLEE 
FOLLOWMEFLAG_ARG_ARRAY_SIZE 

Definition at line 235 of file app_followme.c.

     {
   FOLLOWMEFLAG_ARG_PREDIAL_CALLER,
   FOLLOWMEFLAG_ARG_PREDIAL_CALLEE,

   /* note: this entry _MUST_ be the last one in the enum */
   FOLLOWMEFLAG_ARG_ARRAY_SIZE
};

Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 1577 of file app_followme.c.

static void __unreg_module ( void  ) [static]

Definition at line 1577 of file app_followme.c.

static struct call_followme* alloc_profile ( const char *  fmname) [static, read]

Allocate and initialize followme profile.

Definition at line 295 of file app_followme.c.

References ast_calloc, ast_copy_string(), AST_LIST_HEAD_INIT_NOLOCK, ast_mutex_init, call_followme::blnumbers, call_followme::callfromprompt, call_followme::context, f, call_followme::lock, call_followme::moh, call_followme::name, call_followme::nextindp, call_followme::norecordingprompt, call_followme::numbers, call_followme::optionsprompt, call_followme::plsholdprompt, call_followme::sorryprompt, call_followme::statusprompt, call_followme::takecall, and call_followme::wlnumbers.

Referenced by find_realtime(), and reload_followme().

static int app_exec ( struct ast_channel chan,
const char *  data 
) [static]

Definition at line 1303 of file app_followme.c.

References call_followme::active, args, ast_answer(), AST_APP_ARG, ast_app_exec_sub(), ast_app_expand_sub_args(), ast_app_parse_options(), ast_autoservice_chan_hangup_peer(), ast_bridge_call(), ast_calloc, ast_channel_caller(), ast_channel_connected_line_macro(), ast_channel_connected_line_sub(), ast_channel_language(), ast_channel_lock, ast_channel_make_compatible(), ast_channel_name(), ast_channel_uniqueid(), ast_channel_unlock, ast_channel_update_connected_line(), ast_clear_flag, ast_config_AST_SPOOL_DIR, ast_connected_line_copy_from_caller(), AST_CONTROL_HOLD, AST_CONTROL_RINGING, ast_copy_string(), ast_deactivate_generator(), ast_debug, AST_DECLARE_APP_ARGS, ast_dsp_get_threshold_from_settings(), AST_FEATURE_AUTOMON, AST_FEATURE_REDIRECT, ast_fileexists(), ast_free, ast_indicate(), ast_indicate_data(), AST_LIST_HEAD_INIT_NOLOCK, AST_LIST_INSERT_TAIL, AST_LIST_REMOVE_HEAD, AST_LIST_TRAVERSE, ast_log(), ast_moh_start(), ast_moh_stop(), ast_mutex_lock, ast_mutex_unlock, ast_party_connected_line_free(), ast_play_and_record(), ast_replace_subargument_delimiter(), AST_RWLIST_RDLOCK, AST_RWLIST_TRAVERSE, AST_RWLIST_UNLOCK, ast_set_flag, AST_STANDARD_APP_ARGS, AST_STATE_UP, ast_stream_and_wait(), ast_streamfile(), ast_strlen_zero(), ast_test_flag, ast_waitstream(), ast_channel::caller, call_followme::callfromprompt, fm_args::callfromprompt, fm_args::cnumbers, fm_args::connected_in, fm_args::connected_out, call_followme::context, fm_args::context, create_followme_number(), ast_bridge_config::end_bridge_callback, end_bridge_callback(), ast_bridge_config::end_bridge_callback_data, ast_bridge_config::end_bridge_callback_data_fixup, end_bridge_callback_data_fixup(), f, ast_bridge_config::features_callee, ast_bridge_config::features_caller, find_realtime(), findmeexec(), followme_opts, FOLLOWMEFLAG_ARG_ARRAY_SIZE, FOLLOWMEFLAG_ARG_PREDIAL_CALLEE, FOLLOWMEFLAG_ARG_PREDIAL_CALLER, FOLLOWMEFLAG_DISABLEHOLDPROMPT, FOLLOWMEFLAG_NOANSWER, FOLLOWMEFLAG_PREDIAL_CALLEE, FOLLOWMEFLAG_PREDIAL_CALLER, FOLLOWMEFLAG_RECORDNAME, FOLLOWMEFLAG_STATUSMSG, FOLLOWMEFLAG_UNREACHABLEMSG, fm_args::followmeflags, free_numbers(), call_followme::lock, LOG_WARNING, call_followme::moh, fm_args::mohclass, call_followme::name, fm_args::namerecloc, call_followme::nextindp, fm_args::nextindp, call_followme::norecordingprompt, fm_args::norecordingprompt, number::number, call_followme::numbers, call_followme::optionsprompt, fm_args::optionsprompt, number::order, fm_args::pending_hold, fm_args::pending_in_connected_update, fm_args::pending_out_connected_update, call_followme::plsholdprompt, fm_args::plsholdprompt, fm_args::predial_callee, call_followme::realtime, S_OR, call_followme::sorryprompt, fm_args::sorryprompt, call_followme::statusprompt, fm_args::statusprompt, fm_args::suggested_moh, call_followme::takecall, fm_args::takecall, THRESHOLD_SILENCE, and number::timeout.

Referenced by load_module().

{
   struct fm_args *targs;
   struct ast_bridge_config config;
   struct call_followme *f;
   struct number *nm, *newnm;
   int res = 0;
   char *argstr;
   struct ast_channel *caller;
   struct ast_channel *outbound;
   AST_DECLARE_APP_ARGS(args,
      AST_APP_ARG(followmeid);
      AST_APP_ARG(options);
   );
   char *opt_args[FOLLOWMEFLAG_ARG_ARRAY_SIZE];

   if (ast_strlen_zero(data)) {
      ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
      return -1;
   }

   argstr = ast_strdupa((char *) data);

   AST_STANDARD_APP_ARGS(args, argstr);

   if (ast_strlen_zero(args.followmeid)) {
      ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
      return -1;
   }

   targs = ast_calloc(1, sizeof(*targs));
   if (!targs) {
      return -1;
   }

   AST_RWLIST_RDLOCK(&followmes);
   AST_RWLIST_TRAVERSE(&followmes, f, entry) {
      if (!strcasecmp(f->name, args.followmeid) && (f->active))
         break;
   }
   AST_RWLIST_UNLOCK(&followmes);

   ast_debug(1, "New profile %s.\n", args.followmeid);

   if (!f) {
      f = find_realtime(args.followmeid);
   }

   if (!f) {
      ast_log(LOG_WARNING, "Profile requested, %s, not found in the configuration.\n", args.followmeid);
      ast_free(targs);
      return 0;
   }

   /* XXX TODO: Reinsert the db check value to see whether or not follow-me is on or off */
   if (args.options) {
      ast_app_parse_options(followme_opts, &targs->followmeflags, opt_args, args.options);
   }

   /* Lock the profile lock and copy out everything we need to run with before unlocking it again */
   ast_mutex_lock(&f->lock);
   targs->mohclass = ast_strdupa(f->moh);
   ast_copy_string(targs->context, f->context, sizeof(targs->context));
   ast_copy_string(targs->takecall, f->takecall, sizeof(targs->takecall));
   ast_copy_string(targs->nextindp, f->nextindp, sizeof(targs->nextindp));
   ast_copy_string(targs->callfromprompt, f->callfromprompt, sizeof(targs->callfromprompt));
   ast_copy_string(targs->norecordingprompt, f->norecordingprompt, sizeof(targs->norecordingprompt));
   ast_copy_string(targs->optionsprompt, f->optionsprompt, sizeof(targs->optionsprompt));
   ast_copy_string(targs->plsholdprompt, f->plsholdprompt, sizeof(targs->plsholdprompt));
   ast_copy_string(targs->statusprompt, f->statusprompt, sizeof(targs->statusprompt));
   ast_copy_string(targs->sorryprompt, f->sorryprompt, sizeof(targs->sorryprompt));
   /* Copy the numbers we're going to use into another list in case the master list should get modified 
      (and locked) while we're trying to do a follow-me */
   AST_LIST_HEAD_INIT_NOLOCK(&targs->cnumbers);
   AST_LIST_TRAVERSE(&f->numbers, nm, entry) {
      newnm = create_followme_number(nm->number, nm->timeout, nm->order);
      if (newnm) {
         AST_LIST_INSERT_TAIL(&targs->cnumbers, newnm, entry);
      }
   }
   ast_mutex_unlock(&f->lock);

   /* PREDIAL: Preprocess any callee gosub arguments. */
   if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_PREDIAL_CALLEE)
      && !ast_strlen_zero(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE])) {
      ast_replace_subargument_delimiter(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE]);
      targs->predial_callee =
         ast_app_expand_sub_args(chan, opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE]);
   }

   /* PREDIAL: Run gosub on the caller's channel */
   if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_PREDIAL_CALLER)
      && !ast_strlen_zero(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER])) {
      ast_replace_subargument_delimiter(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER]);
      ast_app_exec_sub(NULL, chan, opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER], 0);
   }

   /* Forget the 'N' option if the call is already up. */
   if (ast_channel_state(chan) == AST_STATE_UP) {
      ast_clear_flag(&targs->followmeflags, FOLLOWMEFLAG_NOANSWER);
   }

   if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_NOANSWER)) {
      ast_indicate(chan, AST_CONTROL_RINGING);
   } else {
      /* Answer the call */
      if (ast_channel_state(chan) != AST_STATE_UP) {
         ast_answer(chan);
      }

      if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_STATUSMSG)) {
         ast_stream_and_wait(chan, targs->statusprompt, "");
      }

      if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_RECORDNAME)) {
         int duration = 5;

         snprintf(targs->namerecloc, sizeof(targs->namerecloc), "%s/followme.%s",
            ast_config_AST_SPOOL_DIR, ast_channel_uniqueid(chan));
         if (ast_play_and_record(chan, "vm-rec-name", targs->namerecloc, 5, "sln", &duration,
            NULL, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE), 0, NULL) < 0) {
            goto outrun;
         }
         if (!ast_fileexists(targs->namerecloc, NULL, ast_channel_language(chan))) {
            targs->namerecloc[0] = '\0';
         }
      }

      if (!ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_DISABLEHOLDPROMPT)) {
         if (ast_streamfile(chan, targs->plsholdprompt, ast_channel_language(chan))) {
            goto outrun;
         }
         if (ast_waitstream(chan, "") < 0)
            goto outrun;
      }
      ast_moh_start(chan, S_OR(targs->mohclass, NULL), NULL);
   }

   ast_channel_lock(chan);
   ast_connected_line_copy_from_caller(&targs->connected_in, ast_channel_caller(chan));
   ast_channel_unlock(chan);

   outbound = findmeexec(targs, chan);
   if (!outbound) {
      if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_NOANSWER)) {
         if (ast_channel_state(chan) != AST_STATE_UP) {
            ast_answer(chan);
         }
      } else {
         ast_moh_stop(chan);
      }

      if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_UNREACHABLEMSG)) {
         ast_stream_and_wait(chan, targs->sorryprompt, "");
      }
      res = 0;
   } else {
      caller = chan;
      /* Bridge the two channels. */

      memset(&config, 0, sizeof(config));
      ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
      ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
      ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
      config.end_bridge_callback = end_bridge_callback;
      config.end_bridge_callback_data = chan;
      config.end_bridge_callback_data_fixup = end_bridge_callback_data_fixup;

      /* Update connected line to caller if available. */
      if (targs->pending_out_connected_update) {
         if (ast_channel_connected_line_sub(outbound, caller, &targs->connected_out, 0) &&
            ast_channel_connected_line_macro(outbound, caller, &targs->connected_out, 1, 0)) {
            ast_channel_update_connected_line(caller, &targs->connected_out, NULL);
         }
      }

      if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_NOANSWER)) {
         if (ast_channel_state(caller) != AST_STATE_UP) {
            ast_answer(caller);
         }
      } else {
         ast_moh_stop(caller);
      }

      /* Be sure no generators are left on it */
      ast_deactivate_generator(caller);
      /* Make sure channels are compatible */
      res = ast_channel_make_compatible(caller, outbound);
      if (res < 0) {
         ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(caller), ast_channel_name(outbound));
         ast_autoservice_chan_hangup_peer(caller, outbound);
         goto outrun;
      }

      /* Update connected line to winner if changed. */
      if (targs->pending_in_connected_update) {
         if (ast_channel_connected_line_sub(caller, outbound, &targs->connected_in, 0) &&
            ast_channel_connected_line_macro(caller, outbound, &targs->connected_in, 0, 0)) {
            ast_channel_update_connected_line(outbound, &targs->connected_in, NULL);
         }
      }

      /* Put winner on hold if caller requested. */
      if (targs->pending_hold) {
         if (ast_strlen_zero(targs->suggested_moh)) {
            ast_indicate_data(outbound, AST_CONTROL_HOLD, NULL, 0);
         } else {
            ast_indicate_data(outbound, AST_CONTROL_HOLD,
               targs->suggested_moh, strlen(targs->suggested_moh) + 1);
         }
      }

      res = ast_bridge_call(caller, outbound, &config);
      ast_autoservice_chan_hangup_peer(caller, outbound);
   }

outrun:
   while ((nm = AST_LIST_REMOVE_HEAD(&targs->cnumbers, entry))) {
      ast_free(nm);
   }
   if (!ast_strlen_zero(targs->namerecloc)) {
      unlink(targs->namerecloc);
   }
   ast_free((char *) targs->predial_callee);
   ast_party_connected_line_free(&targs->connected_in);
   ast_party_connected_line_free(&targs->connected_out);
   ast_free(targs);

   if (f->realtime) {
      /* Not in list */
      free_numbers(f);
      ast_free(f);
   }

   return res;
}
static void clear_caller ( struct findme_user tmpuser) [static]

Definition at line 553 of file app_followme.c.

References ast_cdr_alloc(), ast_cdr_disposition(), ast_cdr_end(), ast_cdr_failed(), ast_cdr_init(), ast_cdr_setapp(), ast_cdr_start(), ast_cdr_update(), ast_channel_cdr(), ast_channel_cdr_set(), ast_channel_hangupcause(), ast_channel_lock, ast_channel_unlock, ast_hangup(), ast_log(), findme_user::dialarg, LOG_WARNING, and findme_user::ochan.

Referenced by clear_unanswered_calls(), destroy_calling_node(), and wait_for_winner().

{
   struct ast_channel *outbound;

   if (!tmpuser->ochan) {
      /* Call already cleared. */
      return;
   }

   outbound = tmpuser->ochan;
   ast_channel_lock(outbound);
   if (!ast_channel_cdr(outbound)) {
      ast_channel_cdr_set(outbound, ast_cdr_alloc());
      if (ast_channel_cdr(outbound)) {
         ast_cdr_init(ast_channel_cdr(outbound), outbound);
      }
   }
   if (ast_channel_cdr(outbound)) {
      char tmp[256];

      snprintf(tmp, sizeof(tmp), "Local/%s", tmpuser->dialarg);
      ast_cdr_setapp(ast_channel_cdr(outbound), "FollowMe", tmp);
      ast_cdr_update(outbound);
      ast_cdr_start(ast_channel_cdr(outbound));
      ast_cdr_end(ast_channel_cdr(outbound));
      /* If the cause wasn't handled properly */
      if (ast_cdr_disposition(ast_channel_cdr(outbound), ast_channel_hangupcause(outbound))) {
         ast_cdr_failed(ast_channel_cdr(outbound));
      }
   } else {
      ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
   }
   ast_channel_unlock(outbound);
   ast_hangup(outbound);
   tmpuser->ochan = NULL;
}
static void clear_unanswered_calls ( struct findme_user_listptr findme_user_list) [static]

Definition at line 590 of file app_followme.c.

References findme_user::answered, AST_LIST_TRAVERSE, and clear_caller().

Referenced by wait_for_winner().

{
   struct findme_user *tmpuser;

   AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
      if (!tmpuser->answered) {
         clear_caller(tmpuser);
      }
   }
}
static struct number* create_followme_number ( const char *  number,
int  timeout,
int  numorder 
) [static, read]

Add a new number.

Definition at line 361 of file app_followme.c.

References ast_calloc, ast_copy_string(), ast_debug, number::number, number::order, and number::timeout.

Referenced by app_exec(), find_realtime(), and reload_followme().

{
   struct number *cur;
   char *buf = ast_strdupa(number);
   char *tmp;

   if (!(cur = ast_calloc(1, sizeof(*cur))))
      return NULL;

   cur->timeout = timeout;
   if ((tmp = strchr(buf, ',')))
      *tmp = '\0';
   ast_copy_string(cur->number, buf, sizeof(cur->number));
   cur->order = numorder;
   ast_debug(1, "Created a number, %s, order of , %d, with a timeout of %ld.\n", cur->number, cur->order, cur->timeout);

   return cur;
}
static void destroy_calling_node ( struct findme_user node) [static]
static void destroy_calling_tree ( struct findme_user_listptr findme_user_list) [static]

Definition at line 608 of file app_followme.c.

References AST_LIST_REMOVE_HEAD, and destroy_calling_node().

Referenced by findmeexec().

{
   struct findme_user *fmuser;

   while ((fmuser = AST_LIST_REMOVE_HEAD(findme_user_list, entry))) {
      destroy_calling_node(fmuser);
   }
}
static void end_bridge_callback ( void *  data) [static]

Definition at line 1277 of file app_followme.c.

References ast_channel_cdr(), ast_channel_lock, ast_channel_unlock, ast_channel::data, and pbx_builtin_setvar_helper().

Referenced by app_exec().

{
   char buf[80];
   time_t end;
   struct ast_channel *chan = data;

   time(&end);

   ast_channel_lock(chan);
   if (ast_channel_cdr(chan)->answer.tv_sec) {
      snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec);
      pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
   }

   if (ast_channel_cdr(chan)->start.tv_sec) {
      snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec);
      pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
   }
   ast_channel_unlock(chan);
}
static void end_bridge_callback_data_fixup ( struct ast_bridge_config bconfig,
struct ast_channel originator,
struct ast_channel terminator 
) [static]

Definition at line 1298 of file app_followme.c.

References ast_bridge_config::end_bridge_callback_data.

Referenced by app_exec().

{
   bconfig->end_bridge_callback_data = originator;
}
static struct call_followme* find_realtime ( const char *  name) [static, read]

Definition at line 1196 of file app_followme.c.

References alloc_profile(), ast_category_browse(), ast_config_destroy(), ast_false(), ast_free, AST_LIST_INSERT_TAIL, ast_load_realtime(), ast_load_realtime_multientry(), ast_mutex_destroy, ast_str_buffer(), ast_str_create(), ast_str_set(), ast_variable_retrieve(), ast_variables_destroy(), create_followme_number(), call_followme::lock, ast_variable::name, ast_variable::next, call_followme::numbers, profile_set_param(), call_followme::realtime, SENTINEL, str, number::timeout, ast_variable::value, and var.

Referenced by app_exec().

{
   struct ast_variable *var;
   struct ast_variable *v;
   struct ast_config *cfg;
   const char *catg;
   struct call_followme *new_follower;
   struct ast_str *str;

   str = ast_str_create(16);
   if (!str) {
      return NULL;
   }

   var = ast_load_realtime("followme", "name", name, SENTINEL);
   if (!var) {
      ast_free(str);
      return NULL;
   }

   if (!(new_follower = alloc_profile(name))) {
      ast_variables_destroy(var);
      ast_free(str);
      return NULL;
   }

   for (v = var; v; v = v->next) {
      if (!strcasecmp(v->name, "active")) {
         if (ast_false(v->value)) {
            ast_mutex_destroy(&new_follower->lock);
            ast_free(new_follower);
            ast_variables_destroy(var);
            ast_free(str);
            return NULL;
         }
      } else {
         profile_set_param(new_follower, v->name, v->value, 0, 0);
      }
   }

   ast_variables_destroy(var);
   new_follower->realtime = 1;

   /* Load numbers */
   cfg = ast_load_realtime_multientry("followme_numbers", "ordinal LIKE", "%", "name",
      name, SENTINEL);
   if (!cfg) {
      ast_mutex_destroy(&new_follower->lock);
      ast_free(new_follower);
      ast_free(str);
      return NULL;
   }

   for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
      const char *numstr;
      const char *timeoutstr;
      const char *ordstr;
      int timeout;
      struct number *cur;

      if (!(numstr = ast_variable_retrieve(cfg, catg, "phonenumber"))) {
         continue;
      }
      if (!(timeoutstr = ast_variable_retrieve(cfg, catg, "timeout"))
         || sscanf(timeoutstr, "%30d", &timeout) != 1
         || timeout < 1) {
         timeout = 25;
      }
      /* This one has to exist; it was part of the query */
      ordstr = ast_variable_retrieve(cfg, catg, "ordinal");
      ast_str_set(&str, 0, "%s", numstr);
      if ((cur = create_followme_number(ast_str_buffer(str), timeout, atoi(ordstr)))) {
         AST_LIST_INSERT_TAIL(&new_follower->numbers, cur, entry);
      }
   }
   ast_config_destroy(cfg);

   ast_free(str);
   return new_follower;
}
static struct ast_channel* findmeexec ( struct fm_args tpargs,
struct ast_channel caller 
) [static, read]

Definition at line 987 of file app_followme.c.

References ast_autoservice_start(), ast_autoservice_stop(), ast_call(), ast_calloc, ast_cause2str(), ast_cdr_init(), ast_channel_accountcode(), ast_channel_caller(), ast_channel_cdr(), ast_channel_connected(), ast_channel_datastore_inherit(), ast_channel_inherit_variables(), ast_channel_language(), ast_channel_lock, ast_channel_lock_both, ast_channel_musicclass(), ast_channel_nativeformats(), ast_channel_unlock, ast_check_hangup(), ast_connected_line_copy_from_caller(), ast_copy_string(), ast_debug, ast_exists_extension(), ast_free, AST_LIST_APPEND_LIST, AST_LIST_EMPTY, AST_LIST_HEAD_NOLOCK_INIT_VALUE, AST_LIST_INSERT_TAIL, AST_LIST_REMOVE_CURRENT, AST_LIST_REMOVE_HEAD, AST_LIST_TRAVERSE, AST_LIST_TRAVERSE_SAFE_BEGIN, AST_LIST_TRAVERSE_SAFE_END, ast_log(), ast_pre_call(), ast_request(), ast_strlen_zero(), ast_test_flag, ast_verb, fm_args::cnumbers, findme_user::connected, fm_args::connected_out, fm_args::context, destroy_calling_node(), destroy_calling_tree(), findme_user::dialarg, FOLLOWMEFLAG_DISABLEOPTIMIZATION, fm_args::followmeflags, LOG_ERROR, LOG_WARNING, number::number, findme_user::ochan, number::order, findme_user::pending_connected_update, fm_args::pending_out_connected_update, fm_args::predial_callee, S_COR, findme_user::state, number::timeout, and wait_for_winner().

Referenced by app_exec().

{
   struct number *nm;
   struct ast_channel *winner = NULL;
   char num[512];
   int dg, idx;
   char *rest, *number;
   struct findme_user *tmpuser;
   struct findme_user *fmuser;
   struct findme_user_listptr findme_user_list = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
   struct findme_user_listptr new_user_list = AST_LIST_HEAD_NOLOCK_INIT_VALUE;

   for (idx = 1; !ast_check_hangup(caller); ++idx) {
      /* Find next followme numbers to dial. */
      AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry) {
         if (nm->order == idx) {
            break;
         }
      }
      if (!nm) {
         ast_verb(3, "No more steps left.\n");
         break;
      }

      ast_debug(2, "Number(s) %s timeout %ld\n", nm->number, nm->timeout);

      /*
       * Put all active outgoing channels into autoservice.
       *
       * This needs to be done because ast_exists_extension() may put
       * the caller into autoservice.
       */
      AST_LIST_TRAVERSE(&findme_user_list, tmpuser, entry) {
         if (tmpuser->ochan) {
            ast_autoservice_start(tmpuser->ochan);
         }
      }

      /* Create all new outgoing calls */
      ast_copy_string(num, nm->number, sizeof(num));
      for (number = num; number; number = rest) {
         struct ast_channel *outbound;

         rest = strchr(number, '&');
         if (rest) {
            *rest++ = 0;
         }

         /* We check if the extension exists, before creating the ast_channel struct */
         if (!ast_exists_extension(caller, tpargs->context, number, 1, S_COR(ast_channel_caller(caller)->id.number.valid, ast_channel_caller(caller)->id.number.str, NULL))) {
            ast_log(LOG_ERROR, "Extension '%s@%s' doesn't exist\n", number, tpargs->context);
            continue;
         }

         tmpuser = ast_calloc(1, sizeof(*tmpuser));
         if (!tmpuser) {
            continue;
         }

         if (ast_strlen_zero(tpargs->context)) {
            snprintf(tmpuser->dialarg, sizeof(tmpuser->dialarg), "%s%s",
               number,
               ast_test_flag(&tpargs->followmeflags, FOLLOWMEFLAG_DISABLEOPTIMIZATION)
                  ? "/n" : "/m");
         } else {
            snprintf(tmpuser->dialarg, sizeof(tmpuser->dialarg), "%s@%s%s",
               number, tpargs->context,
               ast_test_flag(&tpargs->followmeflags, FOLLOWMEFLAG_DISABLEOPTIMIZATION)
                  ? "/n" : "/m");
         }

         outbound = ast_request("Local", ast_channel_nativeformats(caller), caller,
            tmpuser->dialarg, &dg);
         if (!outbound) {
            ast_log(LOG_WARNING, "Unable to allocate a channel for Local/%s cause: %s\n",
               tmpuser->dialarg, ast_cause2str(dg));
            ast_free(tmpuser);
            continue;
         }

         ast_channel_lock_both(caller, outbound);
         ast_connected_line_copy_from_caller(ast_channel_connected(outbound), ast_channel_caller(caller));
         ast_channel_inherit_variables(caller, outbound);
         ast_channel_datastore_inherit(caller, outbound);
         ast_channel_language_set(outbound, ast_channel_language(caller));
         ast_channel_accountcode_set(outbound, ast_channel_accountcode(caller));
         ast_channel_musicclass_set(outbound, ast_channel_musicclass(caller));
         ast_channel_unlock(outbound);
         ast_channel_unlock(caller);

         tmpuser->ochan = outbound;
         tmpuser->state = 0;
         AST_LIST_INSERT_TAIL(&new_user_list, tmpuser, entry);
      }

      /*
       * PREDIAL: Run gosub on all of the new callee channels
       *
       * We run the callee predial before ast_call() in case the user
       * wishes to do something on the newly created channels before
       * the channel does anything important.
       */
      if (tpargs->predial_callee && !AST_LIST_EMPTY(&new_user_list)) {
         /* Put caller into autoservice. */
         ast_autoservice_start(caller);

         /* Run predial on all new outgoing calls. */
         AST_LIST_TRAVERSE(&new_user_list, tmpuser, entry) {
            ast_pre_call(tmpuser->ochan, tpargs->predial_callee);
         }

         /* Take caller out of autoservice. */
         if (ast_autoservice_stop(caller)) {
            /*
             * Caller hungup.
             *
             * Destoy all new outgoing calls.
             */
            while ((tmpuser = AST_LIST_REMOVE_HEAD(&new_user_list, entry))) {
               ast_channel_lock(tmpuser->ochan);
               if (ast_channel_cdr(tmpuser->ochan)) {
                  ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
               }
               ast_channel_unlock(tmpuser->ochan);
               destroy_calling_node(tmpuser);
            }

            /* Take all active outgoing channels out of autoservice. */
            AST_LIST_TRAVERSE(&findme_user_list, tmpuser, entry) {
               if (tmpuser->ochan) {
                  ast_autoservice_stop(tmpuser->ochan);
               }
            }
            break;
         }
      }

      /* Start all new outgoing calls */
      AST_LIST_TRAVERSE_SAFE_BEGIN(&new_user_list, tmpuser, entry) {
         ast_verb(3, "calling Local/%s\n", tmpuser->dialarg);
         if (ast_call(tmpuser->ochan, tmpuser->dialarg, 0)) {
            ast_verb(3, "couldn't reach at this number.\n");
            AST_LIST_REMOVE_CURRENT(entry);

            /* Destroy this failed new outgoing call. */
            ast_channel_lock(tmpuser->ochan);
            if (ast_channel_cdr(tmpuser->ochan)) {
               ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
            }
            ast_channel_unlock(tmpuser->ochan);
            destroy_calling_node(tmpuser);
         }
      }
      AST_LIST_TRAVERSE_SAFE_END;

      /* Take all active outgoing channels out of autoservice. */
      AST_LIST_TRAVERSE_SAFE_BEGIN(&findme_user_list, tmpuser, entry) {
         if (tmpuser->ochan && ast_autoservice_stop(tmpuser->ochan)) {
            /* Existing outgoing call hungup. */
            AST_LIST_REMOVE_CURRENT(entry);
            destroy_calling_node(tmpuser);
         }
      }
      AST_LIST_TRAVERSE_SAFE_END;

      if (AST_LIST_EMPTY(&new_user_list)) {
         /* No new channels remain at this order level.  If there were any at all. */
         continue;
      }

      /* Add new outgoing channels to the findme list. */
      AST_LIST_APPEND_LIST(&findme_user_list, &new_user_list, entry);

      winner = wait_for_winner(&findme_user_list, nm, caller, tpargs);
      if (!winner) {
         /* Remove all dead outgoing nodes. */
         AST_LIST_TRAVERSE_SAFE_BEGIN(&findme_user_list, tmpuser, entry) {
            if (!tmpuser->ochan) {
               AST_LIST_REMOVE_CURRENT(entry);
               destroy_calling_node(tmpuser);
            }
         }
         AST_LIST_TRAVERSE_SAFE_END;
         continue;
      }

      /* Destroy losing calls up to the winner.  The rest will be destroyed later. */
      while ((fmuser = AST_LIST_REMOVE_HEAD(&findme_user_list, entry))) {
         if (fmuser->ochan == winner) {
            /*
             * Pass any connected line info up.
             *
             * NOTE: This code must be in line with destroy_calling_node().
             */
            tpargs->connected_out = fmuser->connected;
            tpargs->pending_out_connected_update = fmuser->pending_connected_update;
            ast_free(fmuser);
            break;
         } else {
            /* Destroy losing call. */
            destroy_calling_node(fmuser);
         }
      }
      break;
   }
   destroy_calling_tree(&findme_user_list);
   return winner;
}
static void free_numbers ( struct call_followme f) [static]

Definition at line 272 of file app_followme.c.

References ast_free, AST_LIST_HEAD_INIT_NOLOCK, AST_LIST_REMOVE_HEAD, call_followme::blnumbers, call_followme::numbers, and call_followme::wlnumbers.

Referenced by app_exec(), reload_followme(), and unload_module().

{
   /* Free numbers attached to the profile */
   struct number *prev;

   while ((prev = AST_LIST_REMOVE_HEAD(&f->numbers, entry)))
      /* Free the number */
      ast_free(prev);
   AST_LIST_HEAD_INIT_NOLOCK(&f->numbers);

   while ((prev = AST_LIST_REMOVE_HEAD(&f->blnumbers, entry)))
      /* Free the blacklisted number */
      ast_free(prev);
   AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);

   while ((prev = AST_LIST_REMOVE_HEAD(&f->wlnumbers, entry)))
      /* Free the whitelisted number */
      ast_free(prev);
   AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
}
static void init_profile ( struct call_followme f) [static]

Definition at line 320 of file app_followme.c.

References call_followme::active, ast_copy_string(), and call_followme::moh.

Referenced by reload_followme().

{
   f->active = 1;
   ast_copy_string(f->moh, defaultmoh, sizeof(f->moh));
}
static void profile_set_param ( struct call_followme f,
const char *  param,
const char *  val,
int  linenum,
int  failunknown 
) [static]

Set parameter in profile from configuration file.

Definition at line 329 of file app_followme.c.

References ast_copy_string(), ast_log(), call_followme::callfromprompt, call_followme::context, LOG_WARNING, call_followme::moh, call_followme::name, call_followme::nextindp, call_followme::norecordingprompt, call_followme::optionsprompt, call_followme::plsholdprompt, call_followme::sorryprompt, call_followme::statusprompt, and call_followme::takecall.

Referenced by find_realtime(), and reload_followme().

{

   if (!strcasecmp(param, "musicclass") || !strcasecmp(param, "musiconhold") || !strcasecmp(param, "music")) 
      ast_copy_string(f->moh, val, sizeof(f->moh));
   else if (!strcasecmp(param, "context")) 
      ast_copy_string(f->context, val, sizeof(f->context));
   else if (!strcasecmp(param, "takecall"))
      ast_copy_string(f->takecall, val, sizeof(f->takecall));
   else if (!strcasecmp(param, "declinecall"))
      ast_copy_string(f->nextindp, val, sizeof(f->nextindp));
   else if (!strcasecmp(param, "call-from-prompt") || !strcasecmp(param, "call_from_prompt"))
      ast_copy_string(f->callfromprompt, val, sizeof(f->callfromprompt));
   else if (!strcasecmp(param, "followme-norecording-prompt") || !strcasecmp(param, "norecording_prompt")) 
      ast_copy_string(f->norecordingprompt, val, sizeof(f->norecordingprompt));
   else if (!strcasecmp(param, "followme-options-prompt") || !strcasecmp(param, "options_prompt")) 
      ast_copy_string(f->optionsprompt, val, sizeof(f->optionsprompt));
   else if (!strcasecmp(param, "followme-pls-hold-prompt") || !strcasecmp(param, "pls_hold_prompt"))
      ast_copy_string(f->plsholdprompt, val, sizeof(f->plsholdprompt));
   else if (!strcasecmp(param, "followme-status-prompt") || !strcasecmp(param, "status_prompt")) 
      ast_copy_string(f->statusprompt, val, sizeof(f->statusprompt));
   else if (!strcasecmp(param, "followme-sorry-prompt") || !strcasecmp(param, "sorry_prompt")) 
      ast_copy_string(f->sorryprompt, val, sizeof(f->sorryprompt));
   else if (failunknown) {
      if (linenum >= 0)
         ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s at line %d of followme.conf\n", f->name, param, linenum);
      else
         ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s\n", f->name, param);
   }
}
static int reload ( void  ) [static]

Definition at line 1566 of file app_followme.c.

References reload_followme().

{
   reload_followme(1);

   return 0;
}
static int reload_followme ( int  reload) [static]

Reload followme application module.

Definition at line 381 of file app_followme.c.

References call_followme::active, alloc_profile(), ast_category_browse(), ast_config_destroy(), ast_config_load, ast_copy_string(), ast_debug, AST_LIST_INSERT_TAIL, AST_LIST_TRAVERSE, ast_log(), ast_mutex_lock, ast_mutex_unlock, AST_RWLIST_INSERT_HEAD, AST_RWLIST_TRAVERSE, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, ast_strlen_zero(), ast_variable_browse(), ast_variable_retrieve(), CONFIG_FLAG_FILEUNCHANGED, CONFIG_STATUS_FILEINVALID, CONFIG_STATUS_FILEUNCHANGED, create_followme_number(), f, free_numbers(), init_profile(), ast_variable::lineno, call_followme::lock, LOG_ERROR, LOG_WARNING, ast_variable::name, call_followme::name, ast_variable::next, call_followme::numbers, profile_set_param(), number::timeout, ast_variable::value, and var.

Referenced by load_module(), and reload().

{
   struct call_followme *f;
   struct ast_config *cfg;
   char *cat = NULL, *tmp;
   struct ast_variable *var;
   struct number *cur, *nm;
   char *numberstr;
   int timeout;
   int numorder;
   const char *takecallstr;
   const char *declinecallstr;
   const char *tmpstr;
   struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };

   if (!(cfg = ast_config_load("followme.conf", config_flags))) {
      ast_log(LOG_WARNING, "No follow me config file (followme.conf), so no follow me\n");
      return 0;
   } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
      return 0;
   } else if (cfg == CONFIG_STATUS_FILEINVALID) {
      ast_log(LOG_ERROR, "Config file followme.conf is in an invalid format.  Aborting.\n");
      return 0;
   }

   AST_RWLIST_WRLOCK(&followmes);

   /* Reset Global Var Values */
   featuredigittimeout = 5000;

   /* Mark all profiles as inactive for the moment */
   AST_RWLIST_TRAVERSE(&followmes, f, entry) {
      f->active = 0;
   }

   featuredigittostr = ast_variable_retrieve(cfg, "general", "featuredigittimeout");

   if (!ast_strlen_zero(featuredigittostr)) {
      if (!sscanf(featuredigittostr, "%30d", &featuredigittimeout))
         featuredigittimeout = 5000;
   }

   if ((takecallstr = ast_variable_retrieve(cfg, "general", "takecall")) && !ast_strlen_zero(takecallstr)) {
      ast_copy_string(takecall, takecallstr, sizeof(takecall));
   }

   if ((declinecallstr = ast_variable_retrieve(cfg, "general", "declinecall")) && !ast_strlen_zero(declinecallstr)) {
      ast_copy_string(nextindp, declinecallstr, sizeof(nextindp));
   }

   if ((tmpstr = ast_variable_retrieve(cfg, "general", "call-from-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "call_from_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
   }

   if ((tmpstr = ast_variable_retrieve(cfg, "general", "norecording-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "norecording_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
   }


   if ((tmpstr = ast_variable_retrieve(cfg, "general", "options-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "options_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
   }

   if ((tmpstr = ast_variable_retrieve(cfg, "general", "pls-hold-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "pls_hold_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
   }

   if ((tmpstr = ast_variable_retrieve(cfg, "general", "status-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "status_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
   }

   if ((tmpstr = ast_variable_retrieve(cfg, "general", "sorry-prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
   } else if ((tmpstr = ast_variable_retrieve(cfg, "general", "sorry_prompt")) && !ast_strlen_zero(tmpstr)) {
      ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
   }

   /* Chug through config file */
   while ((cat = ast_category_browse(cfg, cat))) {
      int new = 0;

      if (!strcasecmp(cat, "general"))
         continue;

      /* Look for an existing one */
      AST_LIST_TRAVERSE(&followmes, f, entry) {
         if (!strcasecmp(f->name, cat))
            break;
      }

      ast_debug(1, "New profile %s.\n", cat);

      if (!f) {
         /* Make one then */
         f = alloc_profile(cat);
         new = 1;
      }

      /* Totally fail if we fail to find/create an entry */
      if (!f)
         continue;

      if (!new)
         ast_mutex_lock(&f->lock);
      /* Re-initialize the profile */
      init_profile(f);
      free_numbers(f);
      var = ast_variable_browse(cfg, cat);
      while (var) {
         if (!strcasecmp(var->name, "number")) {
            int idx = 0;

            /* Add a new number */
            numberstr = ast_strdupa(var->value);
            if ((tmp = strchr(numberstr, ','))) {
               *tmp++ = '\0';
               timeout = atoi(tmp);
               if (timeout < 0) {
                  timeout = 25;
               }
               if ((tmp = strchr(tmp, ','))) {
                  *tmp++ = '\0';
                  numorder = atoi(tmp);
                  if (numorder < 0)
                     numorder = 0;
               } else 
                  numorder = 0;
            } else {
               timeout = 25;
               numorder = 0;
            }

            if (!numorder) {
               idx = 1;
               AST_LIST_TRAVERSE(&f->numbers, nm, entry) 
                  idx++;
               numorder = idx;
            }
            cur = create_followme_number(numberstr, timeout, numorder);
            if (cur) {
               AST_LIST_INSERT_TAIL(&f->numbers, cur, entry);
            }
         } else {
            profile_set_param(f, var->name, var->value, var->lineno, 1);
            ast_debug(2, "Logging parameter %s with value %s from lineno %d\n", var->name, var->value, var->lineno);
         }
         var = var->next;
      } /* End while(var) loop */

      if (!new) 
         ast_mutex_unlock(&f->lock);
      else
         AST_RWLIST_INSERT_HEAD(&followmes, f, entry);
   }

   ast_config_destroy(cfg);

   AST_RWLIST_UNLOCK(&followmes);

   return 1;
}
static int unload_module ( void  ) [static]
static struct ast_channel* wait_for_winner ( struct findme_user_listptr findme_user_list,
struct number nm,
struct ast_channel caller,
struct fm_args tpargs 
) [static, read]

Definition at line 617 of file app_followme.c.

References findme_user::answered, ARRAY_LEN, AST_CAUSE_NORMAL_CLEARING, ast_channel_hangupcause_set(), ast_channel_language(), ast_channel_name(), ast_channel_sched(), ast_channel_stream(), ast_channel_timingfunc(), ast_connected_line_parse_data(), AST_CONTROL_ANSWER, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION, AST_CONTROL_CONNECTED_LINE, AST_CONTROL_FLASH, AST_CONTROL_HANGUP, AST_CONTROL_HOLD, AST_CONTROL_OFFHOOK, AST_CONTROL_PROCEEDING, AST_CONTROL_PROGRESS, AST_CONTROL_PVT_CAUSE_CODE, AST_CONTROL_REDIRECTING, AST_CONTROL_RINGING, AST_CONTROL_SRCUPDATE, AST_CONTROL_UNHOLD, AST_CONTROL_VIDUPDATE, ast_copy_string(), ast_debug, AST_FRAME_CONTROL, AST_FRAME_DTMF, ast_frfree, ast_indicate_data(), AST_LIST_TRAVERSE, ast_log(), ast_party_connected_line_free(), ast_party_connected_line_set(), ast_party_connected_line_set_init(), ast_read(), ast_sched_runq(), ast_sched_wait(), ast_stopstream(), ast_streamfile(), ast_strlen_zero(), ast_test_flag, ast_verb, ast_waitfor_n(), fm_args::callfromprompt, clear_caller(), clear_unanswered_calls(), findme_user::connected, fm_args::connected_in, ast_frame::data, ast_frame::datalen, findme_user::digts, f, FOLLOWMEFLAG_IGNORE_CONNECTEDLINE, fm_args::followmeflags, ast_frame::frametype, ast_frame_subclass::integer, LOG_NOTICE, LOG_WARNING, fm_args::namerecloc, fm_args::nextindp, fm_args::norecordingprompt, findme_user::ochan, fm_args::optionsprompt, findme_user::pending_connected_update, fm_args::pending_hold, fm_args::pending_in_connected_update, ast_frame::ptr, findme_user::state, ast_frame::subclass, fm_args::suggested_moh, fm_args::takecall, number::timeout, ast_frame::uint32, findme_user::yn, and findme_user::ynidx.

Referenced by findmeexec().

{
   struct ast_party_connected_line connected;
   struct ast_channel *watchers[256];
   int pos;
   struct ast_channel *winner;
   struct ast_frame *f;
   struct findme_user *tmpuser;
   int to = 0;
   int livechannels;
   int tmpto;
   long totalwait = 0, wtd = 0, towas = 0;
   char *callfromname;
   char *pressbuttonname;

   /* ------------ wait_for_winner_channel start --------------- */ 

   callfromname = ast_strdupa(tpargs->callfromprompt);
   pressbuttonname = ast_strdupa(tpargs->optionsprompt);

   totalwait = nm->timeout * 1000;

   for (;;) {
      to = 1000;
      pos = 1; 
      livechannels = 0;
      watchers[0] = caller;

      winner = NULL;
      AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
         if (!tmpuser->ochan) {
            continue;
         }
         if (tmpuser->state == 3) {
            tmpuser->digts += (towas - wtd);
         }
         if (tmpuser->digts && (tmpuser->digts > featuredigittimeout)) {
            ast_verb(3, "<%s> We've been waiting for digits longer than we should have.\n",
               ast_channel_name(tmpuser->ochan));
            if (!ast_strlen_zero(tpargs->namerecloc)) {
               tmpuser->state = 1;
               tmpuser->digts = 0;
               if (!ast_streamfile(tmpuser->ochan, callfromname, ast_channel_language(tmpuser->ochan))) {
                  ast_sched_runq(ast_channel_sched(tmpuser->ochan));
               } else {
                  ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
                  clear_caller(tmpuser);
                  continue;
               }
            } else {
               tmpuser->state = 2;
               tmpuser->digts = 0;
               if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, ast_channel_language(tmpuser->ochan)))
                  ast_sched_runq(ast_channel_sched(tmpuser->ochan));
               else {
                  ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
                  clear_caller(tmpuser);
                  continue;
               }
            }
         }
         if (ast_channel_stream(tmpuser->ochan)) {
            ast_sched_runq(ast_channel_sched(tmpuser->ochan));
            tmpto = ast_sched_wait(ast_channel_sched(tmpuser->ochan));
            if (tmpto > 0 && tmpto < to)
               to = tmpto;
            else if (tmpto < 0 && !ast_channel_timingfunc(tmpuser->ochan)) {
               ast_stopstream(tmpuser->ochan);
               switch (tmpuser->state) {
               case 1:
                  ast_verb(3, "<%s> Playback of the call-from file appears to be done.\n",
                     ast_channel_name(tmpuser->ochan));
                  if (!ast_streamfile(tmpuser->ochan, tpargs->namerecloc, ast_channel_language(tmpuser->ochan))) {
                     tmpuser->state = 2;
                  } else {
                     ast_log(LOG_NOTICE, "<%s> Unable to playback %s. Maybe the caller didn't record their name?\n",
                        ast_channel_name(tmpuser->ochan), tpargs->namerecloc);
                     memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
                     tmpuser->ynidx = 0;
                     if (!ast_streamfile(tmpuser->ochan, pressbuttonname, ast_channel_language(tmpuser->ochan)))
                        tmpuser->state = 3;
                     else {
                        ast_log(LOG_WARNING, "Unable to playback %s.\n", pressbuttonname);
                        clear_caller(tmpuser);
                        continue;
                     }
                  }
                  break;
               case 2:
                  ast_verb(3, "<%s> Playback of name file appears to be done.\n",
                     ast_channel_name(tmpuser->ochan));
                  memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
                  tmpuser->ynidx = 0;
                  if (!ast_streamfile(tmpuser->ochan, pressbuttonname, ast_channel_language(tmpuser->ochan))) {
                     tmpuser->state = 3;
                  } else {
                     clear_caller(tmpuser);
                     continue;
                  }
                  break;
               case 3:
                  ast_verb(3, "<%s> Playback of the next step file appears to be done.\n",
                     ast_channel_name(tmpuser->ochan));
                  tmpuser->digts = 0;
                  break;
               default:
                  break;
               }
            }
         }
         watchers[pos++] = tmpuser->ochan;
         livechannels++;
      }
      if (!livechannels) {
         ast_verb(3, "No live channels left for this step.\n");
         return NULL;
      }

      tmpto = to;
      if (to < 0) {
         to = 1000;
         tmpto = 1000;
      }
      towas = to;
      winner = ast_waitfor_n(watchers, pos, &to);
      tmpto -= to;
      totalwait -= tmpto;
      wtd = to;
      if (totalwait <= 0) {
         ast_verb(3, "We've hit our timeout for this step. Dropping unanswered calls and starting the next step.\n");
         clear_unanswered_calls(findme_user_list);
         return NULL;
      }
      if (winner) {
         /* Need to find out which channel this is */
         if (winner != caller) {
            /* The winner is an outgoing channel. */
            AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
               if (tmpuser->ochan == winner) {
                  break;
               }
            }
         } else {
            tmpuser = NULL;
         }

         f = ast_read(winner);
         if (f) {
            if (f->frametype == AST_FRAME_CONTROL) {
               switch (f->subclass.integer) {
               case AST_CONTROL_HANGUP:
                  ast_verb(3, "%s received a hangup frame.\n", ast_channel_name(winner));
                  if (f->data.uint32) {
                     ast_channel_hangupcause_set(winner, f->data.uint32);
                  }
                  if (!tmpuser) {
                     ast_verb(3, "The calling channel hungup. Need to drop everyone.\n");
                     ast_frfree(f);
                     return NULL;
                  }
                  clear_caller(tmpuser);
                  break;
               case AST_CONTROL_ANSWER:
                  if (!tmpuser) {
                     /* The caller answered?  We want an outgoing channel to answer. */
                     break;
                  }
                  ast_verb(3, "%s answered %s\n", ast_channel_name(winner), ast_channel_name(caller));
                  tmpuser->answered = 1;
                  /* If call has been answered, then the eventual hangup is likely to be normal hangup */ 
                  ast_channel_hangupcause_set(winner, AST_CAUSE_NORMAL_CLEARING);
                  ast_channel_hangupcause_set(caller, AST_CAUSE_NORMAL_CLEARING);
                  ast_verb(3, "Starting playback of %s\n", callfromname);
                  if (!ast_strlen_zero(tpargs->namerecloc)) {
                     if (!ast_streamfile(winner, callfromname, ast_channel_language(winner))) {
                        ast_sched_runq(ast_channel_sched(winner));
                        tmpuser->state = 1;
                     } else {
                        ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
                        clear_caller(tmpuser);
                     }
                  } else {
                     tmpuser->state = 2;
                     if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, ast_channel_language(tmpuser->ochan)))
                        ast_sched_runq(ast_channel_sched(tmpuser->ochan));
                     else {
                        ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
                        clear_caller(tmpuser);
                     }
                  }
                  break;
               case AST_CONTROL_BUSY:
                  ast_verb(3, "%s is busy\n", ast_channel_name(winner));
                  if (tmpuser) {
                     /* Outbound call was busy.  Drop it. */
                     clear_caller(tmpuser);
                  }
                  break;
               case AST_CONTROL_CONGESTION:
                  ast_verb(3, "%s is circuit-busy\n", ast_channel_name(winner));
                  if (tmpuser) {
                     /* Outbound call was congested.  Drop it. */
                     clear_caller(tmpuser);
                  }
                  break;
               case AST_CONTROL_RINGING:
                  ast_verb(3, "%s is ringing\n", ast_channel_name(winner));
                  break;
               case AST_CONTROL_PROGRESS:
                  ast_verb(3, "%s is making progress\n", ast_channel_name(winner));
                  break;
               case AST_CONTROL_VIDUPDATE:
                  ast_verb(3, "%s requested a video update\n", ast_channel_name(winner));
                  break;
               case AST_CONTROL_SRCUPDATE:
                  ast_verb(3, "%s requested a source update\n", ast_channel_name(winner));
                  break;
               case AST_CONTROL_PROCEEDING:
                  ast_verb(3, "%s is proceeding\n", ast_channel_name(winner));
                  break;
               case AST_CONTROL_HOLD:
                  ast_verb(3, "%s placed call on hold\n", ast_channel_name(winner));
                  if (!tmpuser) {
                     /* Caller placed outgoing calls on hold. */
                     tpargs->pending_hold = 1;
                     if (f->data.ptr) {
                        ast_copy_string(tpargs->suggested_moh, f->data.ptr,
                           sizeof(tpargs->suggested_moh));
                     } else {
                        tpargs->suggested_moh[0] = '\0';
                     }
                  } else {
                     /*
                      * Outgoing call placed caller on hold.
                      *
                      * Ignore because the outgoing call should not be able to place
                      * the caller on hold until after they are bridged.
                      */
                  }
                  break;
               case AST_CONTROL_UNHOLD:
                  ast_verb(3, "%s removed call from hold\n", ast_channel_name(winner));
                  if (!tmpuser) {
                     /* Caller removed outgoing calls from hold. */
                     tpargs->pending_hold = 0;
                  } else {
                     /*
                      * Outgoing call removed caller from hold.
                      *
                      * Ignore because the outgoing call should not be able to place
                      * the caller on hold until after they are bridged.
                      */
                  }
                  break;
               case AST_CONTROL_OFFHOOK:
               case AST_CONTROL_FLASH:
                  /* Ignore going off hook and flash */
                  break;
               case AST_CONTROL_CONNECTED_LINE:
                  if (!tmpuser) {
                     /*
                      * Hold connected line update from caller until we have a
                      * winner.
                      */
                     ast_verb(3,
                        "%s connected line has changed. Saving it until we have a winner.\n",
                        ast_channel_name(winner));
                     ast_party_connected_line_set_init(&connected, &tpargs->connected_in);
                     if (!ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected)) {
                        ast_party_connected_line_set(&tpargs->connected_in,
                           &connected, NULL);
                        tpargs->pending_in_connected_update = 1;
                     }
                     ast_party_connected_line_free(&connected);
                     break;
                  }
                  if (ast_test_flag(&tpargs->followmeflags, FOLLOWMEFLAG_IGNORE_CONNECTEDLINE)) {
                     ast_verb(3, "Connected line update from %s prevented.\n",
                        ast_channel_name(winner));
                  } else {
                     ast_verb(3,
                        "%s connected line has changed. Saving it until answer.\n",
                        ast_channel_name(winner));
                     ast_party_connected_line_set_init(&connected, &tmpuser->connected);
                     if (!ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected)) {
                        ast_party_connected_line_set(&tmpuser->connected,
                           &connected, NULL);
                        tmpuser->pending_connected_update = 1;
                     }
                     ast_party_connected_line_free(&connected);
                  }
                  break;
               case AST_CONTROL_REDIRECTING:
                  /*
                   * Ignore because we are masking the FollowMe search progress to
                   * the caller.
                   */
                  break;
               case AST_CONTROL_PVT_CAUSE_CODE:
                  ast_indicate_data(caller, f->subclass.integer, f->data.ptr, f->datalen);
                  break;
               case -1:
                  ast_verb(3, "%s stopped sounds\n", ast_channel_name(winner));
                  break;
               default:
                  ast_debug(1, "Dunno what to do with control type %d from %s\n",
                     f->subclass.integer, ast_channel_name(winner));
                  break;
               }
            } 
            if (tmpuser && tmpuser->state == 3 && f->frametype == AST_FRAME_DTMF) {
               int cmp_len;

               if (ast_channel_stream(winner))
                  ast_stopstream(winner);
               tmpuser->digts = 0;
               ast_debug(1, "DTMF received: %c\n", (char) f->subclass.integer);
               if (tmpuser->ynidx < ARRAY_LEN(tmpuser->yn) - 1) {
                  tmpuser->yn[tmpuser->ynidx++] = f->subclass.integer;
               } else {
                  /* Discard oldest digit. */
                  memmove(tmpuser->yn, tmpuser->yn + 1,
                     sizeof(tmpuser->yn) - 2 * sizeof(tmpuser->yn[0]));
                  tmpuser->yn[ARRAY_LEN(tmpuser->yn) - 2] = f->subclass.integer;
               }
               ast_debug(1, "DTMF string: %s\n", tmpuser->yn);
               cmp_len = strlen(tpargs->takecall);
               if (cmp_len <= tmpuser->ynidx
                  && !strcmp(tmpuser->yn + (tmpuser->ynidx - cmp_len), tpargs->takecall)) {
                  ast_debug(1, "Match to take the call!\n");
                  ast_frfree(f);
                  return tmpuser->ochan;
               }
               cmp_len = strlen(tpargs->nextindp);
               if (cmp_len <= tmpuser->ynidx
                  && !strcmp(tmpuser->yn + (tmpuser->ynidx - cmp_len), tpargs->nextindp)) {
                  ast_debug(1, "Declined to take the call.\n");
                  clear_caller(tmpuser);
               }
            }

            ast_frfree(f);
         } else {
            ast_debug(1, "we didn't get a frame. hanging up.\n");
            if (!tmpuser) {
               /* Caller hung up. */
               ast_verb(3, "The calling channel hungup. Need to drop everyone.\n");
               return NULL;
            }
            /* Outgoing channel hung up. */
            clear_caller(tmpuser);
         }
      } else {
         ast_debug(1, "timed out waiting for action\n");
      }
   }

   /* Unreachable. */
}

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Find-Me/Follow-Me Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, } [static]

Definition at line 1577 of file app_followme.c.

char* app = "FollowMe" [static]

Definition at line 140 of file app_followme.c.

Definition at line 1577 of file app_followme.c.

char callfromprompt[PATH_MAX] = "followme/call-from" [static]

Definition at line 261 of file app_followme.c.

const char* defaultmoh = "default" [static]

Default Music-On-Hold Class

Definition at line 257 of file app_followme.c.

int featuredigittimeout = 5000 [static]

Feature Digit Timeout

Definition at line 256 of file app_followme.c.

const char* featuredigittostr [static]

Definition at line 255 of file app_followme.c.

struct ast_app_option followme_opts[128] = { [ 'a' ] = { .flag = FOLLOWMEFLAG_RECORDNAME }, [ 'B' ] = { .flag = FOLLOWMEFLAG_PREDIAL_CALLER , .arg_index = FOLLOWMEFLAG_ARG_PREDIAL_CALLER + 1 }, [ 'b' ] = { .flag = FOLLOWMEFLAG_PREDIAL_CALLEE , .arg_index = FOLLOWMEFLAG_ARG_PREDIAL_CALLEE + 1 }, [ 'd' ] = { .flag = FOLLOWMEFLAG_DISABLEHOLDPROMPT }, [ 'I' ] = { .flag = FOLLOWMEFLAG_IGNORE_CONNECTEDLINE }, [ 'l' ] = { .flag = FOLLOWMEFLAG_DISABLEOPTIMIZATION }, [ 'N' ] = { .flag = FOLLOWMEFLAG_NOANSWER }, [ 'n' ] = { .flag = FOLLOWMEFLAG_UNREACHABLEMSG }, [ 's' ] = { .flag = FOLLOWMEFLAG_STATUSMSG },} [static]

Definition at line 253 of file app_followme.c.

Referenced by app_exec().

struct followmes followmes [static]
char nextindp[MAX_YN_STRING] = "2" [static]

Definition at line 260 of file app_followme.c.

char norecordingprompt[PATH_MAX] = "followme/no-recording" [static]

Definition at line 262 of file app_followme.c.

char optionsprompt[PATH_MAX] = "followme/options" [static]

Definition at line 263 of file app_followme.c.

char plsholdprompt[PATH_MAX] = "followme/pls-hold-while-try" [static]

Definition at line 264 of file app_followme.c.

char sorryprompt[PATH_MAX] = "followme/sorry" [static]

Definition at line 266 of file app_followme.c.

char statusprompt[PATH_MAX] = "followme/status" [static]

Definition at line 265 of file app_followme.c.

char takecall[MAX_YN_STRING] = "1" [static]

Definition at line 259 of file app_followme.c.