[Freeswitch-svn] [commit] r11219 - freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill

FreeSWITCH SVN dschreiber at freeswitch.org
Wed Jan 14 23:56:15 PST 2009


Author: dschreiber
Date: Thu Jan 15 01:56:15 2009
New Revision: 11219

Log:
Updated nibblebill to add some dialplan applications

more to come



Modified:
   freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c

Modified: freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c
==============================================================================
--- freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c	(original)
+++ freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c	Thu Jan 15 01:56:15 2009
@@ -29,9 +29,15 @@
  * Darren Schreiber <d at d-man.org>
  *
  * mod_nibblebill.c - Nibble Billing
- * Set the session heartbeat on a call that has a billrate and deduct currency from
- * a database table each "tick" of the billing until funds are depleted.
- * Terminate the call upon depletion.
+ * Purpose is to allow real-time debiting of credit or cash from a database while calls are in progress. I had the following goals: 
+ *
+ * Debit credit/cash from accounts real-time 
+ * Allow for billing at different rates during a single call 
+ * Allow for warning callers when their balance is low (via audio, in-channel) 
+ * Allow for disconnecting or re-routing calls when balance is depleted 
+ * Allow for billing to function as listed above with multiple concurrent calls 
+ * 
+ * Thanks go to bandwidth.com for funding this work.
  *
  *
  * TODO: Create an override flag so you can force a start-time for when billing should count *from*
@@ -52,13 +58,20 @@
 #endif
 
 /* Defaults */
-/* static char SQL_LOOKUP[] = "SELECT cash FROM accounts WHERE id=\"%s\""; */
-static char SQL_SAVE[] = "UPDATE accounts SET cash=cash-%f WHERE id=\"%s\"";
+#ifdef SWITCH_HAVE_ODBC
+/*static char SQL_LOOKUP[] = "SELECT %s FROM %s WHERE %s=\"%s\"";*/
+static char SQL_SAVE[] = "UPDATE %s SET %s=%s-%f WHERE %s=\"%s\"";
+#endif
 
 typedef struct
 {
 	switch_time_t lastts;	/* Last time we did any billing */
 	float total;	/* Total amount billed so far */
+
+	switch_time_t pausets;	/* Timestamp of when a pause action started. 0 if not paused */
+	float bill_adjustments;	/* Adjustments to make to the next billing, based on pause/resume events */
+
+	float manual_adjustments;	/* Manually added/deducted amounts from this account, to be committed at next database query */
 } nibble_data_t;
 
 
@@ -88,12 +101,16 @@
 	float	nobal_amt;		/* Minimum amount that must remain in the account */
 	char	*nobal_action;		/* Drop action */
 
+	/* Other options */
+	int	global_heartbeat;	/* Supervise and bill every X seconds, 0 means off */
+
 	/* Database settings */
 	char *db_username;
 	char *db_password;
 	char *db_dsn;
-	char *lookup_query;
-	char *save_query;
+	char *db_table;
+	char *db_column_cash;
+	char *db_column_account;
 #ifdef SWITCH_HAVE_ODBC
 	switch_odbc_handle_t *master_odbc;
 #else   
@@ -117,12 +134,14 @@
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_username, globals.db_username);
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_password, globals.db_password);
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_dsn, globals.db_dsn);
-SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_lookup_query, globals.lookup_query);
-SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_save_query, globals.save_query);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_table, globals.db_table);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_column_cash, globals.db_column_cash);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_column_account, globals.db_column_account);
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_percall_action, globals.percall_action);
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_lowbal_action, globals.lowbal_action);
 SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_nobal_action, globals.nobal_action);
 
+#ifdef SWITCH_HAVE_ODBC
 static int nibblebill_callback(void *pArg, int argc, char **argv, char **columnNames)
 {
 	nibblebill_results_t *cbt = (nibblebill_results_t *) pArg;
@@ -131,6 +150,7 @@
 
 	return 0;
 }
+#endif
 
 static switch_status_t load_config(void)
 {
@@ -141,7 +161,7 @@
 	if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf);
 		status = SWITCH_STATUS_SUCCESS;  /* We don't fail because we can still write to a text file or buffer */
