From 87973eecffbf400da98b67eca1dbed038af40439 Mon Sep 17 00:00:00 2001
From: Jason Parker <jparker@digium.com>
Date: Mon, 8 Jul 2013 14:46:20 +0000
Subject: [PATCH] ARI: Add support for getting/setting channel and global
 variables.

This allows for reading and writing of functions on channels.

(closes issue ASTERISK-21868)

Review: https://reviewboard.asterisk.org/r/2641/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393806 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 include/asterisk/stasis_app.h       | 19 ++++++
 res/res_stasis_http_asterisk.c      | 61 ++++++++++++++++++-
 res/res_stasis_http_channels.c      | 73 ++++++++++++++++++++++-
 res/stasis/control.c                | 28 +++++++++
 res/stasis_http/resource_asterisk.c | 41 +++++++++++++
 res/stasis_http/resource_asterisk.h | 28 +++++++++
 res/stasis_http/resource_channels.c | 52 ++++++++++++++++
 res/stasis_http/resource_channels.h | 32 ++++++++++
 rest-api/api-docs/asterisk.json     | 56 ++++++++++++++++++
 rest-api/api-docs/channels.json     | 92 +++++++++++++++++++++++++++++
 10 files changed, 478 insertions(+), 4 deletions(-)

diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 93d5cf36c8..33091409c7 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -173,6 +173,25 @@ int stasis_app_control_continue(struct stasis_app_control *control, const char *
  */
 int stasis_app_control_answer(struct stasis_app_control *control);
 
+/*!
+ * \brief Get the value of a variable on the channel associated with this control.
+ * \param control Control for \c res_stasis.
+ * \param variable The name of the variable.
+ * \return The value of the variable.  The returned variable must be freed.
+ */
+char *stasis_app_control_get_channel_var(struct stasis_app_control *control, const char *variable);
+
+/*!
+ * \brief Set a variable on the channel associated with this control to value.
+ * \param control Control for \c res_stasis.
+ * \param variable The name of the variable
+ * \param value The value to set the variable to
+ *
+ * \return 0 for success.
+ * \return -1 for error.
+ */
+int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value);
+
 /*!
  * \brief Place the channel associated with the control on hold.
  * \param control Control for \c res_stasis.
diff --git a/res/res_stasis_http_asterisk.c b/res/res_stasis_http_asterisk.c
index 01f082ad69..c0ff660cdf 100644
--- a/res/res_stasis_http_asterisk.c
+++ b/res/res_stasis_http_asterisk.c
@@ -98,6 +98,53 @@ static void stasis_http_get_asterisk_info_cb(
 	}
 #endif /* AST_DEVMODE */
 }
