Merge changes from team/russell/console_devices
authorRussell Bryant <russell@russellbryant.com>
Sun, 20 Jan 2008 06:11:49 +0000 (06:11 +0000)
committerRussell Bryant <russell@russellbryant.com>
Sun, 20 Jan 2008 06:11:49 +0000 (06:11 +0000)
 - Add support for multiple devices.  All devices are configured in console.conf.
 - Add "console list devices" CLI command to show configured devices.  Also, changed
 the old "list devices" to be "list available", which queries PortAudio for all
 audio devices that are available for use.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@99227 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_console.c
configs/console.conf.sample

index 58f456c..59bdcac 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2006 - 2007, Digium, Inc.
+ * Copyright (C) 2006 - 2008, Digium, Inc.
  *
  * Russell Bryant <russell@digium.com>
  *
@@ -68,6 +68,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/musiconhold.h"
 #include "asterisk/callerid.h"
+#include "asterisk/astobj2.h"
 
 /*! 
  * \brief The sample rate to request from PortAudio 
@@ -126,6 +127,8 @@ static struct console_pvt {
        AST_DECLARE_STRING_FIELDS(
                /*! Name of the device */
                AST_STRING_FIELD(name);
+               AST_STRING_FIELD(input_device);
+               AST_STRING_FIELD(output_device);
                /*! Default context for outgoing calls */
                AST_STRING_FIELD(context);
                /*! Default extension for outgoing calls */
@@ -157,14 +160,20 @@ static struct console_pvt {
        unsigned int autoanswer:1;
        /*! Ignore context in the console dial CLI command */
        unsigned int overridecontext:1;
-       /*! Lock to protect data in this struct */
-       ast_mutex_t __lock;
+       /*! Set during a reload so that we know to destroy this if it is no longer
+        *  in the configuration file. */
+       unsigned int destroy:1;
        /*! ID for the stream monitor thread */
        pthread_t thread;
-} console_pvt = {
-       .__lock = AST_MUTEX_INIT_VALUE,
-       .thread = AST_PTHREADT_NULL,
-};
+} globals;
+
+AST_MUTEX_DEFINE_STATIC(globals_lock);
+
+static struct ao2_container *pvts;
+#define NUM_PVT_BUCKETS 7
+
+static struct console_pvt *active_pvt;
+AST_RWLOCK_DEFINE_STATIC(active_lock);
 
 /*! 
  * \brief Global jitterbuffer configuration 
@@ -218,10 +227,32 @@ static const struct ast_channel_tech console_tech = {
 };
 
 /*! \brief lock a console_pvt struct */
-#define console_pvt_lock(pvt) ast_mutex_lock(&(pvt)->__lock)
+#define console_pvt_lock(pvt) ao2_lock(pvt)
 
 /*! \brief unlock a console_pvt struct */
