mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2025-11-15 07:38:12 +00:00
Compare commits
19 Commits
benzea/ci-
...
benzea/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc3f622b2a | ||
|
|
5de49b33e6 | ||
|
|
81e198c034 | ||
|
|
c0895a858d | ||
|
|
5d5995f201 | ||
|
|
f71045b743 | ||
|
|
0274d0783b | ||
|
|
5c5a4f6907 | ||
|
|
5b6f5c9aad | ||
|
|
41e05b1133 | ||
|
|
579e01359b | ||
|
|
cc887c1a37 | ||
|
|
fdd2d6abf8 | ||
|
|
6bf29108a1 | ||
|
|
d0751ae06b | ||
|
|
a218a5efdd | ||
|
|
c6ae8e58a4 | ||
|
|
87c7894c28 | ||
|
|
e7ff4f705c |
@@ -207,6 +207,8 @@ fpi_print_set_type
|
|||||||
fpi_print_set_device_stored
|
fpi_print_set_device_stored
|
||||||
fpi_print_add_from_image
|
fpi_print_add_from_image
|
||||||
fpi_print_bz3_match
|
fpi_print_bz3_match
|
||||||
|
fpi_print_generate_user_id
|
||||||
|
fpi_print_fill_from_user_id
|
||||||
</SECTION>
|
</SECTION>
|
||||||
|
|
||||||
<SECTION>
|
<SECTION>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Example fingerprint enrollment program
|
* Example fingerprint enrollment program
|
||||||
* Enrolls your choosen finger and saves the print to disk
|
* Enrolls your chosen finger and saves the print to disk
|
||||||
* Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
|
* Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
|
||||||
* Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
|
* Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ calibrate_run_state (FpiSsm *ssm, FpDevice *dev)
|
|||||||
fp_dbg ("calibration failed");
|
fp_dbg ("calibration failed");
|
||||||
fpi_ssm_mark_failed (ssm,
|
fpi_ssm_mark_failed (ssm,
|
||||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
||||||
"Callibration failed!"));
|
"Calibration failed!"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -859,13 +859,15 @@ m_capture_state (FpiSsm *ssm, FpDevice *dev)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FpImage *img;
|
|
||||||
unsigned int img_size;
|
|
||||||
/* Remove empty parts 2 times for the 2 frames */
|
/* Remove empty parts 2 times for the 2 frames */
|
||||||
process_removefpi_end (self);
|
process_removefpi_end (self);
|
||||||
process_removefpi_end (self);
|
process_removefpi_end (self);
|
||||||
img_size = self->fp_height * FE_WIDTH;
|
|
||||||
img = fp_image_new (FE_WIDTH, self->fp_height);
|
if (self->fp_height >= FE_WIDTH)
|
||||||
|
{
|
||||||
|
FpImage *img = fp_image_new (FE_WIDTH, self->fp_height);
|
||||||
|
unsigned int img_size = self->fp_height * FE_WIDTH;
|
||||||
|
|
||||||
/* Images received are white on black, so invert it. */
|
/* Images received are white on black, so invert it. */
|
||||||
/* TODO detect sweep direction */
|
/* TODO detect sweep direction */
|
||||||
img->flags = FPI_IMAGE_COLORS_INVERTED | FPI_IMAGE_V_FLIPPED;
|
img->flags = FPI_IMAGE_COLORS_INVERTED | FPI_IMAGE_V_FLIPPED;
|
||||||
@@ -874,6 +876,12 @@ m_capture_state (FpiSsm *ssm, FpDevice *dev)
|
|||||||
fp_dbg ("Sending the raw fingerprint image (%dx%d)",
|
fp_dbg ("Sending the raw fingerprint image (%dx%d)",
|
||||||
img->width, img->height);
|
img->width, img->height);
|
||||||
fpi_image_device_image_captured (idev, img);
|
fpi_image_device_image_captured (idev, img);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fpi_image_device_retry_scan (idev, FP_DEVICE_RETRY_TOO_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
fpi_image_device_report_finger_status (idev, FALSE);
|
fpi_image_device_report_finger_status (idev, FALSE);
|
||||||
fpi_ssm_mark_completed (ssm);
|
fpi_ssm_mark_completed (ssm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ typedef struct bmkt_init_resp
|
|||||||
*/
|
*/
|
||||||
typedef struct bmkt_enroll_resp
|
typedef struct bmkt_enroll_resp
|
||||||
{
|
{
|
||||||
int progress; /**< Shows current progress stutus [0-100] */
|
int progress; /**< Shows current progress status [0-100] */
|
||||||
uint8_t finger_id; /**< User's finger id [1-10] */
|
uint8_t finger_id; /**< User's finger id [1-10] */
|
||||||
uint8_t user_id[BMKT_MAX_USER_ID_LEN]; /**< User name to be enrolled */
|
uint8_t user_id[BMKT_MAX_USER_ID_LEN]; /**< User name to be enrolled */
|
||||||
} bmkt_enroll_resp_t;
|
} bmkt_enroll_resp_t;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ static const FpIdEntry id_table[] = {
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_recieve_cb (FpiUsbTransfer *transfer,
|
cmd_receive_cb (FpiUsbTransfer *transfer,
|
||||||
FpDevice *device,
|
FpDevice *device,
|
||||||
gpointer user_data,
|
gpointer user_data,
|
||||||
GError *error)
|
GError *error)
|
||||||
@@ -234,7 +234,7 @@ synaptics_cmd_run_state (FpiSsm *ssm,
|
|||||||
fpi_usb_transfer_submit (transfer,
|
fpi_usb_transfer_submit (transfer,
|
||||||
5000,
|
5000,
|
||||||
NULL,
|
NULL,
|
||||||
cmd_recieve_cb,
|
cmd_receive_cb,
|
||||||
fpi_ssm_get_data (ssm));
|
fpi_ssm_get_data (ssm));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -321,7 +321,7 @@ synaptics_sensor_cmd (FpiDeviceSynaptics *self,
|
|||||||
g_assert (payload || payload_len == 0);
|
g_assert (payload || payload_len == 0);
|
||||||
|
|
||||||
/* seq_num of 0 means a normal command, -1 means the current commands
|
/* seq_num of 0 means a normal command, -1 means the current commands
|
||||||
* sequence number should not be udpated (i.e. second async command which
|
* sequence number should not be updated (i.e. second async command which
|
||||||
* may only be a cancellation currently). */
|
* may only be a cancellation currently). */
|
||||||
if (seq_num <= 0)
|
if (seq_num <= 0)
|
||||||
{
|
{
|
||||||
@@ -515,39 +515,7 @@ list_msg_cb (FpiDeviceSynaptics *self,
|
|||||||
g_object_set (print, "fpi-data", data, NULL);
|
g_object_set (print, "fpi-data", data, NULL);
|
||||||
g_object_set (print, "description", get_enroll_templates_resp->templates[n].user_id, NULL);
|
g_object_set (print, "description", get_enroll_templates_resp->templates[n].user_id, NULL);
|
||||||
|
|
||||||
/* The format has 24 bytes at the start and some dashes in the right places */
|
fpi_print_fill_from_user_id (print, userid);
|
||||||
if (g_str_has_prefix (userid, "FP1-") && strlen (userid) >= 24 &&
|
|
||||||
userid[12] == '-' && userid[14] == '-' && userid[23] == '-')
|
|
||||||
{
|
|
||||||
g_autofree gchar *copy = g_strdup (userid);
|
|
||||||
g_autoptr(GDate) date = NULL;
|
|
||||||
gint32 date_ymd;
|
|
||||||
gint32 finger;
|
|
||||||
gchar *username;
|
|
||||||
/* Try to parse information from the string. */
|
|
||||||
|
|
||||||
copy[12] = '\0';
|
|
||||||
date_ymd = g_ascii_strtod (copy + 4, NULL);
|
|
||||||
if (date_ymd > 0)
|
|
||||||
date = g_date_new_dmy (date_ymd % 100,
|
|
||||||
(date_ymd / 100) % 100,
|
|
||||||
date_ymd / 10000);
|
|
||||||
else
|
|
||||||
date = g_date_new ();
|
|
||||||
|
|
||||||
fp_print_set_enroll_date (print, date);
|
|
||||||
|
|
||||||
copy[14] = '\0';
|
|
||||||
finger = g_ascii_strtoll (copy + 13, NULL, 16);
|
|
||||||
fp_print_set_finger (print, finger);
|
|
||||||
|
|
||||||
/* We ignore the next chunk, it is just random data.
|
|
||||||
* Then comes the username; nobody is the default if the metadata
|
|
||||||
* is unknown */
|
|
||||||
username = copy + 24;
|
|
||||||
if (strlen (username) > 0 && g_strcmp0 (username, "nobody") != 0)
|
|
||||||
fp_print_set_username (print, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_ptr_array_add (self->list_result, g_object_ref_sink (print));
|
g_ptr_array_add (self->list_result, g_object_ref_sink (print));
|
||||||
}
|
}
|
||||||
@@ -635,7 +603,7 @@ verify_msg_cb (FpiDeviceSynaptics *self,
|
|||||||
else if (resp->result == BMKT_FP_NO_MATCH)
|
else if (resp->result == BMKT_FP_NO_MATCH)
|
||||||
{
|
{
|
||||||
fp_info ("Print didn't match");
|
fp_info ("Print didn't match");
|
||||||
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error);
|
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL);
|
||||||
verify_complete_after_finger_removal (self);
|
verify_complete_after_finger_removal (self);
|
||||||
}
|
}
|
||||||
else if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS)
|
else if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS)
|
||||||
@@ -730,7 +698,7 @@ enroll_msg_cb (FpiDeviceSynaptics *self,
|
|||||||
if (enroll_resp->progress < 100)
|
if (enroll_resp->progress < 100)
|
||||||
done_stages = MIN (done_stages, ENROLL_SAMPLES - 1);
|
done_stages = MIN (done_stages, ENROLL_SAMPLES - 1);
|
||||||
|
|
||||||
/* Emit a retry error if there has been no discernable
|
/* Emit a retry error if there has been no discernible
|
||||||
* progress. Some firmware revisions report more required
|
* progress. Some firmware revisions report more required
|
||||||
* touches. */
|
* touches. */
|
||||||
if (self->enroll_stage == done_stages)
|
if (self->enroll_stage == done_stages)
|
||||||
@@ -795,8 +763,6 @@ enroll_msg_cb (FpiDeviceSynaptics *self,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TEMPLATE_ID_SIZE 20
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
enroll (FpDevice *device)
|
enroll (FpDevice *device)
|
||||||
{
|
{
|
||||||
@@ -804,52 +770,21 @@ enroll (FpDevice *device)
|
|||||||
FpPrint *print = NULL;
|
FpPrint *print = NULL;
|
||||||
GVariant *data = NULL;
|
GVariant *data = NULL;
|
||||||
GVariant *uid = NULL;
|
GVariant *uid = NULL;
|
||||||
const gchar *username;
|
|
||||||
guint finger;
|
guint finger;
|
||||||
g_autofree gchar *user_id = NULL;
|
g_autofree gchar *user_id = NULL;
|
||||||
gssize user_id_len;
|
gssize user_id_len;
|
||||||
g_autofree guint8 *payload = NULL;
|
g_autofree guint8 *payload = NULL;
|
||||||
const GDate *date;
|
|
||||||
gint y, m, d;
|
|
||||||
gint32 rand_id = 0;
|
|
||||||
|
|
||||||
fpi_device_get_enroll_data (device, &print);
|
fpi_device_get_enroll_data (device, &print);
|
||||||
|
|
||||||
G_DEBUG_HERE ();
|
G_DEBUG_HERE ();
|
||||||
|
|
||||||
date = fp_print_get_enroll_date (print);
|
user_id = fpi_print_generate_user_id (print);
|
||||||
if (date && g_date_valid (date))
|
|
||||||
{
|
|
||||||
y = g_date_get_year (date);
|
|
||||||
m = g_date_get_month (date);
|
|
||||||
d = g_date_get_day (date);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
y = 0;
|
|
||||||
m = 0;
|
|
||||||
d = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
username = fp_print_get_username (print);
|
|
||||||
if (!username)
|
|
||||||
username = "nobody";
|
|
||||||
|
|
||||||
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
|
|
||||||
rand_id = 0;
|
|
||||||
else
|
|
||||||
rand_id = g_random_int ();
|
|
||||||
|
|
||||||
user_id = g_strdup_printf ("FP1-%04d%02d%02d-%X-%08X-%s",
|
|
||||||
y, m, d,
|
|
||||||
fp_print_get_finger (print),
|
|
||||||
rand_id,
|
|
||||||
username);
|
|
||||||
|
|
||||||
user_id_len = strlen (user_id);
|
user_id_len = strlen (user_id);
|
||||||
user_id_len = MIN (BMKT_MAX_USER_ID_LEN, user_id_len);
|
user_id_len = MIN (BMKT_MAX_USER_ID_LEN, user_id_len);
|
||||||
|
|
||||||
/* We currently always use finger 1 from the devices piont of view */
|
/* We currently always use finger 1 from the devices point of view */
|
||||||
finger = 1;
|
finger = 1;
|
||||||
|
|
||||||
uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
||||||
@@ -1029,7 +964,7 @@ dev_probe (FpDevice *device)
|
|||||||
|
|
||||||
if (!read_ok)
|
if (!read_ok)
|
||||||
{
|
{
|
||||||
g_warning ("Transfer in response to verison query was too short");
|
g_warning ("Transfer in response to version query was too short");
|
||||||
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
|
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
|
||||||
goto err_close;
|
goto err_close;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1119,7 +1119,6 @@ e_handle_resp02 (FpDevice *dev, unsigned char *data,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
GVariant *fp_data;
|
GVariant *fp_data;
|
||||||
print = fp_print_new (dev);
|
|
||||||
|
|
||||||
fpi_device_get_enroll_data (dev, &print);
|
fpi_device_get_enroll_data (dev, &print);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
/*
|
/*
|
||||||
* This is a virtual driver to debug the image based drivers. A small
|
* This is a virtual driver to debug the image based drivers. A small
|
||||||
* python script is provided to connect to it via a socket, allowing
|
* python script is provided to connect to it via a socket, allowing
|
||||||
* prints to be sent to this device programatically.
|
* prints to be sent to this device programmatically.
|
||||||
* Using this it is possible to test libfprint and fprintd.
|
* Using this it is possible to test libfprint and fprintd.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -94,22 +94,21 @@ async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer u
|
|||||||
FpContext *context;
|
FpContext *context;
|
||||||
FpContextPrivate *priv;
|
FpContextPrivate *priv;
|
||||||
|
|
||||||
device = (FpDevice *) g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, &error);
|
device = FP_DEVICE (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
|
||||||
if (!device)
|
res, &error));
|
||||||
{
|
|
||||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
context = FP_CONTEXT (user_data);
|
context = FP_CONTEXT (user_data);
|
||||||
priv = fp_context_get_instance_private (context);
|
priv = fp_context_get_instance_private (context);
|
||||||
priv->pending_devices--;
|
priv->pending_devices--;
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
g_message ("Ignoring device due to initialization error: %s", error->message);
|
g_message ("Ignoring device due to initialization error: %s", error->message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context = FP_CONTEXT (user_data);
|
|
||||||
priv = fp_context_get_instance_private (context);
|
|
||||||
priv->pending_devices--;
|
|
||||||
g_ptr_array_add (priv->devices, device);
|
g_ptr_array_add (priv->devices, device);
|
||||||
g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
|
g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
* @title: Internal FpDevice
|
* @title: Internal FpDevice
|
||||||
* @short_description: Internal device routines
|
* @short_description: Internal device routines
|
||||||
*
|
*
|
||||||
* The methods that are availabe for drivers to manipulate a device. See
|
* The methods that are available for drivers to manipulate a device. See
|
||||||
* #FpDeviceClass for more information. Also note that most of these are
|
* #FpDeviceClass for more information. Also note that most of these are
|
||||||
* not relevant for image based devices, see #FpImageDeviceClass in that
|
* not relevant for image based devices, see #FpImageDeviceClass in that
|
||||||
* case.
|
* case.
|
||||||
@@ -339,7 +339,7 @@ fp_device_class_init (FpDeviceClass *klass)
|
|||||||
properties[PROP_OPEN] =
|
properties[PROP_OPEN] =
|
||||||
g_param_spec_boolean ("open",
|
g_param_spec_boolean ("open",
|
||||||
"Opened",
|
"Opened",
|
||||||
"Wether 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ typedef enum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FpDeviceError:
|
* FpDeviceError:
|
||||||
* @FP_DEVICE_ERROR_GENERAL: A general error occured.
|
* @FP_DEVICE_ERROR_GENERAL: A general error occurred.
|
||||||
* @FP_DEVICE_ERROR_NOT_SUPPORTED: The device does not support the requested
|
* @FP_DEVICE_ERROR_NOT_SUPPORTED: The device does not support the requested
|
||||||
* operation.
|
* operation.
|
||||||
* @FP_DEVICE_ERROR_NOT_OPEN: The device needs to be opened to start this
|
* @FP_DEVICE_ERROR_NOT_OPEN: The device needs to be opened to start this
|
||||||
@@ -113,7 +113,7 @@ GQuark fp_device_error_quark (void);
|
|||||||
* FpEnrollProgress:
|
* FpEnrollProgress:
|
||||||
* @device: a #FpDevice
|
* @device: a #FpDevice
|
||||||
* @completed_stages: Number of completed stages
|
* @completed_stages: Number of completed stages
|
||||||
* @print: (nullable) (transfer none): The last scaned print
|
* @print: (nullable) (transfer none): The last scanned print
|
||||||
* @user_data: (nullable) (transfer none): User provided data
|
* @user_data: (nullable) (transfer none): User provided data
|
||||||
* @error: (nullable) (transfer none): #GError or %NULL
|
* @error: (nullable) (transfer none): #GError or %NULL
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ fp_image_device_close (FpDevice *device)
|
|||||||
* 1. We are inactive
|
* 1. We are inactive
|
||||||
* -> immediately close
|
* -> immediately close
|
||||||
* 2. We are waiting for finger off
|
* 2. We are waiting for finger off
|
||||||
* -> imediately deactivate
|
* -> immediately deactivate
|
||||||
* 3. We are deactivating
|
* 3. We are deactivating
|
||||||
* -> handled by deactivate_complete */
|
* -> handled by deactivate_complete */
|
||||||
|
|
||||||
|
|||||||
@@ -184,10 +184,8 @@ fp_image_detect_minutiae_cb (GObject *source_object,
|
|||||||
GTask *task = G_TASK (res);
|
GTask *task = G_TASK (res);
|
||||||
FpImage *image;
|
FpImage *image;
|
||||||
DetectMinutiaeData *data = g_task_get_task_data (task);
|
DetectMinutiaeData *data = g_task_get_task_data (task);
|
||||||
GCancellable *cancellable;
|
|
||||||
|
|
||||||
cancellable = g_task_get_cancellable (task);
|
if (!g_task_had_error (task))
|
||||||
if (!cancellable || !g_cancellable_is_cancelled (cancellable))
|
|
||||||
{
|
{
|
||||||
gint i;
|
gint i;
|
||||||
image = FP_IMAGE (source_object);
|
image = FP_IMAGE (source_object);
|
||||||
@@ -316,6 +314,14 @@ fp_image_detect_minutiae_thread_func (GTask *task,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data->minutiae || data->minutiae->num == 0)
|
||||||
|
{
|
||||||
|
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"No minutiae found");
|
||||||
|
g_object_unref (task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
g_task_return_boolean (task, TRUE);
|
g_task_return_boolean (task, TRUE);
|
||||||
g_object_unref (task);
|
g_object_unref (task);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ enum {
|
|||||||
PROP_IMAGE,
|
PROP_IMAGE,
|
||||||
|
|
||||||
/* The following is metadata that is stored by default for each print.
|
/* The following is metadata that is stored by default for each print.
|
||||||
* Drivers may make use of these during enrollment (e.g. to additionaly store
|
* Drivers may make use of these during enrollment (e.g. to additionally store
|
||||||
* the metadata on the device). */
|
* the metadata on the device). */
|
||||||
PROP_FINGER,
|
PROP_FINGER,
|
||||||
PROP_USERNAME,
|
PROP_USERNAME,
|
||||||
@@ -588,7 +588,7 @@ fp_print_equal (FpPrint *self, FpPrint *other)
|
|||||||
}
|
}
|
||||||
else if (self->type == FPI_PRINT_NBIS)
|
else if (self->type == FPI_PRINT_NBIS)
|
||||||
{
|
{
|
||||||
gint i;
|
guint i;
|
||||||
|
|
||||||
if (self->prints->len != other->prints->len)
|
if (self->prints->len != other->prints->len)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -661,7 +661,7 @@ fp_print_serialize (FpPrint *print,
|
|||||||
if (print->type == FPI_PRINT_NBIS)
|
if (print->type == FPI_PRINT_NBIS)
|
||||||
{
|
{
|
||||||
GVariantBuilder nested = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a(aiaiai))"));
|
GVariantBuilder nested = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a(aiaiai))"));
|
||||||
gint i;
|
guint i;
|
||||||
|
|
||||||
g_variant_builder_open (&nested, G_VARIANT_TYPE ("a(aiaiai)"));
|
g_variant_builder_open (&nested, G_VARIANT_TYPE ("a(aiaiai)"));
|
||||||
for (i = 0; i < print->prints->len; i++)
|
for (i = 0; i < print->prints->len; i++)
|
||||||
@@ -812,7 +812,7 @@ fp_print_deserialize (const guchar *data,
|
|||||||
if (type == FPI_PRINT_NBIS)
|
if (type == FPI_PRINT_NBIS)
|
||||||
{
|
{
|
||||||
g_autoptr(GVariant) prints = g_variant_get_child_value (print_data, 0);
|
g_autoptr(GVariant) prints = g_variant_get_child_value (print_data, 0);
|
||||||
gint i;
|
guint i;
|
||||||
|
|
||||||
result = g_object_new (FP_TYPE_PRINT,
|
result = g_object_new (FP_TYPE_PRINT,
|
||||||
"driver", driver,
|
"driver", driver,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
* @title: Internal FpDevice
|
* @title: Internal FpDevice
|
||||||
* @short_description: Internal device routines
|
* @short_description: Internal device routines
|
||||||
*
|
*
|
||||||
* The methods that are availabe for drivers to manipulate a device. See
|
* The methods that are available for drivers to manipulate a device. See
|
||||||
* #FpDeviceClass for more information. Also note that most of these are
|
* #FpDeviceClass for more information. Also note that most of these are
|
||||||
* not relevant for image based devices, see #FpImageDeviceClass in that
|
* not relevant for image based devices, see #FpImageDeviceClass in that
|
||||||
* case.
|
* case.
|
||||||
@@ -100,7 +100,7 @@ fpi_device_error_new (FpDeviceError error)
|
|||||||
switch (error)
|
switch (error)
|
||||||
{
|
{
|
||||||
case FP_DEVICE_ERROR_GENERAL:
|
case FP_DEVICE_ERROR_GENERAL:
|
||||||
msg = "An unspecified error occured!";
|
msg = "An unspecified error occurred!";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FP_DEVICE_ERROR_NOT_SUPPORTED:
|
case FP_DEVICE_ERROR_NOT_SUPPORTED:
|
||||||
@@ -138,7 +138,7 @@ fpi_device_error_new (FpDeviceError error)
|
|||||||
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;
|
||||||
msg = "An unspecified error occured!";
|
msg = "An unspecified error occurred!";
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_error_new_literal (FP_DEVICE_ERROR, error, msg);
|
return g_error_new_literal (FP_DEVICE_ERROR, error, msg);
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ struct _FpIdEntry
|
|||||||
* @probe: Called immediately for all devices. Most drivers will not need to
|
* @probe: Called immediately for all devices. Most drivers will not need to
|
||||||
* implement this. Drivers should setup the device identifier from the probe
|
* implement this. Drivers should setup the device identifier from the probe
|
||||||
* callback which will be used to verify the compatibility of stored
|
* callback which will be used to verify the compatibility of stored
|
||||||
* #FpPrint's. It is permissable to temporarily open the USB device if this
|
* #FpPrint's. It is permissible to temporarily open the USB device if this
|
||||||
* is required for the operation. If an error is returned, then the device
|
* is required for the operation. If an error is returned, then the device
|
||||||
* will be destroyed again immediately and never reported to the API user.
|
* will be destroyed again immediately and never reported to the API user.
|
||||||
* @open: Open the device for futher operations. Any of the normal actions are
|
* @open: Open the device for further operations. Any of the normal actions are
|
||||||
* guaranteed to only happen when the device is open (this includes delete).
|
* guaranteed to only happen when the device is open (this includes delete).
|
||||||
* @close: Close the device again
|
* @close: Close the device again
|
||||||
* @enroll: Start an enroll operation
|
* @enroll: Start an enroll operation
|
||||||
|
|||||||
@@ -171,7 +171,16 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||||||
print = fp_print_new (device);
|
print = fp_print_new (device);
|
||||||
fpi_print_set_type (print, FPI_PRINT_NBIS);
|
fpi_print_set_type (print, FPI_PRINT_NBIS);
|
||||||
if (!fpi_print_add_from_image (print, image, &error))
|
if (!fpi_print_add_from_image (print, image, &error))
|
||||||
|
{
|
||||||
g_clear_object (&print);
|
g_clear_object (&print);
|
||||||
|
|
||||||
|
if (error->domain != FP_DEVICE_RETRY)
|
||||||
|
{
|
||||||
|
fpi_device_action_error (device, error);
|
||||||
|
fpi_image_device_deactivate (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == FPI_DEVICE_ACTION_ENROLL)
|
if (action == FPI_DEVICE_ACTION_ENROLL)
|
||||||
|
|||||||
@@ -247,3 +247,115 @@ fpi_print_bz3_match (FpPrint *template, FpPrint *print, gint bz3_threshold, GErr
|
|||||||
|
|
||||||
return FPI_MATCH_FAIL;
|
return FPI_MATCH_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fpi_print_generate_user_id:
|
||||||
|
* @print: #FpPrint to generate the ID for
|
||||||
|
*
|
||||||
|
* Generates a string identifier for the represented print. This identifier
|
||||||
|
* encodes some metadata about the print. It also includes a random string
|
||||||
|
* and may be assumed to be unique.
|
||||||
|
*
|
||||||
|
* This is useful if devices are able to store a string identifier, but more
|
||||||
|
* storing more metadata may be desirable. In effect, this means the driver
|
||||||
|
* can provide somewhat more meaningful data to fp_device_list_prints().
|
||||||
|
*
|
||||||
|
* The generated ID may be truncated after 23 characters. However, more space
|
||||||
|
* is required to include the username, and it is recommended to store at
|
||||||
|
* at least 31 bytes.
|
||||||
|
*
|
||||||
|
* The generated format may change in the future. It is versioned though and
|
||||||
|
* decoding should remain functional.
|
||||||
|
*
|
||||||
|
* Returns: A unique string of 23 + strlen(username) characters
|
||||||
|
*/
|
||||||
|
gchar *
|
||||||
|
fpi_print_generate_user_id (FpPrint *print)
|
||||||
|
{
|
||||||
|
const gchar *username = NULL;
|
||||||
|
gchar *user_id = NULL;
|
||||||
|
const GDate *date;
|
||||||
|
gint y = 0, m = 0, d = 0;
|
||||||
|
gint32 rand_id = 0;
|
||||||
|
|
||||||
|
g_assert (print);
|
||||||
|
date = fp_print_get_enroll_date (print);
|
||||||
|
if (date && g_date_valid (date))
|
||||||
|
{
|
||||||
|
y = g_date_get_year (date);
|
||||||
|
m = g_date_get_month (date);
|
||||||
|
d = g_date_get_day (date);
|
||||||
|
}
|
||||||
|
|
||||||
|
username = fp_print_get_username (print);
|
||||||
|
if (!username)
|
||||||
|
username = "nobody";
|
||||||
|
|
||||||
|
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
|
||||||
|
rand_id = 0;
|
||||||
|
else
|
||||||
|
rand_id = g_random_int ();
|
||||||
|
|
||||||
|
user_id = g_strdup_printf ("FP1-%04d%02d%02d-%X-%08X-%s",
|
||||||
|
y, m, d,
|
||||||
|
fp_print_get_finger (print),
|
||||||
|
rand_id,
|
||||||
|
username);
|
||||||
|
|
||||||
|
return user_id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fpi_print_fill_from_user_id:
|
||||||
|
* @print: #FpPrint to fill metadata into
|
||||||
|
* @user_id: An ID that was likely encoded using fpi_print_generate_user_id()
|
||||||
|
*
|
||||||
|
* This is the reverse operation of fpi_print_generate_user_id(), allowing
|
||||||
|
* the driver to encode some print metadata in a string.
|
||||||
|
*
|
||||||
|
* Returns: Whether a valid ID was found
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
fpi_print_fill_from_user_id (FpPrint *print, const char *user_id)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (user_id, FALSE);
|
||||||
|
|
||||||
|
/* The format has 24 bytes at the start and some dashes in the right places */
|
||||||
|
if (g_str_has_prefix (user_id, "FP1-") && strlen (user_id) >= 24 &&
|
||||||
|
user_id[12] == '-' && user_id[14] == '-' && user_id[23] == '-')
|
||||||
|
{
|
||||||
|
g_autofree gchar *copy = g_strdup (user_id);
|
||||||
|
g_autoptr(GDate) date = NULL;
|
||||||
|
gint32 date_ymd;
|
||||||
|
gint32 finger;
|
||||||
|
gchar *username;
|
||||||
|
/* Try to parse information from the string. */
|
||||||
|
|
||||||
|
copy[12] = '\0';
|
||||||
|
date_ymd = g_ascii_strtod (copy + 4, NULL);
|
||||||
|
if (date_ymd > 0)
|
||||||
|
date = g_date_new_dmy (date_ymd % 100,
|
||||||
|
(date_ymd / 100) % 100,
|
||||||
|
date_ymd / 10000);
|
||||||
|
else
|
||||||
|
date = g_date_new ();
|
||||||
|
|
||||||
|
fp_print_set_enroll_date (print, date);
|
||||||
|
|
||||||
|
copy[14] = '\0';
|
||||||
|
finger = g_ascii_strtoll (copy + 13, NULL, 16);
|
||||||
|
fp_print_set_finger (print, finger);
|
||||||
|
|
||||||
|
/* We ignore the next chunk, it is just random data.
|
||||||
|
* Then comes the username; nobody is the default if the metadata
|
||||||
|
* is unknown */
|
||||||
|
username = copy + 24;
|
||||||
|
if (strlen (username) > 0 && g_strcmp0 (username, "nobody") != 0)
|
||||||
|
fp_print_set_username (print, username);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ typedef enum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FpiMatchResult:
|
* FpiMatchResult:
|
||||||
* @FPI_MATCH_ERROR: An error occured during matching
|
* @FPI_MATCH_ERROR: An error occurred during matching
|
||||||
* @FPI_MATCH_FAIL: The prints did not match
|
* @FPI_MATCH_FAIL: The prints did not match
|
||||||
* @FPI_MATCH_SUCCESS: The prints matched
|
* @FPI_MATCH_SUCCESS: The prints matched
|
||||||
*/
|
*/
|
||||||
@@ -47,4 +47,9 @@ FpiMatchResult fpi_print_bz3_match (FpPrint * template,
|
|||||||
gint bz3_threshold,
|
gint bz3_threshold,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
|
/* Helpers to encode metadata into user ID strings. */
|
||||||
|
gchar * fpi_print_generate_user_id (FpPrint *print);
|
||||||
|
gboolean fpi_print_fill_from_user_id (FpPrint *print,
|
||||||
|
const char *user_id);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ transfer_finish_cb (GObject *source_object, GAsyncResult *res, gpointer user_dat
|
|||||||
*
|
*
|
||||||
* Note that #FpiUsbTransfer will be stolen when this function is called.
|
* Note that #FpiUsbTransfer will be stolen when this function is called.
|
||||||
* So that all associated data will be free'ed automatically, after the
|
* So that all associated data will be free'ed automatically, after the
|
||||||
* callback ran unless fpi_usb_transfer_ref() is explictly called.
|
* callback ran unless fpi_usb_transfer_ref() is explicitly called.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
fpi_usb_transfer_submit (FpiUsbTransfer *transfer,
|
fpi_usb_transfer_submit (FpiUsbTransfer *transfer,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ typedef enum {
|
|||||||
* @length: The requested length of the transfer in bytes.
|
* @length: The requested length of the transfer in bytes.
|
||||||
* @actual_length: The actual length of the transfer
|
* @actual_length: The actual length of the transfer
|
||||||
* (see also fpi_usb_transfer_set_short_error())
|
* (see also fpi_usb_transfer_set_short_error())
|
||||||
* @buffer: The transfered data.
|
* @buffer: The transferred data.
|
||||||
*
|
*
|
||||||
* Helper for handling USB transfers.
|
* Helper for handling USB transfers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Copyright (C) 2009 Red Hat <mjg@redhat.com>
|
* Copyright (C) 2009 Red Hat <mjg@redhat.com>
|
||||||
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
|
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
|
||||||
* Copyright (C) 2008 Timo Hoenig <thoenig@suse.de>, <thoenig@nouse.net>
|
* Copyright (C) 2008 Timo Hoenig <thoenig@suse.de>, <thoenig@nouse.net>
|
||||||
* Coypright (C) 2019 Benjamin Berg <bberg@redhat.com>
|
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Copyright (C) 2009 Red Hat <mjg@redhat.com>
|
* Copyright (C) 2009 Red Hat <mjg@redhat.com>
|
||||||
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
|
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
|
||||||
* Copyright (C) 2008 Timo Hoenig <thoenig@suse.de>, <thoenig@nouse.net>
|
* Copyright (C) 2008 Timo Hoenig <thoenig@suse.de>, <thoenig@nouse.net>
|
||||||
* Coypright (C) 2019 Benjamin Berg <bberg@redhat.com>
|
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -959,7 +959,7 @@ test_driver_verify_complete_retry (void)
|
|||||||
|
|
||||||
g_test_assert_expected_messages ();
|
g_test_assert_expected_messages ();
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->user_data));
|
g_steal_pointer (&fake_dev->user_data);
|
||||||
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
||||||
g_assert_true (match_data->called);
|
g_assert_true (match_data->called);
|
||||||
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
||||||
@@ -981,7 +981,7 @@ test_driver_verify_complete_retry (void)
|
|||||||
g_test_assert_expected_messages ();
|
g_test_assert_expected_messages ();
|
||||||
|
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->user_data));
|
g_steal_pointer (&fake_dev->user_data);
|
||||||
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
||||||
g_assert_true (match_data->called);
|
g_assert_true (match_data->called);
|
||||||
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
||||||
@@ -1001,7 +1001,7 @@ test_driver_verify_complete_retry (void)
|
|||||||
g_test_assert_expected_messages ();
|
g_test_assert_expected_messages ();
|
||||||
|
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->user_data));
|
g_steal_pointer (&fake_dev->user_data);
|
||||||
g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL);
|
g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL);
|
||||||
g_assert_true (match_data->called);
|
g_assert_true (match_data->called);
|
||||||
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL);
|
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL);
|
||||||
@@ -1296,7 +1296,7 @@ test_driver_identify_complete_retry (void)
|
|||||||
g_test_assert_expected_messages ();
|
g_test_assert_expected_messages ();
|
||||||
|
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
g_assert_true (error != g_steal_pointer (&fake_dev->ret_error));
|
||||||
g_assert_true (error != g_steal_pointer (&fake_dev->user_data));
|
g_steal_pointer (&fake_dev->user_data);
|
||||||
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
|
||||||
g_assert_true (match_data->called);
|
g_assert_true (match_data->called);
|
||||||
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT);
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
import sys
|
import sys
|
||||||
try:
|
try:
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('FPrint', '2.0')
|
|
||||||
from gi.repository import FPrint, GLib, Gio
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
from gi.repository import GLib, Gio
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
@@ -20,6 +19,8 @@ except Exception as e:
|
|||||||
print("Missing dependencies: %s" % str(e))
|
print("Missing dependencies: %s" % str(e))
|
||||||
sys.exit(77)
|
sys.exit(77)
|
||||||
|
|
||||||
|
FPrint = None
|
||||||
|
|
||||||
# Re-run the test with the passed wrapper if set
|
# Re-run the test with the passed wrapper if set
|
||||||
wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER')
|
wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER')
|
||||||
if wrapper:
|
if wrapper:
|
||||||
@@ -101,12 +102,14 @@ class VirtualImage(unittest.TestCase):
|
|||||||
del self.con
|
del self.con
|
||||||
self.dev.close_sync()
|
self.dev.close_sync()
|
||||||
|
|
||||||
def send_retry(self, retry_error=FPrint.DeviceRetry.TOO_SHORT, iterate=True):
|
def send_retry(self, retry_error=None, iterate=True):
|
||||||
|
retry_error = retry_error if retry_error else FPrint.DeviceRetry.TOO_SHORT
|
||||||
self.con.sendall(struct.pack('ii', -1, retry_error))
|
self.con.sendall(struct.pack('ii', -1, retry_error))
|
||||||
while iterate and ctx.pending():
|
while iterate and ctx.pending():
|
||||||
ctx.iteration(False)
|
ctx.iteration(False)
|
||||||
|
|
||||||
def send_error(self, device_error=FPrint.DeviceError.GENERAL, iterate=True):
|
def send_error(self, device_error=None, iterate=True):
|
||||||
|
device_error = device_error if device_error else FPrint.DeviceError.GENERAL
|
||||||
self.con.sendall(struct.pack('ii', -2, device_error))
|
self.con.sendall(struct.pack('ii', -2, device_error))
|
||||||
while iterate and ctx.pending():
|
while iterate and ctx.pending():
|
||||||
ctx.iteration(False)
|
ctx.iteration(False)
|
||||||
@@ -346,5 +349,12 @@ class VirtualImage(unittest.TestCase):
|
|||||||
assert(not self._verify_match)
|
assert(not self._verify_match)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
gi.require_version('FPrint', '2.0')
|
||||||
|
from gi.repository import FPrint
|
||||||
|
except Exception as e:
|
||||||
|
print("Missing dependencies: %s" % str(e))
|
||||||
|
sys.exit(77)
|
||||||
|
|
||||||
# avoid writing to stderr
|
# avoid writing to stderr
|
||||||
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
|
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
|
||||||
|
|||||||
Reference in New Issue
Block a user