+/*!
+ * \brief Parameter parsing callback for /asterisk/variable.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_global_var_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+	struct ast_get_global_var_args args = {};
+	struct ast_variable *i;
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		{}
+	}
+	stasis_http_get_global_var(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /asterisk/variable.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_set_global_var_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+	struct ast_set_global_var_args args = {};
+	struct ast_variable *i;
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		if (strcmp(i->name, "value") == 0) {
+			args.value = (i->value);
+		} else
+		{}
+	}
+	stasis_http_set_global_var(headers, &args, response);
+}
 
 /*! \brief REST handler for /api-docs/asterisk.{format} */
 static struct stasis_rest_handlers asterisk_info = {
@@ -109,12 +156,22 @@ static struct stasis_rest_handlers asterisk_info = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_variable = {
+	.path_segment = "variable",
+	.callbacks = {
+		[AST_HTTP_GET] = stasis_http_get_global_var_cb,
+		[AST_HTTP_POST] = stasis_http_set_global_var_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
 static struct stasis_rest_handlers asterisk = {
 	.path_segment = "asterisk",
 	.callbacks = {
 	},
-	.num_children = 1,
-	.children = { &asterisk_info, }
+	.num_children = 2,
+	.children = { &asterisk_info,&asterisk_variable, }
 };
 
 static int load_module(void)
diff --git a/res/res_stasis_http_channels.c b/res/res_stasis_http_channels.c
index 108f356c10..a3d2932a27 100644
--- a/res/res_stasis_http_channels.c
+++ b/res/res_stasis_http_channels.c
@@ -810,6 +810,65 @@ static void stasis_http_record_channel_cb(
 	}
 #endif /* AST_DEVMODE */
 }
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variable.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_channel_var_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+	struct ast_get_channel_var_args args = {};
+	struct ast_variable *i;
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (i->value);
+		} else
+		{}
+	}
+	stasis_http_get_channel_var(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variable.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_set_channel_var_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+	struct ast_set_channel_var_args args = {};
+	struct ast_variable *i;
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		if (strcmp(i->name, "value") == 0) {
+			args.value = (i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (i->value);
+		} else
+		{}
+	}
+	stasis_http_set_channel_var(headers, &args, response);
+}
 
 /*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels_channelId_dial = {
@@ -893,6 +952,16 @@ static struct stasis_rest_handlers channels_channelId_record = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_variable = {
+	.path_segment = "variable",
+	.callbacks = {
+		[AST_HTTP_GET] = stasis_http_get_channel_var_cb,
+		[AST_HTTP_POST] = stasis_http_set_channel_var_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels_channelId = {
 	.path_segment = "channelId",
 	.is_wildcard = 1,
@@ -900,8 +969,8 @@ static struct stasis_rest_handlers channels_channelId = {
 		[AST_HTTP_GET] = stasis_http_get_channel_cb,
 		[AST_HTTP_DELETE] = stasis_http_delete_channel_cb,
 	},
-	.num_children = 9,
-	.children = { &channels_channelId_dial,&channels_channelId_continue,&channels_channelId_answer,&channels_channelId_mute,&channels_channelId_unmute,&channels_channelId_hold,&channels_channelId_unhold,&channels_channelId_play,&channels_channelId_record, }
+	.num_children = 10,
+	.children = { &channels_channelId_dial,&channels_channelId_continue,&channels_channelId_answer,&channels_channelId_mute,&channels_channelId_unmute,&channels_channelId_hold,&channels_channelId_unhold,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable, }
 };
 /*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels = {
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 2bba842edf..6a7a9ba4d5 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -207,6 +207,34 @@ int stasis_app_control_continue(struct stasis_app_control *control, const char *
 	return 0;
 }
 
+char *stasis_app_control_get_channel_var(struct stasis_app_control *control, const char *variable)
+{
+	SCOPED_CHANNELLOCK(lockvar, control->channel);
+
+	RAII_VAR(struct ast_str *, tmp, ast_str_create(32), ast_free);
+
+	if (!tmp) {
+		return NULL;
+	}
+
+	if (variable[strlen(variable) - 1] == ')') {
+		if (ast_func_read2(control->channel, variable, &tmp, 0)) {
+			return NULL;
+		}
+	} else {
+		if (!ast_str_retrieve_variable(&tmp, 0, control->channel, NULL, variable)) {
+			return NULL;
+		}
+	}
+
+	return ast_strdup(ast_str_buffer(tmp));
+}
+
+int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value)
+{
+	return pbx_builtin_setvar_helper(control->channel, variable, value);
+}
+
 static void *app_control_hold(struct stasis_app_control *control,
 	struct ast_channel *chan, void *data)
 {
diff --git a/res/stasis_http/resource_asterisk.c b/res/stasis_http/resource_asterisk.c
index b5e8b0f548..c2a0bc0cc2 100644
--- a/res/stasis_http/resource_asterisk.c
+++ b/res/stasis_http/resource_asterisk.c
@@ -32,8 +32,49 @@
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "resource_asterisk.h"
+#include "asterisk/pbx.h"
 
 void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response)
 {
 	ast_log(LOG_ERROR, "TODO: stasis_http_get_asterisk_info\n");
 }
+
+void stasis_http_get_global_var(struct ast_variable *headers, struct ast_get_global_var_args *args, struct stasis_http_response *response)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+	RAII_VAR(struct ast_str *, tmp, ast_str_create(32), ast_free);
+
+	const char *value;
+
+	ast_assert(response != NULL);
+
+	if (!tmp) {
+		stasis_http_response_alloc_failed(response);
+		return;
+	}
+
+	value = ast_str_retrieve_variable(&tmp, 0, NULL, NULL, args->variable);
+
+	if (!(json = ast_json_pack("{s: s}", "value", S_OR(value, "")))) {
+		stasis_http_response_alloc_failed(response);
+		return;
+	}
+
+	stasis_http_response_ok(response, ast_json_ref(json));
+}
+
+void stasis_http_set_global_var(struct ast_variable *headers, struct ast_set_global_var_args *args, struct stasis_http_response *response)
+{
+	ast_assert(response != NULL);
+
+	if (ast_strlen_zero(args->variable)) {
+		stasis_http_response_error(
+			response, 400, "Bad Request",
+			"Variable name is required");
+		return;
+	}
+
+	pbx_builtin_setvar_helper(NULL, args->variable, args->value);
+
+	stasis_http_response_no_content(response);
+}
diff --git a/res/stasis_http/resource_asterisk.h b/res/stasis_http/resource_asterisk.h
index 0d373ccb29..e32e919ab6 100644
--- a/res/stasis_http/resource_asterisk.h
+++ b/res/stasis_http/resource_asterisk.h
@@ -52,5 +52,33 @@ struct ast_get_asterisk_info_args {
  * \param[out] response HTTP response
  */
 void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_global_var() */