-		goto done;
+		goto setdefaults;
 	}
 	
 	if ((settings = switch_xml_child(cfg, "settings"))) {
@@ -149,35 +169,46 @@
 			char *var = (char *) switch_xml_attr_soft(param, "name");
 			char *val = (char *) switch_xml_attr_soft(param, "value");
 
-			if (!strcasecmp(var, "db-username")) {
+			if (!strcasecmp(var, "db_username")) {
 				set_global_db_username(val);
-			} else if (!strcasecmp(var, "db-password")) {
+			} else if (!strcasecmp(var, "db_password")) {
 				set_global_db_password(val);
-			} else if (!strcasecmp(var, "db-dsn")) {
+			} else if (!strcasecmp(var, "db_dsn")) {
 				set_global_db_dsn(val);
-			} else if (!strcasecmp(var, "lookup_query")) {
-				set_global_lookup_query(val);
-			} else if (!strcasecmp(var, "save_query")) {
-				set_global_save_query(val);
+			} else if (!strcasecmp(var, "db_table")) {
+				set_global_db_table(val);
+			} else if (!strcasecmp(var, "db_column_cash")) {
+				set_global_db_column_cash(val);
+			} else if (!strcasecmp(var, "db_column_account")) {
+				set_global_db_column_account(val);
 			} else if (!strcasecmp(var, "percall_action")) {
 				set_global_percall_action(val);
+			} else if (!strcasecmp(var, "percall_max_amt")) {
+				globals.percall_max_amt = atof(val);
 			} else if (!strcasecmp(var, "lowbal_action")) {
 				set_global_lowbal_action(val);
+			} else if (!strcasecmp(var, "lowbal_amt")) {
+				globals.lowbal_amt = atof(val);
 			} else if (!strcasecmp(var, "nobal_action")) {
 				set_global_nobal_action(val);
+			} else if (!strcasecmp(var, "nobal_amt")) {
+				globals.nobal_amt = atof(val);
+			} else if (!strcasecmp(var, "global_heartbeat")) {
+				globals.global_heartbeat = atoi(val);
 			}
 		}
 	}
 	
-done:
+/* Set defaults for any variables still not set */
+setdefaults:
 	if (switch_strlen_zero(globals.db_username)) {
-		set_global_db_username("phonebooth");
+		set_global_db_username("bandwidth.com");
 	}
 	if (switch_strlen_zero(globals.db_password)) {
 		set_global_db_password("dev");
 	}
 	if (switch_strlen_zero(globals.db_dsn)) {
-	set_global_db_dsn("phonebooth");
+		set_global_db_dsn("bandwidth.com");
 	}
 	if (switch_strlen_zero(globals.percall_action)) {
 		set_global_percall_action("hangup");
@@ -188,14 +219,13 @@
 	if (switch_strlen_zero(globals.nobal_action)) {
 		set_global_nobal_action("hangup");
 	}
-	/* Need to add defaults for the integers! */
 
 #ifdef SWITCH_HAVE_ODBC
 	if (globals.db_dsn) {
 		if (!(globals.master_odbc = switch_odbc_handle_new(globals.db_dsn, globals.db_username, globals.db_password))) {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot create handle to ODBC Database!\n");
 			status = SWITCH_STATUS_FALSE;
-			goto reallydone;
+			goto done;
 		} else {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Opened ODBC Database handle!\n");
 		}
@@ -203,7 +233,7 @@
 		if (switch_odbc_handle_connect(globals.master_odbc) != SWITCH_ODBC_SUCCESS) {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot connect to ODBC driver/database %s (user: %s / pass %s)!\n", globals.db_dsn, globals.db_username, globals.db_password);
 			status = SWITCH_STATUS_FALSE;
-			goto reallydone;
+			goto done;
 		} else {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Opened ODBC Database!\n");
 		}
@@ -216,7 +246,7 @@
 #endif
 
 #ifdef SWITCH_HAVE_ODBC
-reallydone:
+done:
 #endif
 
 	if (xml) {
@@ -245,7 +275,11 @@
 static switch_status_t sched_billing(switch_core_session_t *session)
 {
 
-	switch_core_session_enable_heartbeat(session, 60);
+	if (globals.global_heartbeat > 0) {
+		switch_core_session_enable_heartbeat(session, globals.global_heartbeat);
+	}
+
+	// Check account balance here
 
 	return SWITCH_STATUS_SUCCESS;
 
@@ -263,7 +297,9 @@
 	nibble_data_t *nibble_data;
 	switch_time_t ts = switch_timestamp_now();
 	float billamount;
+	float adjustments = 0;
 	char date[80] = "";
+	char *tmp;
 	char *uuid;
 	switch_size_t retsize;
 	switch_time_exp_t tm;
@@ -313,6 +349,15 @@
 	/* Get our nibble data var. This will be NULL if it's our first call here for this session */
 	nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
 
+	/* Are we in paused mode? If so, we don't do anything here - go back! */
+	if (nibble_data->pausets > 0) {
+		if (globals.mutex) {
+			switch_mutex_unlock(globals.mutex);
+		}
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received heartbeat, but we're paused - ignoring\n");
+		return SWITCH_STATUS_SUCCESS;
+	}
+
 	/* Have we done any billing on this channel yet? If no, set up vars for doing so */
 	if (!nibble_data) {
 		nibble_data = switch_core_session_alloc(session, sizeof(*nibble_data));
@@ -337,34 +382,46 @@
 	if ((ts - nibble_data->lastts) > 0) {
 		/* Convert billrate into microseconds and multiply by # of microseconds that have passed */
 		billamount = (atof(billrate) / 1000000 / 60) * ((ts - nibble_data->lastts));
+		adjustments = nibble_data->manual_adjustments + nibble_data->bill_adjustments;
 
 		/* if ODBC call fails, we should return BEFORE updating the timestamp of last success! */
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Billing $%f to %s (Call: %s / %f so far)\n", billamount, billaccount, uuid, nibble_data->total);
 
+		if (adjustments > 0) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "(Also adjusting the account by $%f)\n", adjustments);
+		}
+
 		/* DO ODBC BILLING HERE! */
 #ifdef SWITCH_HAVE_ODBC
 		char sql[1024] = "";
 		nibblebill_results_t pdata;
 
 		memset(&pdata, 0, sizeof(pdata));
-		if (!globals.save_query){
-			snprintf(sql, 1024, SQL_SAVE, billamount, billaccount);
-		} else {
-			snprintf(sql, 1024, globals.save_query, billamount, billaccount);
-		}
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,  "Doing static update query\n[%s]\n", sql);
+		snprintf(sql, 1024, SQL_SAVE, globals.db_table, globals.db_column_cash, globals.db_column_cash, (billamount - adjustments), globals.db_column_account, billaccount);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,  "Doing update query\n[%s]\n", sql);
 
 		if (!(switch_odbc_handle_callback_exec(globals.master_odbc, sql, nibblebill_callback, &pdata) == SWITCH_ODBC_SUCCESS)){
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "DB Error while updating cash!\n");
 
 			/* TODO: If this is a hangup event, we should store this billing in a text file while the DB is unavailable */
-		}
+		} else {
 #endif
+			/* Increment total cost */
+			nibble_data->total += billamount;
 
-		/* Increment total cost */
-		nibble_data->total += billamount;
+			/* Get rid of any manual adjustments that were just committed */
+			nibble_data->manual_adjustments = 0;
+			nibble_data->bill_adjustments = 0;
+
+			/* Update channel variable with current billing */
+			tmp = switch_mprintf("%f", nibble_data->total);
+			switch_channel_set_variable(channel, "nibble_total_billed", tmp);
+			switch_safe_free(tmp);
+#ifdef SWITCH_HAVE_ODBC
+		}
+#endif
 	} else {
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Just tried to bill %s negative minutes! wtf?\n", uuid);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Just tried to bill %s negative minutes! wtf? That should be impossible.\n", uuid);
 	}
 
 	/* Update the last time we billed */
