Rework device removal to have a nice API
This enhances the device removal to create a well defined behaviour. Primarily, it means that: * "device-removed" will only be called for closed devices * "removed" will be called only when no operation is active Note that all actions will fail with FP_DEVICE_ERROR_REMOVED, *except* for open which will only return this error if it failed. Resolves: #330
This commit is contained in:
parent
656bf3d175
commit
b6dd522459
6 changed files with 205 additions and 9 deletions
|
@ -86,6 +86,60 @@ is_driver_allowed (const gchar *driver)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
FpContext *context;
|
||||||
|
FpDevice *device;
|
||||||
|
} RemoveDeviceData;
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_device_idle_cb (RemoveDeviceData *data)
|
||||||
|
{
|
||||||
|
FpContextPrivate *priv = fp_context_get_instance_private (data->context);
|
||||||
|
guint idx = 0;
|
||||||
|
|
||||||
|
g_return_val_if_fail (g_ptr_array_find (priv->devices, data->device, &idx), G_SOURCE_REMOVE);
|
||||||
|
|
||||||
|
g_signal_emit (data->context, signals[DEVICE_REMOVED_SIGNAL], 0, data->device);
|
||||||
|
g_ptr_array_remove_index_fast (priv->devices, idx);
|
||||||
|
|
||||||
|
g_free (data);
|
||||||
|
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
remove_device (FpContext *context, FpDevice *device)
|
||||||
|
{
|
||||||
|
RemoveDeviceData *data;
|
||||||
|
|
||||||
|
data = g_new (RemoveDeviceData, 1);
|
||||||
|
data->context = context;
|
||||||
|
data->device = device;
|
||||||
|
|
||||||
|
g_idle_add ((GSourceFunc) remove_device_idle_cb, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
device_remove_on_notify_open_cb (FpContext *context, GParamSpec *pspec, FpDevice *device)
|
||||||
|
{
|
||||||
|
remove_device (context, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
device_removed_cb (FpContext *context, FpDevice *device)
|
||||||
|
{
|
||||||
|
gboolean open = FALSE;
|
||||||
|
|
||||||
|
g_object_get (device, "open", &open, NULL);
|
||||||
|
|
||||||
|
/* Wait for device close if the device is currently still open. */
|
||||||
|
if (open)
|
||||||
|
g_signal_connect_swapped (device, "notify::open", (GCallback) device_remove_on_notify_open_cb, context);
|
||||||
|
else
|
||||||
|
remove_device (context, device);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
|
async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
|
||||||
{
|
{
|
||||||
|
@ -110,6 +164,9 @@ async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer u
|
||||||
}
|
}
|
||||||
|
|
||||||
g_ptr_array_add (priv->devices, device);
|
g_ptr_array_add (priv->devices, device);
|
||||||
|
|
||||||
|
g_signal_connect_swapped (device, "removed", (GCallback) device_removed_cb, context);
|
||||||
|
|
||||||
g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
|
g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,12 +246,7 @@ usb_device_removed_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (fpi_device_get_usb_device (dev) == device)
|
if (fpi_device_get_usb_device (dev) == device)
|
||||||
{
|
fpi_device_remove (dev);
|
||||||
g_signal_emit (self, signals[DEVICE_REMOVED_SIGNAL], 0, dev);
|
|
||||||
g_ptr_array_remove_index_fast (priv->devices, i);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +300,10 @@ fp_context_class_init (FpContextClass *klass)
|
||||||
* @device: A #FpDevice
|
* @device: A #FpDevice
|
||||||
*
|
*
|
||||||
* This signal is emitted when a fingerprint reader is removed.
|
* This signal is emitted when a fingerprint reader is removed.
|
||||||
|
*
|
||||||
|
* It is guaranteed that the device has been closed before this signal
|
||||||
|
* is emitted. See the #FpDevice removed signal documentation for more
|
||||||
|
* information.
|
||||||
**/
|
**/
|
||||||
signals[DEVICE_REMOVED_SIGNAL] = g_signal_new ("device-removed",
|
signals[DEVICE_REMOVED_SIGNAL] = g_signal_new ("device-removed",
|
||||||
G_TYPE_FROM_CLASS (klass),
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
|
|
@ -29,6 +29,7 @@ typedef struct
|
||||||
GUsbDevice *usb_device;
|
GUsbDevice *usb_device;
|
||||||
const gchar *virtual_env;
|
const gchar *virtual_env;
|
||||||
|
|
||||||
|
gboolean is_removed;
|
||||||
gboolean is_open;
|
gboolean is_open;
|
||||||
|
|
||||||
gchar *device_id;
|
gchar *device_id;
|
||||||
|
|
|
@ -44,6 +44,7 @@ enum {
|
||||||
PROP_DEVICE_ID,
|
PROP_DEVICE_ID,
|
||||||
PROP_NAME,
|
PROP_NAME,
|
||||||
PROP_OPEN,
|
PROP_OPEN,
|
||||||
|
PROP_REMOVED,
|
||||||
PROP_NR_ENROLL_STAGES,
|
PROP_NR_ENROLL_STAGES,
|
||||||
PROP_SCAN_TYPE,
|
PROP_SCAN_TYPE,
|
||||||
PROP_FINGER_STATUS,
|
PROP_FINGER_STATUS,
|
||||||
|
@ -55,6 +56,13 @@ enum {
|
||||||
|
|
||||||
static GParamSpec *properties[N_PROPS];
|
static GParamSpec *properties[N_PROPS];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
REMOVED_SIGNAL,
|
||||||
|
N_SIGNALS
|
||||||
|
};
|
||||||
|
|
||||||
|
static guint signals[N_SIGNALS] = { 0, };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fp_device_retry_quark:
|
* fp_device_retry_quark:
|
||||||
*
|
*
|
||||||
|
@ -204,6 +212,10 @@ fp_device_get_property (GObject *object,
|
||||||
g_value_set_boolean (value, priv->is_open);
|
g_value_set_boolean (value, priv->is_open);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_REMOVED:
|
||||||
|
g_value_set_boolean (value, priv->is_removed);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
}
|
}
|
||||||
|
@ -351,6 +363,41 @@ fp_device_class_init (FpDeviceClass *klass)
|
||||||
"Whether the device is open or not", FALSE,
|
"Whether the device is open or not", FALSE,
|
||||||
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||||
|
|
||||||
|
properties[PROP_REMOVED] =
|
||||||
|
g_param_spec_boolean ("removed",
|
||||||
|
"Removed",
|
||||||
|
"Whether the device has been removed from the system", FALSE,
|
||||||
|
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FpDevice::removed:
|
||||||
|
* @device: the #FpDevice instance that emitted the signal
|
||||||
|
*
|
||||||
|
* This signal is emitted after the device has been removed and no operation
|
||||||
|
* is pending anymore.
|
||||||
|
*
|
||||||
|
* The API user is still required to close a removed device. The above
|
||||||
|
* guarantee means that the call to close the device can be made immediately
|
||||||
|
* from the signal handler.
|
||||||
|
*
|
||||||
|
* The close operation will return FP_DEVICE_ERROR_REMOVED, but the device
|
||||||
|
* will still be considered closed afterwards.
|
||||||
|
*
|
||||||
|
* The device will only be removed from the #FpContext after it has been
|
||||||
|
* closed by the API user.
|
||||||
|
**/
|
||||||
|
signals[REMOVED_SIGNAL] = g_signal_new ("removed",
|
||||||
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
g_cclosure_marshal_VOID__VOID,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
0);
|
||||||
|
|
||||||
|
/* Private properties */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FpDevice::fpi-environ: (skip)
|
* FpDevice::fpi-environ: (skip)
|
||||||
*
|
*
|
||||||
|
|
|
@ -91,6 +91,7 @@ typedef enum {
|
||||||
* @FP_DEVICE_ERROR_DATA_NOT_FOUND: Requested print was not found on device
|
* @FP_DEVICE_ERROR_DATA_NOT_FOUND: Requested print was not found on device
|
||||||
* @FP_DEVICE_ERROR_DATA_FULL: No space on device available for operation
|
* @FP_DEVICE_ERROR_DATA_FULL: No space on device available for operation
|
||||||
* @FP_DEVICE_ERROR_DATA_DUPLICATE: Enrolling template duplicates storaged templates
|
* @FP_DEVICE_ERROR_DATA_DUPLICATE: Enrolling template duplicates storaged templates
|
||||||
|
* @FP_DEVICE_ERROR_REMOVED: The device has been removed.
|
||||||
*
|
*
|
||||||
* Error codes for device operations. More specific errors from other domains
|
* Error codes for device operations. More specific errors from other domains
|
||||||
* such as #G_IO_ERROR or #G_USB_DEVICE_ERROR may also be reported.
|
* such as #G_IO_ERROR or #G_USB_DEVICE_ERROR may also be reported.
|
||||||
|
@ -106,6 +107,8 @@ typedef enum {
|
||||||
FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||||
FP_DEVICE_ERROR_DATA_FULL,
|
FP_DEVICE_ERROR_DATA_FULL,
|
||||||
FP_DEVICE_ERROR_DATA_DUPLICATE,
|
FP_DEVICE_ERROR_DATA_DUPLICATE,
|
||||||
|
/* Leave some room to add more DATA related errors */
|
||||||
|
FP_DEVICE_ERROR_REMOVED = 0x100,
|
||||||
} FpDeviceError;
|
} FpDeviceError;
|
||||||
|
|
||||||
GQuark fp_device_retry_quark (void);
|
GQuark fp_device_retry_quark (void);
|
||||||
|
|
|
@ -139,6 +139,10 @@ fpi_device_error_new (FpDeviceError error)
|
||||||
msg = "This finger has already enrolled, please try a different finger";
|
msg = "This finger has already enrolled, please try a different finger";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FP_DEVICE_ERROR_REMOVED:
|
||||||
|
msg = "This device has been removed from the system.";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
g_warning ("Unsupported error, returning general error instead!");
|
g_warning ("Unsupported error, returning general error instead!");
|
||||||
error = FP_DEVICE_ERROR_GENERAL;
|
error = FP_DEVICE_ERROR_GENERAL;
|
||||||
|
@ -576,6 +580,49 @@ fpi_device_get_cancellable (FpDevice *device)
|
||||||
return g_task_get_cancellable (priv->current_task);
|
return g_task_get_cancellable (priv->current_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
emit_removed_on_task_completed (FpDevice *device)
|
||||||
|
{
|
||||||
|
g_signal_emit_by_name (device, "removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fpi_device_remove:
|
||||||
|
* @device: The #FpDevice
|
||||||
|
*
|
||||||
|
* Called to signal to the #FpDevice that it has been unplugged (physically
|
||||||
|
* removed from the system).
|
||||||
|
*
|
||||||
|
* For USB devices, this API is called automatically by #FpContext.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fpi_device_remove (FpDevice *device)
|
||||||
|
{
|
||||||
|
FpDevicePrivate *priv = fp_device_get_instance_private (device);
|
||||||
|
|
||||||
|
g_return_if_fail (FP_IS_DEVICE (device));
|
||||||
|
g_return_if_fail (!priv->is_removed);
|
||||||
|
|
||||||
|
priv->is_removed = TRUE;
|
||||||
|
|
||||||
|
g_object_notify (G_OBJECT (device), "removed");
|
||||||
|
|
||||||
|
/* If there is a pending action, we wait for it to fail, otherwise we
|
||||||
|
* immediately emit the "removed" signal. */
|
||||||
|
if (priv->current_task)
|
||||||
|
{
|
||||||
|
g_signal_connect_object (priv->current_task,
|
||||||
|
"notify::completed",
|
||||||
|
(GCallback) emit_removed_on_task_completed,
|
||||||
|
device,
|
||||||
|
G_CONNECT_SWAPPED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_signal_emit_by_name (device, "removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fpi_device_action_error:
|
* fpi_device_action_error:
|
||||||
* @device: The #FpDevice
|
* @device: The #FpDevice
|
||||||
|
@ -691,6 +738,7 @@ fp_device_task_return_in_idle_cb (gpointer user_data)
|
||||||
FpDeviceTaskReturnData *data = user_data;
|
FpDeviceTaskReturnData *data = user_data;
|
||||||
FpDevicePrivate *priv = fp_device_get_instance_private (data->device);
|
FpDevicePrivate *priv = fp_device_get_instance_private (data->device);
|
||||||
g_autofree char *action_str = NULL;
|
g_autofree char *action_str = NULL;
|
||||||
|
FpiDeviceAction action;
|
||||||
|
|
||||||
g_autoptr(GTask) task = NULL;
|
g_autoptr(GTask) task = NULL;
|
||||||
|
|
||||||
|
@ -699,9 +747,24 @@ fp_device_task_return_in_idle_cb (gpointer user_data)
|
||||||
g_debug ("Completing action %s in idle!", action_str);
|
g_debug ("Completing action %s in idle!", action_str);
|
||||||
|
|
||||||
task = g_steal_pointer (&priv->current_task);
|
task = g_steal_pointer (&priv->current_task);
|
||||||
|
action = priv->current_action;
|
||||||
priv->current_action = FPI_DEVICE_ACTION_NONE;
|
priv->current_action = FPI_DEVICE_ACTION_NONE;
|
||||||
priv->current_task_idle_return_source = NULL;
|
priv->current_task_idle_return_source = NULL;
|
||||||
|
|
||||||
|
/* Return FP_DEVICE_ERROR_REMOVED if the device is removed,
|
||||||
|
* with the exception of a successful open, which is an odd corner case. */
|
||||||
|
if (priv->is_removed &&
|
||||||
|
((action != FPI_DEVICE_ACTION_OPEN) ||
|
||||||
|
(action == FPI_DEVICE_ACTION_OPEN && data->type == FP_DEVICE_TASK_RETURN_ERROR)))
|
||||||
|
{
|
||||||
|
g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_REMOVED));
|
||||||
|
|
||||||
|
/* NOTE: The removed signal will be emitted from the GTask
|
||||||
|
* notify::completed if that is neccessary. */
|
||||||
|
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
switch (data->type)
|
switch (data->type)
|
||||||
{
|
{
|
||||||
case FP_DEVICE_TASK_RETURN_INT:
|
case FP_DEVICE_TASK_RETURN_INT:
|
||||||
|
@ -713,16 +776,17 @@ fp_device_task_return_in_idle_cb (gpointer user_data)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FP_DEVICE_TASK_RETURN_OBJECT:
|
case FP_DEVICE_TASK_RETURN_OBJECT:
|
||||||
g_task_return_pointer (task, data->result, g_object_unref);
|
g_task_return_pointer (task, g_steal_pointer (&data->result),
|
||||||
|
g_object_unref);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
|
case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
|
||||||
g_task_return_pointer (task, data->result,
|
g_task_return_pointer (task, g_steal_pointer (&data->result),
|
||||||
(GDestroyNotify) g_ptr_array_unref);
|
(GDestroyNotify) g_ptr_array_unref);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FP_DEVICE_TASK_RETURN_ERROR:
|
case FP_DEVICE_TASK_RETURN_ERROR:
|
||||||
g_task_return_error (task, data->result);
|
g_task_return_error (task, g_steal_pointer (&data->result));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -735,6 +799,30 @@ fp_device_task_return_in_idle_cb (gpointer user_data)
|
||||||
static void
|
static void
|
||||||
fpi_device_task_return_data_free (FpDeviceTaskReturnData *data)
|
fpi_device_task_return_data_free (FpDeviceTaskReturnData *data)
|
||||||
{
|
{
|
||||||
|
if (data->result)
|
||||||
|
{
|
||||||
|
switch (data->type)
|
||||||
|
{
|
||||||
|
case FP_DEVICE_TASK_RETURN_INT:
|
||||||
|
case FP_DEVICE_TASK_RETURN_BOOL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FP_DEVICE_TASK_RETURN_OBJECT:
|
||||||
|
g_clear_object ((GObject **) &data->result);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
|
||||||
|
g_clear_pointer ((GPtrArray **) &data->result, g_ptr_array_unref);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FP_DEVICE_TASK_RETURN_ERROR:
|
||||||
|
g_clear_error ((GError **) &data->result);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
}
|
||||||
g_object_unref (data->device);
|
g_object_unref (data->device);
|
||||||
g_free (data);
|
g_free (data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,7 @@ void fpi_device_get_delete_data (FpDevice *device,
|
||||||
FpPrint **print);
|
FpPrint **print);
|
||||||
GCancellable *fpi_device_get_cancellable (FpDevice *device);
|
GCancellable *fpi_device_get_cancellable (FpDevice *device);
|
||||||
|
|
||||||
|
void fpi_device_remove (FpDevice *device);
|
||||||
|
|
||||||
GSource * fpi_device_add_timeout (FpDevice *device,
|
GSource * fpi_device_add_timeout (FpDevice *device,
|
||||||
gint interval,
|
gint interval,
|
||||||
|
|
Loading…
Reference in a new issue