+struct ast_get_global_var_args {
+	/*! \brief The variable to get */
+	const char *variable;
+};
+/*!
+ * \brief Get the value of a global variable.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_global_var(struct ast_variable *headers, struct ast_get_global_var_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_set_global_var() */
+struct ast_set_global_var_args {
+	/*! \brief The variable to set */
+	const char *variable;
+	/*! \brief The value to set the variable to */
+	const char *value;
+};
+/*!
+ * \brief Set the value of a global variable.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_set_global_var(struct ast_variable *headers, struct ast_set_global_var_args *args, struct stasis_http_response *response);
 
 #endif /* _ASTERISK_RESOURCE_ASTERISK_H */
diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c
index 8db3b697c1..1700b86cae 100644
--- a/res/stasis_http/resource_channels.c
+++ b/res/stasis_http/resource_channels.c
@@ -561,3 +561,55 @@ void stasis_http_originate(struct ast_variable *headers,
 
 	stasis_http_response_no_content(response);
 }
+
+void stasis_http_get_channel_var(struct ast_variable *headers, struct ast_get_channel_var_args *args, struct stasis_http_response *response)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+	RAII_VAR(char *, value, NULL, ast_free);
+
+	ast_assert(response != NULL);
+
+	control = find_control(response, args->channel_id);
+	if (control == NULL) {
+		return;
+	}
+
+	value = stasis_app_control_get_channel_var(control, args->variable);
+
+	if (!(json = ast_json_pack("{s: s}", "value", S_OR(value, "")))) {
+		stasis_http_response_alloc_failed(response);
+		return;
+	}
+
+	stasis_http_response_ok(response, ast_json_ref(json));
+}
+
+void stasis_http_set_channel_var(struct ast_variable *headers, struct ast_set_channel_var_args *args, struct stasis_http_response *response)
+{
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+	ast_assert(response != NULL);
+
+	control = find_control(response, args->channel_id);
+	if (control == NULL) {
+		return;
+	}
+
+	if (ast_strlen_zero(args->variable)) {
+		stasis_http_response_error(
+			response, 400, "Bad Request",
+			"Variable name is required");
+		return;
+	}
+
+	if (stasis_app_control_set_channel_var(control, args->variable, args->value)) {
+		stasis_http_response_error(
+			response, 400, "Bad Request",
+			"Failed to execute function");
+		return;
+	}
+
+	stasis_http_response_no_content(response);
+}
+
diff --git a/res/stasis_http/resource_channels.h b/res/stasis_http/resource_channels.h
index 7e8dc5dbe6..999e169545 100644
--- a/res/stasis_http/resource_channels.h
+++ b/res/stasis_http/resource_channels.h
@@ -264,5 +264,37 @@ struct ast_record_channel_args {
  * \param[out] response HTTP response
  */
 void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_channel_var() */
+struct ast_get_channel_var_args {
+	/*! \brief Channel's id */
+	const char *channel_id;
+	/*! \brief The channel variable or function to get */
+	const char *variable;
+};
+/*!
+ * \brief Get the value of a channel variable or function.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_channel_var(struct ast_variable *headers, struct ast_get_channel_var_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_set_channel_var() */
+struct ast_set_channel_var_args {
+	/*! \brief Channel's id */
+	const char *channel_id;
+	/*! \brief The channel variable or function to set */
+	const char *variable;
+	/*! \brief The value to set the variable to */
+	const char *value;
+};
+/*!
+ * \brief Set the value of a channel variable or function.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_set_channel_var(struct ast_variable *headers, struct ast_set_channel_var_args *args, struct stasis_http_response *response);
 
 #endif /* _ASTERISK_RESOURCE_CHANNELS_H */
diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json
index 8ee88e4394..8c404a075e 100644
--- a/rest-api/api-docs/asterisk.json
+++ b/rest-api/api-docs/asterisk.json
@@ -36,6 +36,52 @@
 					]
 				}
 			]