@@ -373,14 +430,16 @@
 	/* Save everything back */
 	switch_channel_set_private(channel, "_nibble_data_", nibble_data);
 
-	/* Release the session lock */
-	switch_core_session_rwunlock(session);
-
 	/* Done changing - release lock */
 	if (globals.mutex) {
 		switch_mutex_unlock(globals.mutex);
 	}
 
+	/* Go check if this call is allowed to continue */
+
+	/* Release the session lock */
+	switch_core_session_rwunlock(session);
+
 	return SWITCH_STATUS_SUCCESS;
 }
 
@@ -421,28 +480,99 @@
 	switch_core_session_rwunlock(session);
 }
 
-SWITCH_STANDARD_APP(nibblebill_app_function)
+
+static float nibblebill_check(switch_core_session_t *session)
 {
 	switch_channel_t *channel = switch_core_session_get_channel(session);
+	nibble_data_t *nibble_data;
+	float amount = 0;
 
-	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "We made it!\n");
+	if (!channel) {
+		return -99999;
+	}
+
+	/* Lock this session's data for this module while we tinker with it */
+	if (globals.mutex) {
+		switch_mutex_lock(globals.mutex);
+	}
+
+	/* Get our nibble data var. This will be NULL if it's our first call here for this session */
+	nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
+
+	amount = nibble_data->total;
+
+	/* Done checking - release lock */
+	if (globals.mutex) {
+		switch_mutex_unlock(globals.mutex);
+	}
+
+	return amount;
+}
+
+static void nibblebill_adjust(switch_core_session_t *session, float amount)
+{
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+	nibble_data_t *nibble_data;
+	char *uuid;
 
 	if (!channel) {
 		return;
 	}
+
+	uuid = switch_core_session_get_uuid(session);
+
+	/* Lock this session's data for this module while we tinker with it */
+	if (globals.mutex) {
+		switch_mutex_lock(globals.mutex);
+	}
+
+	/* Get our nibble data var. This will be NULL if it's our first call here for this session */
+	nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
+
+	/*Add or remove amount from adjusted billing here */
+	nibble_data->manual_adjustments += amount;
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Current adjustment for %s is $%f\n", uuid, amount);
+
+	/* Done changing - release lock */
+	if (globals.mutex) {
+		switch_mutex_unlock(globals.mutex);
+	}
+
+	/* Release the session lock */
+	switch_core_session_rwunlock(session);
+}
+
+#define CHECK_APP_SYNTAX ""
+SWITCH_STANDARD_APP(nibblebill_check_app_function)
+{
+	float amount;
+
+	amount = nibblebill_check(session);
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Current billing is at $%f\n", amount);
+}
+
+#define ADJUST_APP_SYNTAX "<amount>"
+SWITCH_STANDARD_APP(nibblebill_adjust_app_function)
+{
+	char *mydata = NULL;
+	int argc = 0;
+	char *argv[4] = { 0 };
+	float amount;
 	
-/*	if (!(mydata = switch_core_session_strdup(session, data))) {
+	if (!(mydata = switch_core_session_strdup(session, data))) {
 		return;
 	}
 	
 	if ((argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
-		destnum = argv[0];
-		do_billing(session);
-	}*/
+		amount = atof(argv[0]);
+		nibblebill_adjust(session, amount);
+	}
 }
 
 /* We get here from the API only (theoretically) */