-#define console_pvt_unlock(pvt) ast_mutex_unlock(&(pvt)->__lock)
+#define console_pvt_unlock(pvt) ao2_unlock(pvt)
+
+static inline struct console_pvt *ref_pvt(struct console_pvt *pvt)
+{
+       if (pvt)
+               ao2_ref(pvt, +1);
+       return pvt;
+}
+
+static inline struct console_pvt *unref_pvt(struct console_pvt *pvt)
+{
+       ao2_ref(pvt, -1);
+       return NULL;
+}
+
+static struct console_pvt *find_pvt(const char *name)
+{
+       struct console_pvt tmp_pvt = {
+               .name = name,
+       };
+
+       return ao2_find(pvts, &tmp_pvt, OBJ_POINTER);
+}
 
 /*!
  * \brief Stream monitor thread 
@@ -259,6 +290,67 @@ static void *stream_monitor(void *data)
        return NULL;
 }
 
+static int open_stream(struct console_pvt *pvt)
+{
+       int res = paInternalError;
+
+       if (!strcasecmp(pvt->input_device, "default") && 
+               !strcasecmp(pvt->output_device, "default")) {
+               res = Pa_OpenDefaultStream(&pvt->stream, INPUT_CHANNELS, OUTPUT_CHANNELS, 
+                       paInt16, SAMPLE_RATE, NUM_SAMPLES, NULL, NULL);
+       } else {
+               PaStreamParameters input_params = { 
+                       .channelCount = 1,
+                       .sampleFormat = paInt16,
+                       .suggestedLatency = (1.0 / 50.0), /* 20 ms */
+                       .device = paNoDevice,
+               };
+               PaStreamParameters output_params = { 
+                       .channelCount = 1, 
+                       .sampleFormat = paInt16,
+                       .suggestedLatency = (1.0 / 50.0), /* 20 ms */
+                       .device = paNoDevice,
+               };
+               PaDeviceIndex index, num_devices, def_input, def_output;
+
+               if (!(num_devices = Pa_GetDeviceCount()))
+                       return res;
+
+               def_input = Pa_GetDefaultInputDevice();
+               def_output = Pa_GetDefaultOutputDevice();
+
+               for (index = 0; 
+                       index < num_devices && (input_params.device == paNoDevice 
+                               || output_params.device == paNoDevice); 
+                       index++) 
+               {
+                       const PaDeviceInfo *dev = Pa_GetDeviceInfo(index);
+
+                       if (dev->maxInputChannels) {
+                               if ( (index == def_input && !strcasecmp(pvt->input_device, "default")) ||
+                                       !strcasecmp(pvt->input_device, dev->name) )
+                                       input_params.device = index;
+                       }
+
+                       if (dev->maxOutputChannels) {
+                               if ( (index == def_output && !strcasecmp(pvt->output_device, "default")) ||
+                                       !strcasecmp(pvt->output_device, dev->name) )
+                                       output_params.device = index;
+                       }
+               }
+
+               if (input_params.device == paNoDevice)
+                       ast_log(LOG_ERROR, "No input device found for console device '%s'\n", pvt->name);
+               if (output_params.device == paNoDevice)
+                       ast_log(LOG_ERROR, "No output device found for console device '%s'\n", pvt->name);
+
+               res = Pa_OpenStream(&pvt->stream, &input_params, &output_params,
+                       SAMPLE_RATE, NUM_SAMPLES, paNoFlag, NULL, NULL);
+       }
+
+       return res;
+}
+
 static int start_stream(struct console_pvt *pvt)
 {
        PaError res;
@@ -272,10 +364,9 @@ static int start_stream(struct console_pvt *pvt)
        pvt->streamstate = 1;
        ast_debug(1, "Starting stream\n");
 
-       res = Pa_OpenDefaultStream(&pvt->stream, INPUT_CHANNELS, OUTPUT_CHANNELS, 
-               paInt16, SAMPLE_RATE, NUM_SAMPLES, NULL, NULL);
+       res = open_stream(pvt);
        if (res != paNoError) {
-               ast_log(LOG_WARNING, "Failed to open default audio device - (%d) %s\n",
+               ast_log(LOG_WARNING, "Failed to open stream - (%d) %s\n",
                        res, Pa_GetErrorText(res));
                ret_val = -1;
                goto return_unlock;
@@ -335,7 +426,7 @@ static struct ast_channel *console_new(struct console_pvt *pvt, const char *ext,
        chan->nativeformats = AST_FORMAT_SLINEAR16;
        chan->readformat = AST_FORMAT_SLINEAR16;
        chan->writeformat = AST_FORMAT_SLINEAR16;
-       chan->tech_pvt = pvt;
+       chan->tech_pvt = ref_pvt(pvt);
 
        pvt->owner = chan;
 
@@ -359,19 +450,24 @@ static struct ast_channel *console_new(struct console_pvt *pvt, const char *ext,
 static struct ast_channel *console_request(const char *type, int format, void *data, int *cause)
 {
        int oldformat = format;
-       struct ast_channel *chan;
-       struct console_pvt *pvt = &console_pvt;
+       struct ast_channel *chan = NULL;
+       struct console_pvt *pvt;
+
+       if (!(pvt = find_pvt(data))) {
+               ast_log(LOG_ERROR, "Console device '%s' not found\n", (char *) data);
+               return NULL;
+       }
 
        format &= SUPPORTED_FORMATS;
        if (!format) {
                ast_log(LOG_NOTICE, "Channel requested with unsupported format(s): '%d'\n", oldformat);
-               return NULL;
+               goto return_unref;
        }
 
        if (pvt->owner) {
                ast_log(LOG_NOTICE, "Console channel already active!\n");
                *cause = AST_CAUSE_BUSY;
-               return NULL;
+               goto return_unref;
        }
 
        console_pvt_lock(pvt);
@@ -381,6 +477,9 @@ static struct ast_channel *console_request(const char *type, int format, void *d
        if (!chan)
                ast_log(LOG_WARNING, "Unable to create new Console channel!\n");
 
+return_unref:
+       unref_pvt(pvt);
+
        return chan;
 }
 
@@ -408,22 +507,22 @@ static int console_text(struct ast_channel *c, const char *text)
 
 static int console_hangup(struct ast_channel *c)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = c->tech_pvt;
 
        ast_verb(1, V_BEGIN "Hangup on Console" V_END);
 
        pvt->hookstate = 0;
-       c->tech_pvt = NULL;
        pvt->owner = NULL;
-
        stop_stream(pvt);
 
+       c->tech_pvt = unref_pvt(pvt);
+
        return 0;
 }
 
 static int console_answer(struct ast_channel *c)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = c->tech_pvt;
 
        ast_verb(1, V_BEGIN "Call from Console has been Answered" V_END);
 
@@ -462,7 +561,7 @@ static struct ast_frame *console_read(struct ast_channel *chan)
 static int console_call(struct ast_channel *c, char *dest, int timeout)
 {
        struct ast_frame f = { 0, };
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = c->tech_pvt;
 
        ast_verb(1, V_BEGIN "Call to device '%s' on console from '%s' <%s>" V_END,
                dest, c->cid.cid_name, c->cid.cid_num);
@@ -491,7 +590,7 @@ static int console_call(struct ast_channel *c, char *dest, int timeout)
 
 static int console_write(struct ast_channel *chan, struct ast_frame *f)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = chan->tech_pvt;
 
        Pa_WriteStream(pvt->stream, f->data, f->samples);
 
@@ -534,7 +633,7 @@ static int console_indicate(struct ast_channel *chan, int cond, const void *data
 
 static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = newchan->tech_pvt;
 
        pvt->owner = newchan;
 
@@ -575,10 +674,22 @@ static char *ast_ext_ctx(struct console_pvt *pvt, const char *src, char **ext, c
        return *ext;
 }
 
+static struct console_pvt *get_active_pvt(void)
+{
+       struct console_pvt *pvt;
+
+       ast_rwlock_rdlock(&active_lock);
+       pvt = ref_pvt(active_pvt);      
+       ast_rwlock_unlock(&active_lock);
+
+       return pvt;
+}
+
 static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd, 
        struct ast_cli_args *a)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
+       char *res = CLI_SUCCESS;
 
        switch (cmd) {
        case CLI_INIT:
@@ -594,18 +705,20 @@ static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd,
                return NULL;
        }
 
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active.\n");
+               return CLI_FAILURE;
+       }
+
        if (a->argc == e->args - 1) {
                ast_cli(a->fd, "Auto answer is %s.\n", pvt->autoanswer ? "on" : "off");
+               unref_pvt(pvt);
                return CLI_SUCCESS;
        }
 
-       if (a->argc != e->args)
+       if (a->argc != e->args) {
+               unref_pvt(pvt);
                return CLI_SHOWUSAGE;
-
-       if (!pvt) {
-               ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
-                       pvt->name);
-               return CLI_FAILURE;
        }
 
        if (!strcasecmp(a->argv[e->args-1], "on"))
@@ -613,7 +726,9 @@ static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd,
        else if (!strcasecmp(a->argv[e->args - 1], "off"))
                pvt->autoanswer = 0;
        else
-               return CLI_SHOWUSAGE;
+               res = CLI_SHOWUSAGE;
+
+       unref_pvt(pvt);
 
        return CLI_SUCCESS;
 }
@@ -621,7 +736,7 @@ static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd,
 static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH };
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
 
        if (cmd == CLI_INIT) {
                e->command = "console flash";
@@ -632,11 +747,17 @@ static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_
        } else if (cmd == CLI_GENERATE)
                return NULL;
 
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active\n");
+               return CLI_FAILURE;
+       }
+
        if (a->argc != e->args)
                return CLI_SHOWUSAGE;
 
        if (!pvt->owner) {
                ast_cli(a->fd, "No call to flash\n");
+               unref_pvt(pvt);
                return CLI_FAILURE;
        }
 
@@ -644,6 +765,8 @@ static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_
 
        ast_queue_frame(pvt->owner, &f);
 
+       unref_pvt(pvt);
+
        return CLI_SUCCESS;
 }
 
@@ -651,7 +774,7 @@ static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 {
        char *s = NULL;
        const char *mye = NULL, *myc = NULL; 
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
 
        if (cmd == CLI_INIT) {
                e->command = "console dial";
@@ -662,6 +785,11 @@ static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        } else if (cmd == CLI_GENERATE)
                return NULL;
 
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is currently set as active\n");
+               return CLI_FAILURE;
+       }
+       
        if (a->argc > e->args + 1)
                return CLI_SHOWUSAGE;
 
@@ -671,6 +799,7 @@ static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 
                if (a->argc == e->args) {       /* argument is mandatory here */
                        ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
+                       unref_pvt(pvt);
                        return CLI_FAILURE;
                }
                s = a->argv[e->args];
@@ -679,6 +808,7 @@ static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                        f.subclass = s[i];
                        ast_queue_frame(pvt->owner, &f);
                }
