summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-02-24 00:14:54 +0100
committerwm4 <wm4@nowhere>2020-02-24 00:14:54 +0100
commitb05550fe550cafb61cbb0214fb43d8bc93898ec0 (patch)
treeb7191347c06a123662cbf32af0fd963b6881ef63
parenta9e6b9ea36ddb430f8d7a45cf85df1dc01e5acca (diff)
downloadmpv-b05550fe550cafb61cbb0214fb43d8bc93898ec0.tar.bz2
mpv-b05550fe550cafb61cbb0214fb43d8bc93898ec0.tar.xz
ipc: implement asynchronous commands
I decided to make this explicit. The alternative would have been making all commands asynchronous always, like a small note in the manpage threatened. I think that could have caused compatibility issues. As a design decision, this does not send a reply if an async command started. This could be a good or bad idea, but in any case, it will make async command look almost like synchronous ones, except they don't block the IPC protocol.
-rw-r--r--DOCS/man/ipc.rst35
-rw-r--r--input/ipc.c66
2 files changed, 90 insertions, 11 deletions
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index 17bd373e2c..6d0f9c0d9c 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -136,9 +136,6 @@ Would generate this response:
If you don't specify a ``request_id``, command replies will set it to 0.
-Commands may run asynchronously in the future, instead of blocking the socket
-until a reply is sent.
-
All commands, replies, and events are separated from each other with a line
break character (``\n``).
@@ -150,6 +147,38 @@ with ``#`` and empty lines are ignored.
Currently, embedded 0 bytes terminate the current line, but you should not
rely on this.
+Asynchronous commands
+---------------------
+
+Command can be run asynchronously. This behaves exactly as with normal command
+execution, except that execution is not blocking. Other commands can be sent
+while it's executing, and command completion can be arbitrarily reordered.
+
+The ``async`` field controls this. If present, it must be a boolean. If missing,
+``false`` is assumed.
+
+For example, this initiates an asynchronous command:
+
+::
+
+ { "command": ["screenshot"], "request_id": 123, "async": true }
+
+And this is the completion:
+
+::
+
+ {"request_id":123,"error":"success","data":null}
+
+By design, you will not get a confirmation that the command was started. If a
+command is long running, sending the message will lead to any reply until much
+later when the command finishes.
+
+Some commands execute synchronously, but these will behave like asynchronous
+commands that finished execution immediately.
+
+Cancellation of asynchronous commands is available in the libmpv API, but has
+not yet been implemented in the IPC protocol.
+
Commands
--------
diff --git a/input/ipc.c b/input/ipc.c
index 6568feacfa..fd971cf315 100644
--- a/input/ipc.c
+++ b/input/ipc.c
@@ -109,8 +109,28 @@ static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *
mpv_node_map_add(ta_parent, src, key, &val_node);
}
+// This is supposed to write a reply that looks like "normal" command execution.
+static void mpv_format_command_reply(void *ta_parent, mpv_event *event,
+ mpv_node *dst)
+{
+ assert(event->event_id == MPV_EVENT_COMMAND_REPLY);
+ mpv_event_command *cmd = event->data;
+
+ mpv_node_map_add_int64(ta_parent, dst, "request_id", event->reply_userdata);
+
+ mpv_node_map_add_string(ta_parent, dst, "error",
+ mpv_error_string(event->error));
+
+ mpv_node_map_add(ta_parent, dst, "data", &cmd->result);
+}
+
static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
{
+ if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
+ mpv_format_command_reply(ta_parent, event, dst);
+ return;
+ }
+
mpv_node_map_add_string(ta_parent, dst, "event", mpv_event_name(event->event_id));
if (event->reply_userdata)
@@ -193,6 +213,10 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
mpv_node msg_node;
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
mpv_node *reqid_node = NULL;
+ int64_t reqid = 0;
+ mpv_node *async_node = NULL;
+ bool async = false;
+ bool send_reply = true;
rc = json_parse(ta_parent, &msg_node, &src, 50);
if (rc < 0) {
@@ -206,10 +230,27 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
goto error;
}
+ async_node = node_map_get(&msg_node, "async");
+ if (async_node) {
+ if (async_node->format != MPV_FORMAT_FLAG) {
+ rc = MPV_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ async = async_node->u.flag;
+ }
+
reqid_node = node_map_get(&msg_node, "request_id");
- if (reqid_node && reqid_node->format != MPV_FORMAT_INT64) {
- mp_warn(log, "'request_id' must be an integer. Using other types is "
- "deprecated and will trigger an error in the future!\n");
+ if (reqid_node) {
+ if (reqid_node->format == MPV_FORMAT_INT64) {
+ reqid = reqid_node->u.int64;
+ } else if (async) {
+ mp_err(log, "'request_id' must be an integer for async commands.\n");
+ rc = MPV_ERROR_INVALID_PARAMETER;
+ goto error;
+ } else {
+ mp_warn(log, "'request_id' must be an integer. Using other types is "
+ "deprecated and will trigger an error in the future!\n");
+ }
}
mpv_node *cmd_node = node_map_get(&msg_node, "command");
@@ -396,9 +437,15 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
} else {
mpv_node result_node;
- rc = mpv_command_node(client, cmd_node, &result_node);
- if (rc >= 0)
- mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
+ if (async) {
+ rc = mpv_command_node_async(client, reqid, cmd_node);
+ if (rc >= 0)
+ send_reply = false;
+ } else {
+ rc = mpv_command_node(client, cmd_node, &result_node);
+ if (rc >= 0)
+ mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
+ }
}
error:
@@ -415,8 +462,11 @@ error:
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
char *output = talloc_strdup(ta_parent, "");
- json_write(&output, &reply_node);
- output = ta_talloc_strdup_append(output, "\n");
+
+ if (send_reply) {
+ json_write(&output, &reply_node);
+ output = ta_talloc_strdup_append(output, "\n");
+ }
return output;
}