+		},
+		{
+			"path": "/asterisk/variable",
+			"description": "Global variables",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"summary": "Get the value of a global variable.",
+					"nickname": "getGlobalVar",
+					"responseClass": "Variable",
+					"parameters": [
+						{
+							"name": "variable",
+							"description": "The variable to get",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					]
+				},
+				{
+					"httpMethod": "POST",
+					"summary": "Set the value of a global variable.",
+					"nickname": "setGlobalVar",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "variable",
+							"description": "The variable to set",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "value",
+							"description": "The value to set the variable to",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					]
+				}
+			]
 		}
 	],
 	"models": {
@@ -43,6 +89,16 @@
 			"id": "AsteriskInfo",
 			"description": "Asterisk system information",
 			"properties": {}
+		},
+		"Variable": {
+			"id": "Variable",
+			"properties": {
+				"variable": {
+					"required": true,
+					"type": "string",
+					"description": "The value of the variable requested"
+				}
+			}
 		}
 	}
 }
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 9900db7394..a97f225669 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -645,6 +645,88 @@
 					]
 				}
 			]
+		},
+		{
+			"path": "/channels/{channelId}/variable",
+			"description": "Variables on a channel",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"summary": "Get the value of a channel variable or function.",
+					"nickname": "getChannelVar",
+					"responseClass": "ChannelVariable",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "Channel's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variable",
+							"description": "The channel variable or function to get",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Channel not found"
+						},
+						{
+							"code": 409,
+							"reason": "Channel not in a Stasis application"
+						}
+					]
+				},
+				{
+					"httpMethod": "POST",
+					"summary": "Set the value of a channel variable or function.",
+					"nickname": "setChannelVar",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "Channel's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variable",
+							"description": "The channel variable or function to set",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "value",
+							"description": "The value to set the variable to",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Channel not found"
+						},
+						{
+							"code": 409,
+							"reason": "Channel not in a Stasis application"
+						}
+					]
+				}
+			]
 		}
 	],
 	"models": {
@@ -745,6 +827,16 @@
 					"description": "Timestamp when channel was created"
 				}
 			}
+		},
+		"Variable": {
+			"id": "Variable",
+			"properties": {
+				"variable": {
+					"required": true,
+					"type": "string",
+					"description": "The value of the variable requested"
+				}
+			}
 		}
 	}
 }
-- 
GitLab