+               unref_pvt(pvt);
                return CLI_SUCCESS;
        }
 
@@ -709,12 +839,14 @@ static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        if (s)
                free(s);
 
+       unref_pvt(pvt);
+
        return CLI_SUCCESS;
 }
 
 static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
 
        if (cmd == CLI_INIT) {
                e->command = "console hangup";
@@ -725,11 +857,17 @@ static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli
        } else if (cmd == CLI_GENERATE)
                return NULL;
 
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active\n");
+               return CLI_FAILURE;
+       }
+       
        if (a->argc != e->args)
                return CLI_SHOWUSAGE;
 
        if (!pvt->owner && !pvt->hookstate) {
                ast_cli(a->fd, "No call to hang up\n");
+               unref_pvt(pvt);
                return CLI_FAILURE;
        }
 
@@ -737,14 +875,17 @@ static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli
        if (pvt->owner)
                ast_queue_hangup(pvt->owner);
 
+       unref_pvt(pvt);
+
        return CLI_SUCCESS;
 }
 
 static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        char *s;
-       struct console_pvt *pvt = &console_pvt;
-       
+       struct console_pvt *pvt = get_active_pvt();
+       char *res = CLI_SUCCESS;
+
        if (cmd == CLI_INIT) {
                e->command = "console {mute|unmute}";
                e->usage =
@@ -754,6 +895,11 @@ static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        } else if (cmd == CLI_GENERATE)
                return NULL;
 
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active\n");
+               return CLI_FAILURE;
+       }
+
        if (a->argc != e->args)
                return CLI_SHOWUSAGE;
 
