diff --git a/apps/app_meetme.c b/apps/app_meetme.c index 862db9927d8b6a118c2009c6f9aea3fa38860a98..f0d7a6013a342e5416f5f202ed0236bdb516a0cb 100644 --- a/apps/app_meetme.c +++ b/apps/app_meetme.c @@ -378,6 +378,10 @@ struct sla_station { * is set for a specific trunk on this station, that will take * priority over this value. */ unsigned int ring_timeout; + /*! Ring delay for this station, for any trunk. If a ring delay + * is set for a specific trunk on this station, that will take + * priority over this value. */ + unsigned int ring_delay; }; struct sla_station_ref { @@ -412,6 +416,10 @@ struct sla_trunk_ref { * station. This takes higher priority than a ring timeout set at * the station level. */ unsigned int ring_timeout; + /*! Ring delay to use when this trunk is ringing on this specific + * station. This takes higher priority than a ring delay set at + * the station level. */ + unsigned int ring_delay; }; static AST_RWLIST_HEAD_STATIC(sla_stations, sla_station); @@ -987,31 +995,34 @@ static int sla_show_trunks(int fd, int argc, char **argv) const struct sla_trunk *trunk; ast_cli(fd, "\n" - "--- Configured SLA Trunks -----------------------------------\n" - "-------------------------------------------------------------\n" - "---\n"); + "=============================================================\n" + "=== Configured SLA Trunks ===================================\n" + "=============================================================\n" + "===\n"); AST_RWLIST_RDLOCK(&sla_trunks); AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) { struct sla_station_ref *station_ref; char ring_timeout[16] = "(none)"; if (trunk->ring_timeout) snprintf(ring_timeout, sizeof(ring_timeout), "%u Seconds", trunk->ring_timeout); - ast_cli(fd, "--- Trunk Name: %s\n" - "--- ==> Device: %s\n" - "--- ==> AutoContext: %s\n" - "--- ==> RingTimeout: %s\n" - "--- ==> Stations ...\n", + ast_cli(fd, "=== ---------------------------------------------------------\n" + "=== Trunk Name: %s\n" + "=== ==> Device: %s\n" + "=== ==> AutoContext: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> Stations ...\n", trunk->name, trunk->device, S_OR(trunk->autocontext, "(none)"), ring_timeout); AST_RWLIST_RDLOCK(&sla_stations); AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) - ast_cli(fd, "--- ==> Station name: %s\n", station_ref->station->name); + ast_cli(fd, "=== ==> Station name: %s\n", station_ref->station->name); AST_RWLIST_UNLOCK(&sla_stations); - ast_cli(fd, "---\n"); + ast_cli(fd, "=== ---------------------------------------------------------\n" + "===\n"); } AST_RWLIST_UNLOCK(&sla_trunks); - ast_cli(fd, "-------------------------------------------------------------\n" + ast_cli(fd, "=============================================================\n" "\n"); return RESULT_SUCCESS; @@ -1039,24 +1050,33 @@ static int sla_show_stations(int fd, int argc, char **argv) const struct sla_station *station; ast_cli(fd, "\n" - "--- Configured SLA Stations ---------------------------------\n" - "-------------------------------------------------------------\n" - "---\n"); + "=============================================================\n" + "=== Configured SLA Stations =================================\n" + "=============================================================\n" + "===\n"); AST_RWLIST_RDLOCK(&sla_stations); AST_RWLIST_TRAVERSE(&sla_stations, station, entry) { struct sla_trunk_ref *trunk_ref; char ring_timeout[16] = "(none)"; + char ring_delay[16] = "(none)"; if (station->ring_timeout) { snprintf(ring_timeout, sizeof(ring_timeout), "%u", station->ring_timeout); } - ast_cli(fd, "--- Station Name: %s\n" - "--- ==> Device: %s\n" - "--- ==> AutoContext: %s\n" - "--- ==> RingTimeout: %s\n" - "--- ==> Trunks ...\n", + if (station->ring_delay) { + snprintf(ring_delay, sizeof(ring_delay), + "%u", station->ring_delay); + } + ast_cli(fd, "=== ---------------------------------------------------------\n" + "=== Station Name: %s\n" + "=== ==> Device: %s\n" + "=== ==> AutoContext: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> RingDelay: %s\n" + "=== ==> Trunks ...\n", station->name, station->device, - S_OR(station->autocontext, "(none)"), ring_timeout); + S_OR(station->autocontext, "(none)"), + ring_timeout, ring_delay); AST_RWLIST_RDLOCK(&sla_trunks); AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { if (trunk_ref->ring_timeout) { @@ -1064,17 +1084,25 @@ static int sla_show_stations(int fd, int argc, char **argv) "%u", trunk_ref->ring_timeout); } else strcpy(ring_timeout, "(none)"); - ast_cli(fd, "--- ==> Trunk Name: %s\n", - trunk_ref->trunk->name); - ast_cli(fd, "--- ==> State: %s\n", - trunkstate2str(trunk_ref->state)); - ast_cli(fd, "--- ==> RingTimeout: %s\n", ring_timeout); + if (trunk_ref->ring_delay) { + snprintf(ring_delay, sizeof(ring_delay), + "%u", trunk_ref->ring_delay); + } else + strcpy(ring_delay, "(none)"); + ast_cli(fd, "=== ==> Trunk Name: %s\n" + "=== ==> State: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> RingDelay: %s\n", + trunk_ref->trunk->name, + trunkstate2str(trunk_ref->state), + ring_timeout, ring_delay); } AST_RWLIST_UNLOCK(&sla_trunks); - ast_cli(fd, "---\n"); + ast_cli(fd, "=== ---------------------------------------------------------\n" + "===\n"); } AST_RWLIST_UNLOCK(&sla_stations); - ast_cli(fd, "-------------------------------------------------------------\n" + ast_cli(fd, "============================================================\n" "\n"); return RESULT_SUCCESS; @@ -3047,7 +3075,7 @@ static struct sla_station *sla_find_station(const char *name) return station; } -static struct sla_trunk_ref *sla_sla_find_trunk_ref(const struct sla_station *station, +static struct sla_trunk_ref *sla_find_trunk_ref_byname(const struct sla_station *station, const char *name) { struct sla_trunk_ref *trunk_ref = NULL; @@ -3091,8 +3119,6 @@ static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk struct sla_station *station; struct sla_trunk_ref *trunk_ref; - ast_log(LOG_DEBUG, "Setting all refs of trunk %s to state %s\n", trunk->name, trunkstate2str(state)); - AST_LIST_TRAVERSE(&sla_stations, station, entry) { AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { if (trunk_ref->trunk != trunk || (inactive_only ? trunk_ref->chan : 0)) @@ -3209,6 +3235,64 @@ static void sla_dial_state_callback(struct ast_dial *dial) sla_queue_event(SLA_EVENT_DIAL_STATE); } +/*! \brief Check to see if dialing this station already timed out for this ringing trunk + * \note Assumes sla.lock is locked + */ +static int sla_check_timed_out_station(const struct sla_ringing_trunk *ringing_trunk, + const struct sla_station *station) +{ + struct sla_station_ref *timed_out_station; + + AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, timed_out_station, entry) { + if (station == timed_out_station->station) + return 1; + } + + return 0; +} + +/*! \brief Choose the highest priority ringing trunk for a station + * \param station the station + * \param remove remove the ringing trunk once selected + * \param trunk_ref a place to store the pointer to this stations reference to + * the selected trunk + * \return a pointer to the selected ringing trunk, or NULL if none found + * \note Assumes that sla.lock is locked + */ +static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *station, + struct sla_trunk_ref **trunk_ref, int remove) +{ + struct sla_trunk_ref *s_trunk_ref; + struct sla_ringing_trunk *ringing_trunk = NULL; + + AST_LIST_TRAVERSE(&station->trunks, s_trunk_ref, entry) { + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { + /* Make sure this is the trunk we're looking for */ + if (s_trunk_ref->trunk != ringing_trunk->trunk) + continue; + + /* This trunk on the station is ringing. But, make sure this station + * didn't already time out while this trunk was ringing. */ + if (sla_check_timed_out_station(ringing_trunk, station)) + continue; + + if (remove) + AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); + + if (trunk_ref) + *trunk_ref = s_trunk_ref; + + break; + } + AST_LIST_TRAVERSE_SAFE_END + + if (ringing_trunk) + break; + } + + return ringing_trunk; +} + static void sla_handle_dial_state_event(void) { struct sla_ringing_station *ringing_station; @@ -3235,36 +3319,22 @@ static void sla_handle_dial_state_event(void) case AST_DIAL_RESULT_ANSWERED: AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); /* Find the appropriate trunk to answer. */ - AST_LIST_TRAVERSE(&ringing_station->station->trunks, s_trunk_ref, entry) { - ast_mutex_lock(&sla.lock); - AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { - struct sla_station_ref *station_ref; - if (s_trunk_ref->trunk != ringing_trunk->trunk) - continue; - /* This trunk on the station is ringing. But, make sure this station - * didn't already time out while this trunk was ringing. */ - AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, station_ref, entry) { - if (station_ref->station == ringing_station->station) - break; - } - if (station_ref) - continue; - AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); - break; - } - AST_LIST_TRAVERSE_SAFE_END - ast_mutex_unlock(&sla.lock); - if (ringing_trunk) - break; - } + ast_mutex_lock(&sla.lock); + ringing_trunk = sla_choose_ringing_trunk(ringing_station->station, &s_trunk_ref, 1); + ast_mutex_unlock(&sla.lock); if (!ringing_trunk) { ast_log(LOG_DEBUG, "Found no ringing trunk for station '%s' to answer!\n", ringing_station->station->name); break; } + /* Track the channel that answered this trunk */ s_trunk_ref->chan = ast_dial_answered(ringing_station->station->dial); + /* Actually answer the trunk */ ast_answer(ringing_trunk->trunk->chan); sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS); + /* Now, start a thread that will connect this station to the trunk. The rest of + * the code here sets up the thread and ensures that it is able to save the arguments + * before they are no longer valid since they are allocated on the stack. */ args.trunk_ref = s_trunk_ref; args.station = ringing_station->station; args.cond = &cond; @@ -3299,84 +3369,199 @@ static void sla_handle_dial_state_event(void) AST_LIST_TRAVERSE_SAFE_END } -static void sla_handle_ringing_trunk_event(void) +/*! \brief Check to see if this station is already ringing + * \note Assumes sla.lock is locked + */ +static int sla_check_ringing_station(const struct sla_station *station) +{ + struct sla_ringing_station *ringing_station; + + AST_LIST_TRAVERSE(&sla.ringing_stations, ringing_station, entry) { + if (station == ringing_station->station) + return 1; + } + + return 0; +} + +/*! \brief Check to see if this station has failed to be dialed in the past minute + * \note assumes sla.lock is locked + */ +static int sla_check_failed_station(const struct sla_station *station) +{ + struct sla_failed_station *failed_station; + int res = 0; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.failed_stations, failed_station, entry) { + if (station != failed_station->station) + continue; + if (ast_tvdiff_ms(ast_tvnow(), failed_station->last_try) > 1000) { + AST_LIST_REMOVE_CURRENT(&sla.failed_stations, entry); + free(failed_station); + break; + } + res = 1; + } + AST_LIST_TRAVERSE_SAFE_END + + return res; +} + +/*! \brief Ring a station + * \note Assumes sla.lock is locked + */ +static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_station *station) +{ + char *tech, *tech_data; + struct ast_dial *dial; + struct sla_ringing_station *ringing_station; + + if (!(dial = ast_dial_create())) + return -1; + + ast_dial_set_state_callback(dial, sla_dial_state_callback); + tech_data = ast_strdupa(station->device); + tech = strsep(&tech_data, "/"); + + if (ast_dial_append(dial, tech, tech_data) == -1) { + ast_dial_destroy(dial); + return -1; + } + + if (ast_dial_run(dial, ringing_trunk->trunk->chan, 1) != AST_DIAL_RESULT_TRYING) { + struct sla_failed_station *failed_station; + ast_dial_destroy(dial); + if (!(failed_station = ast_calloc(1, sizeof(*failed_station)))) + return -1; + failed_station->station = station; + failed_station->last_try = ast_tvnow(); + AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry); + return -1; + } + if (!(ringing_station = sla_create_ringing_station(station))) { + ast_dial_join(dial); + ast_dial_destroy(dial); + return -1; + } + + station->dial = dial; + + AST_LIST_INSERT_HEAD(&sla.ringing_stations, ringing_station, entry); + + return 0; +} + +/*! \brief Check to see if a station is in use + */ +static int sla_check_inuse_station(const struct sla_station *station) { struct sla_trunk_ref *trunk_ref; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->chan) + return 1; + } + + return 0; +} + +static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *station, + const struct sla_trunk *trunk) +{ + struct sla_trunk_ref *trunk_ref = NULL; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->trunk == trunk) + break; + } + + return trunk_ref; +} + +/*! \brief Calculate the ring delay for a given ringing trunk on a station + * \param station the station + * \param trunk the trunk. If NULL, the highest priority ringing trunk will be used + * \return the number of ms left before the delay is complete, or INT_MAX if there is no delay + */ +static int sla_check_station_delay(struct sla_station *station, + struct sla_ringing_trunk *ringing_trunk) +{ + struct sla_trunk_ref *trunk_ref; + unsigned int delay = UINT_MAX; + int time_left, time_elapsed; + + if (!ringing_trunk) + ringing_trunk = sla_choose_ringing_trunk(station, &trunk_ref, 0); + else + trunk_ref = sla_find_trunk_ref(station, ringing_trunk->trunk); + + if (!ringing_trunk || !trunk_ref) + return delay; + + /* If this station has a ring delay specific to the highest priority + * ringing trunk, use that. Otherwise, use the ring delay specified + * globally for the station. */ + delay = trunk_ref->ring_delay; + if (!delay) + delay = station->ring_delay; + if (!delay) + return INT_MAX; + + time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin); + time_left = (delay * 1000) - time_elapsed; + + return time_left; +} + +/*! \brief Ring stations based on current set of ringing trunks + * \note Assumes that sla.lock is locked + */ +static void sla_ring_stations(void) +{ struct sla_station_ref *station_ref; struct sla_ringing_trunk *ringing_trunk; - struct sla_ringing_station *ringing_station; - - ast_mutex_lock(&sla.lock); /* Make sure that every station that uses at least one of the ringing * trunks, is ringing. */ AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { AST_LIST_TRAVERSE(&ringing_trunk->trunk->stations, station_ref, entry) { - char *tech, *tech_data; - struct ast_dial *dial; - struct sla_failed_station *failed_station; - struct sla_station_ref *timed_out_station; + int time_left; + + /* Is this station already ringing? */ + if (sla_check_ringing_station(station_ref->station)) + continue; + + /* Is this station already in a call? */ + if (sla_check_inuse_station(station_ref->station)) + continue; + /* Did we fail to dial this station earlier? If so, has it been * a minute since we tried? */ - AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.failed_stations, failed_station, entry) { - if (station_ref->station != failed_station->station) - continue; - if (ast_tvdiff_ms(ast_tvnow(), failed_station->last_try) > 1000) { - AST_LIST_REMOVE_CURRENT(&sla.failed_stations, entry); - free(failed_station); - failed_station = NULL; - } - break; - } - if (failed_station) - continue; - AST_LIST_TRAVERSE_SAFE_END - AST_LIST_TRAVERSE(&sla.ringing_stations, ringing_station, entry) { - if (station_ref->station == ringing_station->station) - break; - } - if (ringing_station) + if (sla_check_failed_station(station_ref->station)) continue; + /* If this station already timed out while this trunk was ringing, * do not dial it again for this ringing trunk. */ - AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, timed_out_station, entry) { - if (station_ref->station == timed_out_station->station) - break; - } - if (timed_out_station) - continue; - if (!(dial = ast_dial_create())) - continue; - ast_dial_set_state_callback(dial, sla_dial_state_callback); - tech_data = ast_strdupa(station_ref->station->device); - tech = strsep(&tech_data, "/"); - if (ast_dial_append(dial, tech, tech_data) == -1) { - ast_dial_destroy(dial); - continue; - } - if (ast_dial_run(dial, ringing_trunk->trunk->chan, 1) != AST_DIAL_RESULT_TRYING) { - ast_dial_destroy(dial); - if (!(failed_station = ast_calloc(1, sizeof(*failed_station)))) - continue; - failed_station->station = station_ref->station; - failed_station->last_try = ast_tvnow(); - AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry); + if (sla_check_timed_out_station(ringing_trunk, station_ref->station)) continue; - } - if (!(ringing_station = sla_create_ringing_station(station_ref->station))) { - ast_dial_join(dial); - ast_dial_destroy(dial); + + /* Check for a ring delay in progress */ + time_left = sla_check_station_delay(station_ref->station, ringing_trunk); + if (time_left != INT_MAX && time_left > 0) continue; - } - station_ref->station->dial = dial; - AST_LIST_INSERT_HEAD(&sla.ringing_stations, ringing_station, entry); - ast_log(LOG_DEBUG, "Started dialing station '%s'\n", station_ref->station->name); + + /* It is time to make this station begin to ring. Do it! */ + sla_ring_station(ringing_trunk, station_ref->station); } } - ast_mutex_unlock(&sla.lock); /* Now, all of the stations that should be ringing, are ringing. */ - - /* Find stations that shouldn't be ringing anymore. */ +} + +static void sla_hangup_stations(void) +{ + struct sla_trunk_ref *trunk_ref; + struct sla_ringing_station *ringing_station; + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) { AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) { struct sla_ringing_trunk *ringing_trunk; @@ -3400,6 +3585,16 @@ static void sla_handle_ringing_trunk_event(void) AST_LIST_TRAVERSE_SAFE_END } +static void sla_handle_ringing_trunk_event(void) +{ + ast_mutex_lock(&sla.lock); + sla_ring_stations(); + ast_mutex_unlock(&sla.lock); + + /* Find stations that shouldn't be ringing anymore. */ + sla_hangup_stations(); +} + static void sla_handle_hold_event(struct sla_event *event) { ast_atomic_fetchadd_int((int *) &event->trunk_ref->trunk->hold_stations, 1); @@ -3420,17 +3615,15 @@ static void sla_handle_unhold_event(struct sla_event *event) } } -/*! \brief Calculate the time until the next known event - * \note Called with sla.lock locked */ -static int sla_process_timers(struct timespec *ts) +/*! \brief Process trunk ring timeouts + * \note Called with sla.lock locked + * \return non-zero if a change to the ringing trunks was made + */ +static int sla_calc_trunk_timeouts(unsigned int *timeout) { struct sla_ringing_trunk *ringing_trunk; - struct sla_ringing_station *ringing_station; - unsigned int timeout = UINT_MAX; - struct timeval tv; - unsigned int change_made = 0; + int res = 0; - /* Check for ring timeouts on ringing trunks */ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { int time_left, time_elapsed; if (!ringing_trunk->trunk->ring_timeout) @@ -3440,37 +3633,51 @@ static int sla_process_timers(struct timespec *ts) if (time_left <= 0) { AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); sla_stop_ringing_trunk(ringing_trunk); - change_made = 1; + res = 1; continue; } - if (time_left < timeout) - timeout = time_left; + if (time_left < *timeout) + *timeout = time_left; } AST_LIST_TRAVERSE_SAFE_END - /* Check for ring timeouts on ringing stations */ + return res; +} + +/*! \brief Process station ring timeouts + * \note Called with sla.lock locked + * \return non-zero if a change to the ringing stations was made + */ +static int sla_calc_station_timeouts(unsigned int *timeout) +{ + struct sla_ringing_trunk *ringing_trunk; + struct sla_ringing_station *ringing_station; + int res = 0; + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) { unsigned int ring_timeout = 0; - int time_elapsed, time_left, final_trunk_time_left = INT_MIN; + int time_elapsed, time_left = INT_MAX, final_trunk_time_left = INT_MIN; struct sla_trunk_ref *trunk_ref; + /* If there are any ring timeouts specified for a specific trunk * on the station, then use the highest per-trunk ring timeout. * Otherwise, use the ring timeout set for the entire station. */ AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) { struct sla_station_ref *station_ref; int trunk_time_elapsed, trunk_time_left; + AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { if (ringing_trunk->trunk == trunk_ref->trunk) break; } if (!ringing_trunk) continue; + /* If there is a trunk that is ringing without a timeout, then the * only timeout that could matter is a global station ring timeout. */ - if (!trunk_ref->ring_timeout) { - final_trunk_time_left = INT_MAX; + if (!trunk_ref->ring_timeout) break; - } + /* This trunk on this station is ringing and has a timeout. * However, make sure this trunk isn't still ringing from a * previous timeout. If so, don't consider it. */ @@ -3480,36 +3687,114 @@ static int sla_process_timers(struct timespec *ts) } if (station_ref) continue; + trunk_time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin); trunk_time_left = (trunk_ref->ring_timeout * 1000) - trunk_time_elapsed; if (trunk_time_left > final_trunk_time_left) final_trunk_time_left = trunk_time_left; } - if (final_trunk_time_left == INT_MAX && !ringing_station->station->ring_timeout) + + /* No timeout was found for ringing trunks, and no timeout for the entire station */ + if (final_trunk_time_left == INT_MIN && !ringing_station->station->ring_timeout) continue; - ring_timeout = ringing_station->station->ring_timeout; - time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_station->ring_begin); - time_left = (ring_timeout * 1000) - time_elapsed; + /* Compute how much time is left for a global station timeout */ + if (ringing_station->station->ring_timeout) { + ring_timeout = ringing_station->station->ring_timeout; + time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_station->ring_begin); + time_left = (ring_timeout * 1000) - time_elapsed; + } + /* If the time left based on the per-trunk timeouts is smaller than the * global station ring timeout, use that. */ if (final_trunk_time_left > INT_MIN && final_trunk_time_left < time_left) time_left = final_trunk_time_left; + + /* If there is no time left, the station needs to stop ringing */ if (time_left <= 0) { AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_TIMEOUT); - change_made = 1; + res = 1; continue; } - if (time_left < timeout) - timeout = time_left; + + /* There is still some time left for this station to ring, so save that + * timeout if it is the first event scheduled to occur */ + if (time_left < *timeout) + *timeout = time_left; } AST_LIST_TRAVERSE_SAFE_END + return res; +} + +/*! \brief Calculate the ring delay for a station + * \note Assumes sla.lock is locked + */ +static int sla_calc_station_delays(unsigned int *timeout) +{ + struct sla_station *station; + int res = 0; + + AST_LIST_TRAVERSE(&sla_stations, station, entry) { + struct sla_ringing_trunk *ringing_trunk; + int time_left; + + /* Ignore stations already ringing */ + if (sla_check_ringing_station(station)) + continue; + + /* Ignore stations already on a call */ + if (sla_check_inuse_station(station)) + continue; + + /* Ignore stations that don't have one of their trunks ringing */ + if (!(ringing_trunk = sla_choose_ringing_trunk(station, NULL, 0))) + continue; + + if ((time_left = sla_check_station_delay(station, ringing_trunk)) == INT_MAX) + continue; + + /* If there is no time left, then the station needs to start ringing. + * Return non-zero so that an event will be queued up an event to + * make that happen. */ + if (time_left <= 0) { + res = 1; + continue; + } + + if (time_left < *timeout) + *timeout = time_left; + } + + return res; +} + +/*! \brief Calculate the time until the next known event + * \note Called with sla.lock locked */ +static int sla_process_timers(struct timespec *ts) +{ + unsigned int timeout = UINT_MAX; + struct timeval tv; + unsigned int change_made = 0; + + /* Check for ring timeouts on ringing trunks */ + if (sla_calc_trunk_timeouts(&timeout)) + change_made = 1; + + /* Check for ring timeouts on ringing stations */ + if (sla_calc_station_timeouts(&timeout)) + change_made = 1; + + /* Check for station ring delays */ + if (sla_calc_station_delays(&timeout)) + change_made = 1; + /* queue reprocessing of ringing trunks */ if (change_made) sla_queue_event_nolock(SLA_EVENT_RINGING_TRUNK); + /* No timeout */ if (timeout == UINT_MAX) return 0; @@ -3541,7 +3826,6 @@ static void *sla_thread(void *data) ast_cond_wait(&sla.cond, &sla.lock); if (sla.stop) break; - ast_log(LOG_DEBUG, "Ooh, I was woken up!\n"); } if (have_timeout) @@ -3650,7 +3934,6 @@ static void *dial_trunk(void *data) ast_mutex_unlock(args->cond_lock); ast_dial_join(dial); ast_dial_destroy(dial); - ast_log(LOG_DEBUG, "broke out with no chan\n"); return NULL; } @@ -3678,7 +3961,21 @@ static void *dial_trunk(void *data) return NULL; } -static int slastation_exec(struct ast_channel *chan, void *data) +/*! \brief For a given station, choose the highest priority idle trunk + */ +static struct sla_trunk_ref *sla_choose_idle_trunk(const struct sla_station *station) +{ + struct sla_trunk_ref *trunk_ref = NULL; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->state == SLA_TRUNK_STATE_IDLE) + break; + } + + return trunk_ref; +} + +static int sla_station_exec(struct ast_channel *chan, void *data) { char *station_name, *trunk_name; struct sla_station *station; @@ -3715,13 +4012,9 @@ static int slastation_exec(struct ast_channel *chan, void *data) AST_RWLIST_RDLOCK(&sla_trunks); if (!ast_strlen_zero(trunk_name)) - trunk_ref = sla_sla_find_trunk_ref(station, trunk_name); - else { - AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { - if (trunk_ref->state == SLA_TRUNK_STATE_IDLE) - break; - } - } + trunk_ref = sla_find_trunk_ref_byname(station, trunk_name); + else + trunk_ref = sla_choose_idle_trunk(station); AST_RWLIST_UNLOCK(&sla_trunks); if (ast_strlen_zero(trunk_name) && !trunk_ref) { @@ -3824,7 +4117,7 @@ static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk) return ringing_trunk; } -static int slatrunk_exec(struct ast_channel *chan, void *data) +static int sla_trunk_exec(struct ast_channel *chan, void *data) { const char *trunk_name = data; char conf_name[MAX_CONFNUM]; @@ -4121,6 +4414,12 @@ static void sla_add_trunk_to_station(struct sla_station *station, struct ast_var "trunk '%s' on station '%s'\n", value, trunk->name, station->name); trunk_ref->ring_timeout = 0; } + } else if (!strcasecmp(name, "ringdelay")) { + if (sscanf(value, "%u", &trunk_ref->ring_delay) != 1) { + ast_log(LOG_WARNING, "Invalid ringdelay value '%s' for " + "trunk '%s' on station '%s'\n", value, trunk->name, station->name); + trunk_ref->ring_delay = 0; + } } else { ast_log(LOG_WARNING, "Invalid option '%s' for " "trunk '%s' on station '%s'\n", name, trunk->name, station->name); @@ -4170,6 +4469,12 @@ static int sla_build_station(struct ast_config *cfg, const char *cat) var->value, station->name); station->ring_timeout = 0; } + } else if (!strcasecmp(var->name, "ringdelay")) { + if (sscanf(var->value, "%u", &station->ring_delay) != 1) { + ast_log(LOG_WARNING, "Invalid ringdelay '%s' specified for station '%s'\n", + var->value, station->name); + station->ring_delay = 0; + } } else if (strcasecmp(var->name, "type") && strcasecmp(var->name, "device")) { ast_log(LOG_ERROR, "Invalid option '%s' specified at line %d of %s!\n", var->name, var->lineno, SLA_CONFIG_FILE); @@ -4236,6 +4541,9 @@ static int sla_load_config(void) const char *cat = NULL; int res = 0; + ast_mutex_init(&sla.lock); + ast_cond_init(&sla.cond, NULL); + if (!(cfg = ast_config_load(SLA_CONFIG_FILE))) return 0; /* Treat no config as normal */ @@ -4304,6 +4612,8 @@ static int load_module(void) { int res; + res |= load_config(0); + ast_cli_register_multiple(cli_meetme, ARRAY_LEN(cli_meetme)); res = ast_manager_register("MeetmeMute", EVENT_FLAG_CALL, action_meetmemute, "Mute a Meetme user"); @@ -4312,16 +4622,14 @@ static int load_module(void) res |= ast_register_application(app3, admin_exec, synopsis3, descrip3); res |= ast_register_application(app2, count_exec, synopsis2, descrip2); res |= ast_register_application(app, conf_exec, synopsis, descrip); - res |= ast_register_application(slastation_app, slastation_exec, + res |= ast_register_application(slastation_app, sla_station_exec, slastation_synopsis, slastation_desc); - res |= ast_register_application(slatrunk_app, slatrunk_exec, + res |= ast_register_application(slatrunk_app, sla_trunk_exec, slatrunk_synopsis, slatrunk_desc); res |= ast_devstate_prov_add("Meetme", meetmestate); res |= ast_devstate_prov_add("SLA", sla_state); - res |= load_config(0); - return res; } diff --git a/configs/sla.conf.sample b/configs/sla.conf.sample index 839d6d936f20c33e3c8d6d73e636b4ccb2753580..701ed1d640992354e40302597ea985057b866b09 100644 --- a/configs/sla.conf.sample +++ b/configs/sla.conf.sample @@ -1,6 +1,8 @@ ; ; Configuration for Shared Line Appearances (SLA). ; +; See doc/sla.txt for more information. +; ; ---- General Options ---------------- [general] @@ -52,15 +54,21 @@ ; have its context configured to the same one listed here. ;ringtimeout=10 ; Set a timeout for how long to allow the station to ring for an -; ; incoming call, in seconds. + ; incoming call, in seconds. +;ringdelay=10 ; Set a time for how long to wait before beginning to ring this station + ; once there is an incoming call, in seconds. ;trunk=line1 ; Individually list all of the trunks that will appear on this station. This ; order is significant. It should be the same order as they appear on the ; phone. The order here defines the order of preference that the trunks will ; be used. ;trunk=line2 -;trunk=line3 +;trunk=line3,ringdelay=5 ; A ring delay for the station can also be specified for a specific trunk. + ; If a ring delay is specified both for the whole station and for a specific + ; trunk on a station, the setting for the specific trunk will take priority. + ; This value is in seconds. + ;trunk=line4,ringtimeout=5 ; A ring timeout for the station can also be specified for a specific trunk. ; If a ring timeout is specified both for the whole station and for a specific ; trunk on a station, the setting for the specific trunk will take priority.