-SWITCH_STANDARD_API(nibblebill_function)
+#define CHECK_API_SYNTAX "<uuid>"
+SWITCH_STANDARD_API(nibblebill_check_api_function)
 {
 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "We made it!\n");
 	
@@ -459,7 +589,7 @@
 {
 	/* on_init */   	NULL,
 	/* on_routing */   	NULL,	/* Need to add a check here for anything in their account before routing */
-	/* on_execute */   	sched_billing,  /* Turn on heartbeat for this session */
+	/* on_execute */   	sched_billing,  /* Turn on heartbeat for this session and do an initial account check */
 	/* on_hangup */   	do_billing, /* On hangup - most important place to go bill */
 	/* on_exch_media */   	NULL,
 	/* on_soft_exec */   	NULL,
@@ -479,8 +609,20 @@
 	
 	/* connect my internal structure to the blank pointer passed to me */
 	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
-	SWITCH_ADD_API(api_interface, "check", "Check the balance of an account", nibblebill_function, "");
-	SWITCH_ADD_APP(app_interface, "check", "Check the balance on an account", "Perform an account check for fund balance", nibblebill_app_function, "<number>", SAF_SUPPORT_NOMEDIA);
+
+	/* Add API and CLI commands */
+/*	SWITCH_ADD_API(api_interface, "pause", "Pause billing on a session", nibblebill_pause_api_function, PAUSE_API_SYNTAX);
+	SWITCH_ADD_API(api_interface, "resume", "Resume billing on a session", nibblebill_resume_api_function, RESUME_API_SYNTAX);
+	SWITCH_ADD_API(api_interface, "reset", "Reset billing start time to right now - disregard any unbilled time thus far", nibblebill_reset_api_function, RESET_API_SYNTAX);
+	SWITCH_ADD_API(api_interface, "adjust", "Add or deduct a dollar amount from a caller's account", nibblebill_adjust_api_function, ADJUST_API_SYNTAX);*/
+	SWITCH_ADD_API(api_interface, "check", "Check the balance of a session", nibblebill_check_api_function, CHECK_API_SYNTAX);
+
+	/* Add dialplan applications */
+/*	SWITCH_ADD_APP(app_interface, "pause", "Pause billing on a session", "Pause billing on a session", nibblebill_pause_app_function, PAUSE_APP_SYNTAX, SAF_NONE);
+	SWITCH_ADD_APP(app_interface, "resume", "Resume billing on a session", "Resume billing on a session", nibblebill_resume_app_function, RESUME_APP_SYNTAX, SAF_NONE);
+	SWITCH_ADD_APP(app_interface, "reset", "Check the balance of a session", "Check the balance of a session", nibblebill_reset_app_function, RESET_APP_SYNTAX, SAF_NONE);*/
+	SWITCH_ADD_APP(app_interface, "adjust", "Add or deduct a dollar amount from a caller's account", "Add or deduct a dollar amount from a callers' account (use negative numbers to deduct)", nibblebill_adjust_app_function, ADJUST_APP_SYNTAX, SAF_NONE);
+	SWITCH_ADD_APP(app_interface, "check", "Check the balance on an account", "Perform an account check for fund balance on a specific caller's account", nibblebill_check_app_function, CHECK_APP_SYNTAX, SAF_NONE);
 
 	/* register state handlers for billing */
 	switch_core_add_state_handler(&nibble_state_handler);



More information about the Freeswitch-svn mailing list