@@ -763,22 +909,24 @@ static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        else if (!strcasecmp(s, "unmute"))
                pvt->muted = 0;
        else
-               return CLI_SHOWUSAGE;
+               res = CLI_SHOWUSAGE;
 
        ast_verb(1, V_BEGIN "The Console is now %s" V_END, 
                pvt->muted ? "Muted" : "Unmuted");
 
-       return CLI_SUCCESS;
+       unref_pvt(pvt);
+
+       return res;
 }
 
-static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *cli_list_available(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        PaDeviceIndex index, num, def_input, def_output;
 
        if (cmd == CLI_INIT) {
-               e->command = "console list devices";
+               e->command = "console list available";
                e->usage =
-                       "Usage: console list devices\n"
+                       "Usage: console list available\n"
                        "       List all available devices.\n";
                return NULL;
        } else if (cmd == CLI_GENERATE)
@@ -819,13 +967,69 @@ static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        return CLI_SUCCESS;
 }
 
+static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ao2_iterator i;
+       struct console_pvt *pvt;
+
+       if (cmd == CLI_INIT) {
+               e->command = "console list devices";
+               e->usage =
+                       "Usage: console list devices\n"
+                       "       List all configured devices.\n";
+               return NULL;
+       } else if (cmd == CLI_GENERATE)
+               return NULL;
+
+       if (a->argc != e->args)
+               return CLI_SHOWUSAGE;
+
+       ast_cli(a->fd, "\n"
+                   "=============================================================\n"
+                   "=== Configured Devices ======================================\n"
+                   "=============================================================\n"
+                   "===\n");
+
+       i = ao2_iterator_init(pvts, 0);
+       while ((pvt = ao2_iterator_next(&i))) {
+               console_pvt_lock(pvt);
+
+               ast_cli(a->fd, "=== ---------------------------------------------------------\n"
+                              "=== Device Name: %s\n"
+                              "=== ---> Active:           %s\n"
+                              "=== ---> Input Device:     %s\n"
+                              "=== ---> Output Device:    %s\n"
+                              "=== ---> Context:          %s\n"
+                              "=== ---> Extension:        %s\n"
+                              "=== ---> CallerID Num:     %s\n"
+                              "=== ---> CallerID Name:    %s\n"
+                              "=== ---> MOH Interpret:    %s\n"
+                              "=== ---> Language:         %s\n"
+                              "=== ---> Muted:            %s\n"
+                              "=== ---> Auto-Answer:      %s\n"
+                              "=== ---> Override Context: %s\n"
+                              "=== ---------------------------------------------------------\n===\n",
+                       pvt->name, (pvt == active_pvt) ? "Yes" : "No",
+                       pvt->input_device, pvt->output_device, pvt->context,
+                       pvt->exten, pvt->cid_num, pvt->cid_name, pvt->mohinterpret,
+                       pvt->language, pvt->muted ? "Yes" : "No", pvt->autoanswer ? "Yes" : "No",
+                       pvt->overridecontext ? "Yes" : "No");
+
+               console_pvt_unlock(pvt);
+               unref_pvt(pvt);
+       }
+
+       ast_cli(a->fd, "=============================================================\n\n");
+
+       return CLI_SUCCESS;
+}
 /*!
  * \brief answer command from the console
  */
 static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
 
        switch (cmd) {
        case CLI_INIT:
@@ -839,11 +1043,19 @@ static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli
                return NULL;    /* no completion */
        }
 
