--- include/asterisk/res_fax.h +++ include/asterisk/res_fax.h.5c988cc4e6c5693f03080f88e3057cb7a5358597 @@ -29,6 +29,7 @@ #include #include #include +#include /*! \brief capabilities for res_fax to locate a fax technology module */ enum ast_fax_capabilities { @@ -255,6 +256,9 @@ struct ast_fax_tech { char * (* const cli_show_capabilities)(int); /*! displays details about the fax session */ char * (* const cli_show_session)(struct ast_fax_session *, int); + /*! Generates manager event detailing the fax session */ + void (* const manager_fax_session)(struct mansession *, + const char *, struct ast_fax_session *); /*! displays statistics from the fax technology module */ char * (* const cli_show_stats)(int); /*! displays settings from the fax technology module */ @@ -276,6 +280,9 @@ unsigned int ast_fax_maxrate(void); /*! \brief convert an ast_fax_state to a string */ const char *ast_fax_state_to_str(enum ast_fax_state state); +/*! \brief get string representation of a FAX session's operation */ +const char *ast_fax_session_operation_str(struct ast_fax_session *s); + /*! * \brief Log message at FAX or recommended level * --- res/res_fax.c +++ res/res_fax.c.5c988cc4e6c5693f03080f88e3057cb7a5358597 @@ -235,6 +235,188 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") SendFax + + + Lists active FAX sessions + + + + + + Will generate a series of FAXSession events with information about each FAXSession. Closes with + a FAXSessionsComplete event which includes a count of the included FAX sessions. This action works in + the same manner as the CLI command 'fax show sessions' + + + + + A single list item for the FAXSessions AMI command + + + + Name of the channel responsible for the FAX session + + + The FAX technology that the FAX session is using + + + The numerical identifier for this particular session + + + FAX session passthru/relay type + + + + + + + FAX session operation type + + + + + + + + + + Current state of the FAX session + + + + + + + + + + + + + File or list of files associated with this FAX session + + + + + + + Raised when all FAXSession events are completed for a FAXSessions command + + + + Count of FAXSession events sent in response to FAXSessions action + + + + + + + Responds with a detailed description of a single FAX session + + + + + The session ID of the fax the user is interested in. + + + + Provides details about a specific FAX session. The response will include a common subset of + the output from the CLI command 'fax show session <session_number>' for each technology. If the + FAX technolgy used by this session does not include a handler for FAXSession, then this action + will fail. + + + + + Raised in response to FAXSession manager command + + + + The numerical identifier for this particular session + + + + + Whether error correcting mode is enabled for the FAX session. This field is not + included when operation is 'V.21 Detect' or if operation is 'gateway' and state is + 'Uninitialized' + + + + + + + + Bit rate of the FAX. This field is not included when operation is 'V.21 Detect' or + if operation is 'gateway' and state is 'Uninitialized'. + + + Resolution of each page of the FAX. Will be in the format of X_RESxY_RES. This field + is not included if the operation is anything other than Receive/Transmit. + + + Current number of pages transferred during this FAX session. May change as the FAX + progresses. This field is not included when operation is 'V.21 Detect' or if operation is + 'gateway' and state is 'Uninitialized'. + + + Filename of the image being sent/recieved for this FAX session. This field is not + included if Operation isn't 'send' or 'receive'. + + + Total number of pages sent during this session. This field is not included if + Operation isn't 'send' or 'receive'. Will always be 0 for 'receive'. + + + Total number of pages received during this session. This field is not included if + Operation is not 'send' or 'receive'. Will be 0 for 'send'. + + + Total number of bad lines sent/recieved during this session. This field is not + included if Operation is not 'send' or 'received'. + + + + + + + Responds with fax statistics + + + + + + Provides FAX statistics including the number of active sessions, reserved sessions, completed + sessions, failed sessions, and the number of receive/transmit attempts. This command provides all + of the non-technology specific information provided by the CLI command 'fax show stats' + + + + + Raised in response to FAXStats manager command + + + + Number of active FAX sessions + + + Number of reserved FAX sessions + + + Total FAX sessions for which Asterisk is/was the transmitter + + + Total FAX sessions for which Asterisk is/was the recipient + + + Total FAX sessions which have been completed successfully + + + Total FAX sessions which failed to complete successfully + + + + ***/ static const char app_receivefax[] = "ReceiveFAX"; @@ -1144,7 +1326,7 @@ static char *generate_filenames_string(struct ast_fax_session_details *details, /* don't process empty lists */ if (AST_LIST_EMPTY(&details->documents)) { - return NULL; + return ast_strdup(""); } /* Calculate the total length of all of the file names */ @@ -3742,6 +3924,43 @@ static char *cli_fax_show_session(struct ast_cli_entry *e, int cmd, struct ast_c return CLI_SUCCESS; } +static int manager_fax_session(struct mansession *s, const struct message *m) +{ + const char *action_id = astman_get_header(m, "ActionID"); + const char *session_number = astman_get_header(m, "SessionNumber"); + char id_text[256] = ""; + struct ast_fax_session *session; + struct ast_fax_session find_session; + + if (sscanf(session_number, "%30u", &find_session.id) != 1) { + astman_send_error(s, m, "Invalid session ID"); + return 0; + } + + session = ao2_find(faxregistry.container, &find_session, OBJ_POINTER); + if (!session) { + astman_send_error(s, m, "Session not found"); + return 0; + } + + if (!session->tech->manager_fax_session) { + astman_send_error(s, m, "Fax technology doesn't provide a handler for FAXSession"); + ao2_ref(session, -1); + return 0; + } + + if (!ast_strlen_zero(action_id)) { + snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", action_id); + } + + astman_send_ack(s, m, "FAXSession event will follow"); + + session->tech->manager_fax_session(s, id_text, session); + ao2_ref(session, -1); + + return 0; +} + /*! \brief display fax stats */ static char *cli_fax_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -3775,7 +3994,36 @@ static char *cli_fax_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli return CLI_SUCCESS; } -static const char *cli_session_type(struct ast_fax_session *s) +static int manager_fax_stats(struct mansession *s, const struct message *m) +{ + const char *action_id = astman_get_header(m, "ActionID"); + + char id_text[256] = ""; + + astman_send_ack(s, m, "FAXStats event will follow"); + + if (!ast_strlen_zero(action_id)) { + snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", action_id); + } + + astman_append(s, "Event: FAXStats\r\n" + "%s" + "CurrentSessions: %d\r\n" + "ReservedSessions: %d\r\n" + "TransmitAttempts: %d\r\n" + "ReceiveAttempts: %d\r\n" + "CompletedFAXes: %d\r\n" + "FailedFAXes: %d\r\n" + "\r\n", + id_text, + faxregistry.active_sessions, faxregistry.reserved_sessions, + faxregistry.fax_tx_attempts, faxregistry.fax_rx_attempts, + faxregistry.fax_complete, faxregistry.fax_failures); + + return 0; +} + +static const char *fax_session_type(struct ast_fax_session *s) { if (s->details->caps & AST_FAX_TECH_AUDIO) { return "G.711"; @@ -3787,7 +4035,7 @@ static const char *cli_session_type(struct ast_fax_session *s) return "none"; } -static const char *cli_session_operation(struct ast_fax_session *s) +const char *ast_fax_session_operation_str(struct ast_fax_session *s) { if (s->details->caps & AST_FAX_TECH_GATEWAY) { return "gateway"; @@ -3835,8 +4083,8 @@ static char *cli_fax_show_sessions(struct ast_cli_entry *e, int cmd, struct ast_ ast_cli(a->fd, "%-20.20s %-10.10s %-10u %-5.5s %-10.10s %-15.15s %-30s\n", s->channame, s->tech->type, s->id, - cli_session_type(s), - cli_session_operation(s), + fax_session_type(s), + ast_fax_session_operation_str(s), ast_fax_state_to_str(s->state), S_OR(filenames, "")); ast_free(filenames); @@ -3850,6 +4098,72 @@ static char *cli_fax_show_sessions(struct ast_cli_entry *e, int cmd, struct ast_ return CLI_SUCCESS; } +static int manager_fax_sessions_entry(struct mansession *s, + struct ast_fax_session *session, const char *id_text) +{ + char *filenames; + + ao2_lock(session); + filenames = generate_filenames_string(session->details, "", ","); + + if (!filenames) { + ast_log(LOG_ERROR, "Error generating Files string"); + ao2_unlock(session); + return -1; + } + + astman_append(s, "Event: FAXSessionsEntry\r\n" + "%s" /* ActionID if present */ + "Channel: %s\r\n" /* Channel name */ + "Technology: %s\r\n" /* Fax session technology */ + "SessionNumber: %u\r\n" /* Session ID */ + "SessionType: %s\r\n" /* G711 or T38 */ + "Operation: %s\r\n" + "State: %s\r\n" + "Files: %s\r\n" + "\r\n", + id_text, session->channame, session->tech->type, session->id, + fax_session_type(session), ast_fax_session_operation_str(session), + ast_fax_state_to_str(session->state), S_OR(filenames, "")); + ast_free(filenames); + ao2_unlock(session); + return 0; +} + +static int manager_fax_sessions(struct mansession *s, const struct message *m) +{ + const char *action_id = astman_get_header(m, "ActionID"); + char id_text[256] = ""; + struct ast_fax_session *session; + struct ao2_iterator iter; + int session_count = 0; + + astman_send_listack(s, m, "FAXSessionsEntry event list will follow", "Start"); + + if (!ast_strlen_zero(action_id)) { + snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", action_id); + } + + iter = ao2_iterator_init(faxregistry.container, 0); + while ((session = ao2_iterator_next(&iter))) { + if (!manager_fax_sessions_entry(s, session, id_text)) { + session_count++; + } + ao2_ref(session, -1); + } + ao2_iterator_destroy(&iter); + + astman_append(s, "Event: FAXSessionsComplete\r\n" + "%s" + "EventList: Complete\r\n" + "Total: %d\r\n" + "\r\n", + id_text, + session_count); + + return 0; +} + static struct ast_cli_entry fax_cli[] = { AST_CLI_DEFINE(cli_fax_show_version, "Show versions of FAX For Asterisk components"), AST_CLI_DEFINE(cli_fax_set_debug, "Enable/Disable FAX debugging on new FAX sessions"), @@ -4246,6 +4560,33 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; } + if (ast_manager_register_xml("FAXSessions", EVENT_FLAG_CALL, manager_fax_sessions)) { + ast_log(LOG_WARNING, "failed to register 'FAXSessions' AMI command.\n"); + ast_unregister_application(app_receivefax); + ast_unregister_application(app_sendfax); + ao2_ref(faxregistry.container, -1); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_manager_register_xml("FAXSession", EVENT_FLAG_CALL, manager_fax_session)) { + ast_log(LOG_WARNING, "failed to register 'FAXSession' AMI command.\n"); + ast_manager_unregister("FAXSession"); + ast_unregister_application(app_receivefax); + ast_unregister_application(app_sendfax); + ao2_ref(faxregistry.container, -1); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_manager_register_xml("FAXStats", EVENT_FLAG_REPORTING, manager_fax_stats)) { + ast_log(LOG_WARNING, "failed to register 'FAXStats' AMI command.\n"); + ast_manager_unregister("FAXSession"); + ast_manager_unregister("FAXSessions"); + ast_unregister_application(app_receivefax); + ast_unregister_application(app_sendfax); + ao2_ref(faxregistry.container, -1); + return AST_MODULE_LOAD_DECLINE; + } + ast_cli_register_multiple(fax_cli, ARRAY_LEN(fax_cli)); res = ast_custom_function_register(&acf_faxopt); fax_logger_level = ast_logger_register_level("FAX"); --- res/res_fax.exports.in +++ res/res_fax.exports.in.5c988cc4e6c5693f03080f88e3057cb7a5358597 @@ -6,6 +6,7 @@ LINKER_SYMBOL_PREFIXast_fax_minrate; LINKER_SYMBOL_PREFIXast_fax_maxrate; LINKER_SYMBOL_PREFIXast_fax_state_to_str; + LINKER_SYMBOL_PREFIXast_fax_session_operation_str; LINKER_SYMBOL_PREFIXast_fax_log; local: *; --- res/res_fax_spandsp.c +++ res/res_fax_spandsp.c.5c988cc4e6c5693f03080f88e3057cb7a5358597 @@ -86,6 +86,8 @@ static void spandsp_v21_tone(void *data, int code, int level, int delay); static char *spandsp_fax_cli_show_capabilities(int fd); static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd); +static void spandsp_manager_fax_session(struct mansession *s, + const char *id_text, struct ast_fax_session *session); static char *spandsp_fax_cli_show_stats(int fd); static char *spandsp_fax_cli_show_settings(int fd); @@ -113,6 +115,7 @@ static struct ast_fax_tech spandsp_fax_tech = { .switch_to_t38 = spandsp_fax_switch_to_t38, .cli_show_capabilities = spandsp_fax_cli_show_capabilities, .cli_show_session = spandsp_fax_cli_show_session, + .manager_fax_session = spandsp_manager_fax_session, .cli_show_stats = spandsp_fax_cli_show_stats, .cli_show_settings = spandsp_fax_cli_show_settings, }; @@ -1073,6 +1076,97 @@ static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd) return CLI_SUCCESS; } +static void spandsp_manager_fax_session(struct mansession *s, + const char *id_text, struct ast_fax_session *session) +{ + struct ast_str *message_string; + struct spandsp_pvt *span_pvt = session->tech_pvt; + int res; + + message_string = ast_str_create(128); + + if (!message_string) { + return; + } + + ao2_lock(session); + res = ast_str_append(&message_string, 0, "SessionNumber: %d\r\n", session->id); + res |= ast_str_append(&message_string, 0, "Operation: %s\r\n", ast_fax_session_operation_str(session)); + res |= ast_str_append(&message_string, 0, "State: %s\r\n", ast_fax_state_to_str(session->state)); + + if (session->details->caps & AST_FAX_TECH_GATEWAY) { + t38_stats_t stats; + + if (session->state == AST_FAX_STATE_UNINITIALIZED) { + goto skip_cap_additions; + } + + t38_gateway_get_transfer_statistics(&span_pvt->t38_gw_state, &stats); + res |= ast_str_append(&message_string, 0, "ErrorCorrectionMode: %s\r\n", + stats.error_correcting_mode ? "yes" : "no"); + res |= ast_str_append(&message_string, 0, "DataRate: %d\r\n", + stats.bit_rate); + res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n", + stats.pages_transferred + 1); + } else if (!(session->details->caps & AST_FAX_TECH_V21_DETECT)) { /* caps is SEND/RECEIVE */ + t30_stats_t stats; + + if (session->state == AST_FAX_STATE_UNINITIALIZED) { + goto skip_cap_additions; + } + + t30_get_transfer_statistics(span_pvt->t30_state, &stats); + res |= ast_str_append(&message_string, 0, "ErrorCorrectionMode: %s\r\n", + stats.error_correcting_mode ? "Yes" : "No"); + res |= ast_str_append(&message_string, 0, "DataRate: %d\r\n", + stats.bit_rate); + res |= ast_str_append(&message_string, 0, "ImageResolution: %dx%d\r\n", + stats.x_resolution, stats.y_resolution); +#if SPANDSP_RELEASE_DATE >= 20090220 + res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n", + ((session->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx) + 1); +#else + res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n", + stats.pages_transferred + 1); +#endif + res |= ast_str_append(&message_string, 0, "FileName: %s\r\n", + session->details->caps & AST_FAX_TECH_RECEIVE ? span_pvt->t30_state->rx_file : + span_pvt->t30_state->tx_file); +#if SPANDSP_RELEASE_DATE >= 20090220 + res |= ast_str_append(&message_string, 0, "PagesTransmitted: %d\r\n", + stats.pages_tx); + res |= ast_str_append(&message_string, 0, "PagesReceived: %d\r\n", + stats.pages_rx); +#else + res |= ast_str_append(&message_string, 0, "PagesTransmitted: %d\r\n", + (session->details->caps & AST_FAX_TECH_SEND) ? stats.pages_transferred : 0); + res |= ast_str_append(&message_string, 0, "PagesReceived: %d\r\n", + (session->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_transferred : 0); +#endif + res |= ast_str_append(&message_string, 0, "TotalBadLines: %d\r\n", + stats.bad_rows); + } + +skip_cap_additions: + + ao2_unlock(session); + + if (res < 0) { + /* One or more of the ast_str_append attempts failed, cancel the message */ + ast_free(message_string); + return; + } + + astman_append(s, "Event: FAXSession\r\n" + "%s" + "%s" + "\r\n", + id_text, + ast_str_buffer(message_string)); + + ast_free(message_string); +} + /*! \brief */ static char *spandsp_fax_cli_show_stats(int fd) {