libqmi-glib,qmi-codegen: timed out operations will issue an ABORT message

Messages can now be tagged with a special 'abort' keyword, so that whenever the
message times out we issue a new ABORT command to cancel the specific timed out
request.

This support is currently only available for the NAS and WDS services, which are
the ones supporting ABORT for their long-running operations.
This commit is contained in:
Aleksander Morgado 2012-10-10 19:49:21 +02:00
parent 5e1507472d
commit 13b53c6a1d
7 changed files with 179 additions and 41 deletions

View File

@ -261,14 +261,20 @@ class Client:
Emits the async methods for each known request/response
"""
def __emit_methods(self, hfile, cfile, message_list):
translations = { 'underscore' : utils.build_underscore_name(self.name),
'camelcase' : utils.build_camelcase_name (self.name) }
translations = { 'underscore' : utils.build_underscore_name(self.name),
'camelcase' : utils.build_camelcase_name (self.name),
'service_lowercase' : string.lower(self.service),
'service_uppercase' : string.upper(self.service),
'service_camelcase' : string.capwords(self.service) }
for message in message_list.list:
if message.type == 'Indication':
continue
if message.static:
continue
translations['message_name'] = message.name
translations['message_underscore'] = utils.build_underscore_name(message.name)
translations['message_fullname_underscore'] = utils.build_underscore_name(message.fullname)
@ -306,11 +312,11 @@ class Client:
' * ${underscore}_${message_underscore}_finish:\n'
' * @self: a #${camelcase}.\n'
' * @res: the #GAsyncResult obtained from the #GAsyncReadyCallback passed to ${underscore}_${message_underscore}().\n'
' * @error: Return location for error or %%NULL.\n'
' * @error: Return location for error or %NULL.\n'
' *\n'
' * Finishes an async operation started with ${underscore}_${message_underscore}().\n'
' *\n'
' * Returns: a #${output_camelcase}, or %%NULL if @error is set. The returned value should be freed with ${output_underscore}_unref().\n'
' * Returns: a #${output_camelcase}, or %NULL if @error is set. The returned value should be freed with ${output_underscore}_unref().\n'
' */\n'
'${output_camelcase} *\n'
'${underscore}_${message_underscore}_finish (\n'
@ -322,7 +328,35 @@ class Client:
' return NULL;\n'
'\n'
' return ${output_underscore}_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));\n'
'}\n'
'}\n')
if message.abort:
template += (
'\n'
'static void\n'
'${message_underscore}_abort_ready (\n'
' QmiDevice *device,\n'
' GAsyncResult *res)\n'
'{\n'
' GError *error = NULL;\n'
' QmiMessage *reply;\n'
' QmiMessage${service_camelcase}AbortOutput *output;\n'
'\n'
' reply = qmi_device_command_finish (device, res, &error);\n'
' if (reply) {\n'
' output = __qmi_message_${service_lowercase}_abort_response_parse (reply, &error);\n'
' if (output)\n'
' qmi_message_${service_lowercase}_abort_output_unref (output);\n'
' qmi_message_unref (reply);\n'
' }\n'
'\n'
' if (error) {\n'
' g_debug ("Operation to abort \'${message_name}\' failed: %s", error->message);\n'
' g_error_free (error);\n'
' }\n'
'}\n')
template += (
'\n'
'static void\n'
'${message_underscore}_ready (\n'
@ -335,7 +369,47 @@ class Client:
' ${output_camelcase} *output;\n'
'\n'
' reply = qmi_device_command_finish (device, res, &error);\n'
' if (!reply) {\n'
' if (!reply) {\n')
if message.abort:
template += (
' if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TIMEOUT)) {\n'
' QmiMessage *abort;\n'
' GObject *self;\n'
' guint16 transaction_id;\n'
' QmiMessage${service_camelcase}AbortInput *input;\n'
'\n'
' self = g_async_result_get_source_object (G_ASYNC_RESULT (simple));\n'
' g_assert (self != NULL);\n'
'\n'
' transaction_id = (guint16) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (simple),\n'
' "transaction-id"));\n'
' g_assert (transaction_id != 0);\n'
'\n'
' input = qmi_message_${service_lowercase}_abort_input_new ();\n'
' qmi_message_${service_lowercase}_abort_input_set_transaction_id (\n'
' input,\n'
' transaction_id,\n'
' NULL);\n'
' abort = __qmi_message_${service_lowercase}_abort_request_create (\n'
' qmi_client_get_next_transaction_id (QMI_CLIENT (self)),\n'
' qmi_client_get_cid (QMI_CLIENT (self)),\n'
' input,\n'
' NULL);\n'
' g_assert (abort != NULL);\n'
' qmi_device_command (device,\n'
' abort,\n'
' 30,\n'
' NULL,\n'
' (GAsyncReadyCallback)${message_underscore}_abort_ready,\n'
' NULL);\n'
' qmi_message_${service_lowercase}_abort_input_unref (input);\n'
' qmi_message_unref (abort);\n'
' g_object_unref (self);\n'
' }\n'
'\n')
template += (
' g_simple_async_result_take_error (simple, error);\n'
' g_simple_async_result_complete (simple);\n'
' g_object_unref (simple);\n'
@ -360,7 +434,7 @@ class Client:
' * @self: a #${camelcase}.\n'
' * @${input_doc}\n'
' * @timeout: maximum time to wait for the method to complete, in seconds.\n'
' * @cancellable: a #GCancellable or %%NULL.\n'
' * @cancellable: a #GCancellable or %NULL.\n'
' * @callback: a #GAsyncReadyCallback to call when the request is satisfied.\n'
' * @user_data: user data to pass to @callback.\n'
' *\n'
@ -372,8 +446,10 @@ class Client:
' */\n'
'void\n'
'${underscore}_${message_underscore} (\n'
' ${camelcase} *self,\n'
' %s,\n'
' ${camelcase} *self,\n')
template += (
' %s,\n' % input_arg_template)
template += (
' guint timeout,\n'
' GCancellable *cancellable,\n'
' GAsyncReadyCallback callback,\n'
@ -382,14 +458,17 @@ class Client:
' GSimpleAsyncResult *result;\n'
' QmiMessage *request;\n'
' GError *error = NULL;\n'
' guint16 transaction_id;\n'
'\n'
' result = g_simple_async_result_new (G_OBJECT (self),\n'
' callback,\n'
' user_data,\n'
' ${underscore}_${message_underscore});\n'
'\n'
' transaction_id = qmi_client_get_next_transaction_id (QMI_CLIENT (self));\n'
'\n'
' request = __${message_fullname_underscore}_request_create (\n'
' qmi_client_get_next_transaction_id (QMI_CLIENT (self)),\n'
' transaction_id,\n'
' qmi_client_get_cid (QMI_CLIENT (self)),\n'
' ${input_var},\n'
' &error);\n'
@ -399,7 +478,16 @@ class Client:
' g_simple_async_result_complete_in_idle (result);\n'
' g_object_unref (result);\n'
' return;\n'
' }\n'
' }\n')
if message.abort:
template += (
'\n'
' g_object_set_data (G_OBJECT (result),\n'
' "transaction-id",\n'
' GUINT_TO_POINTER (transaction_id));\n')
template += (
'\n'
' qmi_device_command (QMI_DEVICE (qmi_client_peek_device (QMI_CLIENT (self))),\n'
' request,\n'
@ -409,7 +497,7 @@ class Client:
' result);\n'
' qmi_message_unref (request);\n'
'}\n'
'\n' % input_arg_template)
'\n')
cfile.write(string.Template(template).substitute(translations))

View File

@ -33,7 +33,7 @@ class Container:
"""
Constructor
"""
def __init__(self, prefix, container_type, dictionary, common_objects_dictionary):
def __init__(self, prefix, container_type, dictionary, common_objects_dictionary, static):
# The field container prefix usually contains the name of the Message,
# e.g. "Qmi Message Ctl Something"
self.prefix = prefix
@ -48,6 +48,8 @@ class Container:
self.name = container_type
self.static = static
# Create the composed full name (prefix + name),
# e.g. "Qmi Message Ctl Something Output"
self.fullname = self.prefix + ' ' + self.name
@ -88,9 +90,9 @@ class Container:
if field_dictionary['type'] == 'TLV':
if field_dictionary['format'] == 'struct' and \
field_dictionary['name'] == 'Result':
self.fields.append(FieldResult(self.fullname, field_dictionary, common_objects_dictionary, container_type))
self.fields.append(FieldResult(self.fullname, field_dictionary, common_objects_dictionary, container_type, static))
else:
self.fields.append(Field(self.fullname, field_dictionary, common_objects_dictionary, container_type))
self.fields.append(Field(self.fullname, field_dictionary, common_objects_dictionary, container_type, static))
"""
@ -124,7 +126,7 @@ class Container:
' * using the provided API.\n'
' */\n'
'typedef struct _${camelcase} ${camelcase};\n'
'GType ${underscore}_get_type (void) G_GNUC_CONST;\n'
'${static}GType ${underscore}_get_type (void) G_GNUC_CONST;\n'
'#define ${type_macro} (${underscore}_get_type ())\n')
hfile.write(string.Template(template).substitute(translations))
@ -159,17 +161,21 @@ class Container:
# Emit container core header
template = (
'\n'
'${camelcase} *${underscore}_ref (${camelcase} *self);\n'
'void ${underscore}_unref (${camelcase} *self);\n')
'${static}${camelcase} *${underscore}_ref (${camelcase} *self);\n'
'${static}void ${underscore}_unref (${camelcase} *self);\n')
if self.readonly == False:
template += (
'${camelcase} *${underscore}_new (void);\n')
hfile.write(string.Template(template).substitute(translations))
'${static}${camelcase} *${underscore}_new (void);\n')
if self.static:
cfile.write(string.Template(template).substitute(translations))
else:
hfile.write(string.Template(template).substitute(translations))
# Emit container core source
template = (
'\n'
'GType\n'
'${static}GType\n'
'${underscore}_get_type (void)\n'
'{\n'
' static volatile gsize g_define_type_id__volatile = 0;\n'
@ -194,7 +200,7 @@ class Container:
' *\n'
' * Returns: the new reference to @self.\n'
' */\n'
'${camelcase} *\n'
'${static}${camelcase} *\n'
'${underscore}_ref (${camelcase} *self)\n'
'{\n'
' g_return_val_if_fail (self != NULL, NULL);\n'
@ -210,7 +216,7 @@ class Container:
' * Atomically decrements the reference count of @self by one.\n'
' * If the reference count drops to 0, @self is completely disposed.\n'
' */\n'
'void\n'
'${static}void\n'
'${underscore}_unref (${camelcase} *self)\n'
'{\n'
' g_return_if_fail (self != NULL);\n'
@ -241,7 +247,7 @@ class Container:
' *\n'
' * Returns: the newly created #${camelcase}. The returned value should be freed with ${underscore}_unref().\n'
' */\n'
'${camelcase} *\n'
'${static}${camelcase} *\n'
'${underscore}_new (void)\n'
'{\n'
' ${camelcase} *self;\n'
@ -259,12 +265,15 @@ class Container:
def emit(self, hfile, cfile):
translations = { 'name' : self.name,
'camelcase' : utils.build_camelcase_name (self.fullname),
'underscore' : utils.build_underscore_name (self.fullname) }
'underscore' : utils.build_underscore_name (self.fullname),
'static' : 'static ' if self.static else '' }
auxfile = cfile if self.static else hfile
if self.fields is None:
template = ('\n'
'/* Note: no fields in the ${name} container */\n')
hfile.write(string.Template(template).substitute(translations))
auxfile.write(string.Template(template).substitute(translations))
cfile.write(string.Template(template).substitute(translations))
return
@ -272,8 +281,8 @@ class Container:
# Emit field getter/setter
if self.fields is not None:
for field in self.fields:
field.emit_types(hfile, cfile)
self.__emit_types(hfile, cfile, translations)
field.emit_types(auxfile, cfile)
self.__emit_types(auxfile, cfile, translations)
# Emit TLV enums
self.__emit_tlv_ids_enum(cfile)
@ -281,12 +290,12 @@ class Container:
# Emit fields
if self.fields is not None:
for field in self.fields:
field.emit_getter(hfile, cfile)
field.emit_getter(auxfile, cfile)
if self.readonly == False:
field.emit_setter(hfile, cfile)
field.emit_setter(auxfile, cfile)
# Emit the container core
self.__emit_core(hfile, cfile, translations)
self.__emit_core(auxfile, cfile, translations)
"""

View File

@ -32,7 +32,7 @@ class Field:
"""
Constructor
"""
def __init__(self, prefix, dictionary, common_objects_dictionary, container_type):
def __init__(self, prefix, dictionary, common_objects_dictionary, container_type, static):
# The field prefix, usually the name of the Container,
# e.g. "Qmi Message Ctl Something Output"
self.prefix = prefix
@ -46,6 +46,8 @@ class Field:
self.type = dictionary['type']
# The container type, which must be either "Input" or "Output"
self.container_type = container_type
# Whether the whole field is internally used only
self.static = static
# Create the composed full name (prefix + name),
# e.g. "Qmi Message Ctl Something Output Result"
@ -105,12 +107,13 @@ class Field:
'variable_getter_imp' : variable_getter_imp,
'underscore' : utils.build_underscore_name(self.name),
'prefix_camelcase' : utils.build_camelcase_name(self.prefix),
'prefix_underscore' : utils.build_underscore_name(self.prefix) }
'prefix_underscore' : utils.build_underscore_name(self.prefix),
'static' : 'static ' if self.static else '' }
# Emit the getter header
template = (
'\n'
'gboolean ${prefix_underscore}_get_${underscore} (\n'
'${static}gboolean ${prefix_underscore}_get_${underscore} (\n'
' ${prefix_camelcase} *self,\n'
'${variable_getter_dec}'
' GError **error);\n')
@ -129,7 +132,7 @@ class Field:
' *\n'
' * Returns: %TRUE if the field is found, %FALSE otherwise.\n'
' */\n'
'gboolean\n'
'${static}gboolean\n'
'${prefix_underscore}_get_${underscore} (\n'
' ${prefix_camelcase} *self,\n'
'${variable_getter_dec}'
@ -168,12 +171,13 @@ class Field:
'variable_setter_imp' : variable_setter_imp,
'underscore' : utils.build_underscore_name(self.name),
'prefix_camelcase' : utils.build_camelcase_name(self.prefix),
'prefix_underscore' : utils.build_underscore_name(self.prefix) }
'prefix_underscore' : utils.build_underscore_name(self.prefix),
'static' : 'static ' if self.static else '' }
# Emit the setter header
template = (
'\n'
'gboolean ${prefix_underscore}_set_${underscore} (\n'
'${static}gboolean ${prefix_underscore}_set_${underscore} (\n'
' ${prefix_camelcase} *self,\n'
'${variable_setter_dec}'
' GError **error);\n')
@ -192,7 +196,7 @@ class Field:
' *\n'
' * Returns: %TRUE if @value was successfully set, %FALSE otherwise.\n'
' */\n'
'gboolean\n'
'${static}gboolean\n'
'${prefix_underscore}_set_${underscore} (\n'
' ${prefix_camelcase} *self,\n'
'${variable_setter_dec}'

View File

@ -42,6 +42,8 @@ class Message:
self.type = dictionary['type']
# The version info, optional
self.version_info = dictionary['version'].split('.') if 'version' in dictionary else []
self.static = True if 'scope' in dictionary and dictionary['scope'] == 'library-only' else False
self.abort = True if 'abort' in dictionary and dictionary['abort'] == 'yes' else False
# The message prefix
self.prefix = 'Qmi ' + self.type
@ -61,7 +63,8 @@ class Message:
self.output = Container(self.fullname,
'Output',
dictionary['output'] if 'output' in dictionary else None,
common_objects_dictionary)
common_objects_dictionary,
self.static)
self.input = None
if self.type == 'Message':
@ -72,7 +75,8 @@ class Message:
self.input = Container(self.fullname,
'Input',
dictionary['input'] if 'input' in dictionary else None,
common_objects_dictionary)
common_objects_dictionary,
self.static)
"""
@ -400,6 +404,8 @@ class Message:
Emit the sections
"""
def emit_sections(self, sfile):
if self.static:
return
translations = { 'hyphened' : utils.build_dashed_name (self.fullname),
'fullname_underscore' : utils.build_underscore_name(self.fullname),

View File

@ -24,6 +24,21 @@
"version" : "1.0",
"output" : [ { "common-ref" : "Operation Result" } ] },
// *********************************************************************************
{ "name" : "Abort",
"type" : "Message",
"service" : "NAS",
"id" : "0x0001",
"version" : "1.0",
// This magic tag allows us to avoid creating a method in the client
"scope" : "library-only",
"input" : [ { "name" : "Transaction ID",
"id" : "0x01",
"mandatory" : "yes",
"type" : "TLV",
"format" : "guint16" } ],
"output" : [ { "common-ref" : "Operation Result" } ] },
// *********************************************************************************
{ "name" : "Set Event Report",
"type" : "Message",

View File

@ -20,6 +20,21 @@
"version" : "1.0",
"output" : [ { "common-ref" : "Operation Result" } ] },
// *********************************************************************************
{ "name" : "Abort",
"type" : "Message",
"service" : "WDS",
"id" : "0x0002",
"version" : "1.0",
// This magic tag allows us to avoid creating a method in the client
"scope" : "library-only",
"input" : [ { "name" : "Transaction ID",
"id" : "0x01",
"mandatory" : "yes",
"type" : "TLV",
"format" : "guint16" } ],
"output" : [ { "common-ref" : "Operation Result" } ] },
// *********************************************************************************
{ "name" : "Start Network",
"type" : "Message",

View File

@ -176,7 +176,8 @@ libqmi_glib_generated_la_CPPFLAGS = \
$(LIBQMI_GLIB_CFLAGS) \
-I$(top_srcdir) \
-I$(top_srcdir)/libqmi-glib \
-DLIBQMI_GLIB_COMPILATION
-DLIBQMI_GLIB_COMPILATION \
-Wno-unused-function
libqmi_glib_generated_la_LIBADD = \
$(LIBQMI_GLIB_LIBS)