-       if (a->argc != e->args)
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active\n");
+               return CLI_FAILURE;
+       }
+
+       if (a->argc != e->args) {
+               unref_pvt(pvt);
                return CLI_SHOWUSAGE;
+       }
 
        if (!pvt->owner) {
                ast_cli(a->fd, "No one is calling us\n");
+               unref_pvt(pvt);
                return CLI_FAILURE;
        }
 
@@ -853,6 +1065,8 @@ static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli
 
        ast_queue_frame(pvt->owner, &f);
 
+       unref_pvt(pvt);
+
        return CLI_SUCCESS;
 }
 
@@ -865,7 +1079,7 @@ static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli
 static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        char buf[TEXT_SIZE];
-       struct console_pvt *pvt = &console_pvt;
+       struct console_pvt *pvt = get_active_pvt();
        struct ast_frame f = {
                .frametype = AST_FRAME_TEXT,
                .data = buf,
@@ -882,17 +1096,27 @@ static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_c
        } else if (cmd == CLI_GENERATE)
                return NULL;
 
-       if (a->argc < e->args + 1)
+       if (!pvt) {
+               ast_cli(a->fd, "No console device is set as active\n");
+               return CLI_FAILURE;
+       }
+
+       if (a->argc < e->args + 1) {
+               unref_pvt(pvt);
                return CLI_SHOWUSAGE;
+       }
 
        if (!pvt->owner) {
                ast_cli(a->fd, "Not in a call\n");
+               unref_pvt(pvt);
                return CLI_FAILURE;
        }
 
        ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
-       if (ast_strlen_zero(buf))
+       if (ast_strlen_zero(buf)) {
+               unref_pvt(pvt);
                return CLI_SHOWUSAGE;
+       }
 
        len = strlen(buf);
        buf[len] = '\n';
@@ -900,6 +1124,8 @@ static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_c
 
        ast_queue_frame(pvt->owner, &f);
 
+       unref_pvt(pvt);
+
        return CLI_SUCCESS;
 }
 
@@ -911,7 +1137,8 @@ static struct ast_cli_entry cli_console[] = {
        AST_CLI_DEFINE(cli_console_sendtext,   "Send text to a connected party"),
        AST_CLI_DEFINE(cli_console_flash,      "Send a flash to the connected party"),
        AST_CLI_DEFINE(cli_console_autoanswer, "Turn autoanswer on or off"),
-       AST_CLI_DEFINE(cli_list_devices,       "List available devices"),
+       AST_CLI_DEFINE(cli_list_available,     "List available devices"),
+       AST_CLI_DEFINE(cli_list_devices,       "List configured devices"),
 };
 
 /*!
@@ -919,24 +1146,33 @@ static struct ast_cli_entry cli_console[] = {
  *
  * \note This function expects the pvt lock to be held.
  */
-static void set_pvt_defaults(struct console_pvt *pvt, int reload)
+static void set_pvt_defaults(struct console_pvt *pvt)
 {
-       if (!reload) {
-               /* This should be changed for multiple device support.  Right now,
-                * there is no way to change the name of a device.  The default
-                * input and output sound devices are the only ones supported. */
-               ast_string_field_set(pvt, name, "default");
-       }
+       if (pvt == &globals) {
+               ast_string_field_set(pvt, mohinterpret, "default");
+               ast_string_field_set(pvt, context, "default");
+               ast_string_field_set(pvt, exten, "s");
+               ast_string_field_set(pvt, language, "");
+               ast_string_field_set(pvt, cid_num, "");
+               ast_string_field_set(pvt, cid_name, "");
+       
+               pvt->overridecontext = 0;
+               pvt->autoanswer = 0;
+       } else {
+               ast_mutex_lock(&globals_lock);
+
+               ast_string_field_set(pvt, mohinterpret, globals.mohinterpret);
+               ast_string_field_set(pvt, context, globals.context);
+               ast_string_field_set(pvt, exten, globals.exten);
+               ast_string_field_set(pvt, language, globals.language);
+               ast_string_field_set(pvt, cid_num, globals.cid_num);
+               ast_string_field_set(pvt, cid_name, globals.cid_name);
 
-       ast_string_field_set(pvt, mohinterpret, "default");
-       ast_string_field_set(pvt, context, "default");
-       ast_string_field_set(pvt, exten, "s");
-       ast_string_field_set(pvt, language, "");
-       ast_string_field_set(pvt, cid_num, "");
-       ast_string_field_set(pvt, cid_name, "");
+               pvt->overridecontext = globals.overridecontext;
+               pvt->autoanswer = globals.autoanswer;
 
-       pvt->overridecontext = 0;
-       pvt->autoanswer = 0;
+               ast_mutex_unlock(&globals_lock);
+       }
 }
 
 static void store_callerid(struct console_pvt *pvt, const char *value)
@@ -951,6 +1187,23 @@ static void store_callerid(struct console_pvt *pvt, const char *value)
        ast_string_field_set(pvt, cid_num, cid_num);
 }
 
+static void set_active(struct console_pvt *pvt, const char *value)
+{
+       if (pvt == &globals) {
+               ast_log(LOG_ERROR, "active is only valid as a per-device setting\n");
+               return;
+       }
+
+       if (!ast_true(value))
+               return;
+
+       ast_rwlock_wrlock(&active_lock);
+       if (active_pvt)
+               unref_pvt(active_pvt);
+       active_pvt = ref_pvt(pvt);
+       ast_rwlock_unlock(&active_lock);
+}
+
 /*!
  * \brief Store a configuration parameter in a pvt struct
  *
@@ -958,7 +1211,7 @@ static void store_callerid(struct console_pvt *pvt, const char *value)
  */
 static void store_config_core(struct console_pvt *pvt, const char *var, const char *value)
 {
-       if (!ast_jb_read_conf(&global_jbconf, var, value))
+       if (pvt == &globals && !ast_jb_read_conf(&global_jbconf, var, value))
                return;
 
        CV_START(var, value);
@@ -970,12 +1223,91 @@ static void store_config_core(struct console_pvt *pvt, const char *var, const ch
        CV_F("callerid", store_callerid(pvt, value));
        CV_BOOL("overridecontext", pvt->overridecontext);
        CV_BOOL("autoanswer", pvt->autoanswer);
-       
+
+       if (pvt != &globals) {
+               CV_F("active", set_active(pvt, value))
+               CV_STRFIELD("input_device", pvt, input_device);
+               CV_STRFIELD("output_device", pvt, output_device);
+       }
+
        ast_log(LOG_WARNING, "Unknown option '%s'\n", var);
 
        CV_END;
 }
 
+static void pvt_destructor(void *obj)
+{
+       struct console_pvt *pvt = obj;
+
+       ast_string_field_free_memory(pvt);
+}
+
+static int init_pvt(struct console_pvt *pvt, const char *name)
+{
+       pvt->thread = AST_PTHREADT_NULL;
+
+       if (ast_string_field_init(pvt, 32))
+               return -1;
+
+       ast_string_field_set(pvt, name, S_OR(name, ""));
+
+       return 0;
+}
+
+static void build_device(struct ast_config *cfg, const char *name)
+{
+       struct ast_variable *v;
+       struct console_pvt *pvt;
+       int new;
+
+       if ((pvt = find_pvt(name))) {
+               console_pvt_lock(pvt);
+               set_pvt_defaults(pvt);
+               pvt->destroy = 0;
+       } else {
+               if (!(pvt = ao2_alloc(sizeof(*pvt), pvt_destructor)))
+                       return;
+               init_pvt(pvt, name);
+               set_pvt_defaults(pvt);
+               new = 1;
+       }
+
+       for (v = ast_variable_browse(cfg, name); v; v = v->next)
+               store_config_core(pvt, v->name, v->value);
+
+       if (new)
+               ao2_link(pvts, pvt);
+       else
+               console_pvt_unlock(pvt);
+       
+       unref_pvt(pvt);
+}
+
+static int pvt_mark_destroy_cb(void *obj, void *arg, int flags)
+{
+       struct console_pvt *pvt = obj;
+       pvt->destroy = 1;
+       return 0;
+}
+
+static void destroy_pvts(void)
+{
+       struct ao2_iterator i;
+       struct console_pvt *pvt;
+
+       i = ao2_iterator_init(pvts, 0);
+       while ((pvt = ao2_iterator_next(&i))) {
+               if (pvt->destroy) {
+                       ao2_unlink(pvts, pvt);
+                       ast_rwlock_wrlock(&active_lock);
+                       if (active_pvt == pvt)
+                               active_pvt = unref_pvt(pvt);
+                       ast_rwlock_unlock(&active_lock);
+               }
+               unref_pvt(pvt);
+       }
+}
+
 /*!
  * \brief Load the configuration
  * \param reload if this was called due to a reload
@@ -986,67 +1318,79 @@ static int load_config(int reload)
 {
        struct ast_config *cfg;
        struct ast_variable *v;
-       struct console_pvt *pvt = &console_pvt;
        struct ast_flags config_flags = { 0 };
-       int res = -1;
+       char *context = NULL;
 
        /* default values */
        memcpy(&global_jbconf, &default_jbconf, sizeof(global_jbconf));
-
-       console_pvt_lock(pvt);
-
-       set_pvt_defaults(pvt, reload);
+       ast_mutex_lock(&globals_lock);
+       set_pvt_defaults(&globals);
+       ast_mutex_unlock(&globals_lock);
 
        if (!(cfg = ast_config_load(config_file, config_flags))) {
                ast_log(LOG_NOTICE, "Unable to open configuration file %s!\n", config_file);
-               goto return_unlock;
+               return -1;
        }
+       
+       ao2_callback(pvts, 0, pvt_mark_destroy_cb, NULL);
 
+       ast_mutex_lock(&globals_lock);
        for (v = ast_variable_browse(cfg, "general"); v; v = v->next)
-               store_config_core(pvt, v->name, v->value);
+               store_config_core(&globals, v->name, v->value);
+       ast_mutex_unlock(&globals_lock);
+
+       while ((context = ast_category_browse(cfg, context))) {
+               if (strcasecmp(context, "general"))
+                       build_device(cfg, context);
+       }
 
        ast_config_destroy(cfg);
 
-       res = 0;
+       destroy_pvts();
 
-return_unlock:
-       console_pvt_unlock(pvt);
-       return res;
+       return 0;
 }
 
-static int init_pvt(struct console_pvt *pvt)
+static int pvt_hash_cb(const void *obj, const int flags)
 {
-       if (ast_string_field_init(pvt, 32))
-               return -1;
-       
-       if (ast_mutex_init(&pvt->__lock)) {
-               ast_log(LOG_ERROR, "Failed to initialize mutex\n");
-               return -1;
-       }
+       const struct console_pvt *pvt = obj;
 
-       return 0;
+       return ast_str_hash(pvt->name);
 }
 
-static void destroy_pvt(struct console_pvt *pvt)
+static int pvt_cmp_cb(void *obj, void *arg, int flags)
 {
-       ast_string_field_free_memory(pvt);
-       
-       ast_mutex_destroy(&pvt->__lock);
+       struct console_pvt *pvt = obj, *pvt2 = arg;
+
+       return !strcasecmp(pvt->name, pvt2->name) ? CMP_MATCH : 0;
+}
+
+static void stop_streams(void)
+{
+       struct console_pvt *pvt;
+       struct ao2_iterator i;
+
+       i = ao2_iterator_init(pvts, 0);
+       while ((pvt = ao2_iterator_next(&i))) {
+               if (pvt->hookstate)
+                       stop_stream(pvt);
+               unref_pvt(pvt);
+       }
 }
 
 static int unload_module(void)
 {
-       struct console_pvt *pvt = &console_pvt;
+       ast_channel_unregister(&console_tech);
+       ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
 
-       if (pvt->hookstate)
-               stop_stream(pvt);
+       stop_streams();
 
        Pa_Terminate();
 
-       ast_channel_unregister(&console_tech);
-       ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
+       /* Will unref all the pvts so they will get destroyed, too */
+       ao2_ref(pvts, -1);
 
-       destroy_pvt(pvt);
+       pvt_destructor(&globals);
 
        return 0;
 }
@@ -1054,9 +1398,10 @@ static int unload_module(void)
 static int load_module(void)
 {
        PaError res;
-       struct console_pvt *pvt = &console_pvt;
 
-       if (init_pvt(pvt))
+       init_pvt(&globals, NULL);
+
+       if (!(pvts = ao2_container_alloc(NUM_PVT_BUCKETS, pvt_hash_cb, pvt_cmp_cb)))
                goto return_error;
 
        if (load_config(0))
@@ -1086,7 +1431,9 @@ return_error_chan_reg:
 return_error_pa_init:
        Pa_Terminate();
 return_error:
-       destroy_pvt(pvt);
+       if (pvts)
+               ao2_ref(pvts, -1);
+       pvt_destructor(&globals);
 
        return AST_MODULE_LOAD_DECLINE;
 }
index 820a04d..1631ac2 100644 (file)
@@ -33,7 +33,7 @@
 ; to dial SIP, IAX and other extensions which use the '@' character.
 ; The default is "no".
 ;
-;overridecontext = no  ; if 'no', the last @ will start the context
+;overridecontext = no    ; if 'no', the last @ will start the context
                         ; if 'yes' the whole string is an extension.
 
 
 
 ; jblog = no                  ; Enables jitterbuffer frame logging. Defaults to "no".
 ;-----------------------------------------------------------------------------------
+
+
+;
+; Any configuration context defined beyond the [general] section configures
+; specific devices for use.
+;
+
+[default]
+input_device = default       ; When configuring an input device and output device,
+output_device = default      ; use the name that you see when you run the "console
+                             ; list devices" CLI command.  If you say "default", the
+                             ; system default input and output devices will be used.
+autoanswer = no
+context = default
+extension = s
+callerid = MyName Here <(256) 428-6000>
+language = en
+overridecontext = no
+mohinterpret = default
+active = yes                 ; This option should only be set for one console.
+                             ; It means that it is the active console to be
+                             ; used from the Asterisk CLI.