diff --git a/src/device.c b/src/device.c index 682b085..41b9939 100644 --- a/src/device.c +++ b/src/device.c @@ -306,7 +306,8 @@ on_nr_enroll_stages_changed (FprintDevice *rdev, FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev); gint nr_enroll_stages; - nr_enroll_stages = fp_device_get_nr_enroll_stages (device); + /* One extra step for our internal identification. */ + nr_enroll_stages = fp_device_get_nr_enroll_stages (device) + 1; g_debug ("Device %s enroll stages changed to %d", fp_device_get_name (device), @@ -1637,6 +1638,7 @@ enroll_progress_cb (FpDevice *dev, g_debug ("enroll_stage_cb: result %s", name); + /* NOTE: We add one more step internally, but we can ignore that here. */ if (completed_stages < fp_device_get_nr_enroll_stages (dev)) g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, FALSE); } @@ -1827,6 +1829,103 @@ enroll_cb (FpDevice *dev, GAsyncResult *res, void *user_data) stoppable_action_completed (rdev); } +static void +enroll_identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpPrint) matched_print = NULL; + g_autoptr(FpPrint) found_print = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + const char *name; + + fp_device_identify_finish (dev, res, &matched_print, &found_print, &error); + + if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND)) + { + g_clear_object (&found_print); + g_clear_error (&error); + } + + /* We may need to retry or error out. */ + if (error) + { + gboolean retry = error->domain == FP_DEVICE_RETRY; + + name = enroll_result_to_name (!retry, FALSE, error); + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, !retry); + + /* Retry or clean up. */ + if (retry) + { + g_autoptr(GPtrArray) all_prints = NULL; + + all_prints = load_all_prints (rdev); + fp_device_identify (priv->dev, + all_prints, + priv->current_cancellable, + NULL, + NULL, + NULL, + (GAsyncReadyCallback) enroll_identify_cb, + rdev); + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Device reported an error during identify for enroll: %s", error->message); + + stoppable_action_completed (rdev); + } + + return; + } + + /* Identify has finished (successfully), there are three possible cases: + * 1. Match found in the gallery, in this case, we error out. + * 2. No match found, but on-device print returned, we should delete it + * 3. None of the above, we can just continue. + */ + + if (matched_print) + { + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, "enroll-duplicate", TRUE); + + stoppable_action_completed (rdev); + return; + } + + if (found_print && fp_device_has_storage (priv->dev)) + { + if (!fp_print_get_device_stored (found_print)) + g_critical ("libfprint driver bug: Returned device print not marked as stored on device."); + + /* Try to delete the print (synchronously), and continue if it succeeds. */ + if (!fp_device_delete_print_sync (priv->dev, + found_print, + priv->current_cancellable, + &error)) + { + g_warning ("Failed to garbage collect duplicate print, cannot continue with enroll."); + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, "enroll-duplicate", TRUE); + + stoppable_action_completed (rdev); + return; + } + } + + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, "enroll-stage-passed", FALSE); + + /* We are good and can start to enroll. */ + fp_device_enroll (priv->dev, + fprint_device_create_enroll_template (rdev, priv->enroll_data), + priv->current_cancellable, + enroll_progress_cb, + rdev, + NULL, + (GAsyncReadyCallback) enroll_cb, + rdev); +} static gboolean fprint_device_enroll_start (FprintDBusDevice *dbus_dev, @@ -1834,6 +1933,7 @@ fprint_device_enroll_start (FprintDBusDevice *dbus_dev, const char *finger_name) { g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) all_prints = NULL; FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); FpFinger finger = finger_name_to_fp_finger (finger_name); @@ -1862,17 +1962,25 @@ fprint_device_enroll_start (FprintDBusDevice *dbus_dev, priv->current_cancellable = g_cancellable_new (); priv->enroll_data = finger; - fp_device_enroll (priv->dev, - fprint_device_create_enroll_template (rdev, priv->enroll_data), - priv->current_cancellable, - enroll_progress_cb, - rdev, - NULL, - (GAsyncReadyCallback) enroll_cb, - rdev); - priv->current_action = ACTION_ENROLL; + /* We (now) have the policy that there must be no duplicate prints. + * We need to do this for MoC devices, as their "identify" function + * will generally just identify across all device stored prints. + * For MoH, we also do it. For consistency and because it allows us + * to implement new features in the future (i.e. logging in/unlocking + * the correct user without selecting it first). + */ + all_prints = load_all_prints (rdev); + fp_device_identify (priv->dev, + all_prints, + priv->current_cancellable, + NULL, + NULL, + NULL, + (GAsyncReadyCallback) enroll_identify_cb, + rdev); + fprint_dbus_device_complete_enroll_start (dbus_dev, invocation); return TRUE; diff --git a/tests/fprintd.py b/tests/fprintd.py index af12d53..37a7bfa 100644 --- a/tests/fprintd.py +++ b/tests/fprintd.py @@ -882,9 +882,10 @@ class FPrintdVirtualStorageDeviceBaseTest(FPrintdVirtualDeviceBaseTest): def _maybe_reduce_enroll_stages(self, stages=-1): # Reduce the number of default enroll stages, we can go a bit faster stages = stages if stages > 0 else self.enroll_stages + stages += 1 # Adding the extra stage for duplicates-check if self.num_enroll_stages == stages: return - self.send_command('SET_ENROLL_STAGES', stages) + self.send_command('SET_ENROLL_STAGES', stages - 1) while self.num_enroll_stages != stages: ctx.iteration(True) self.assertIn({'num-enroll-stages': stages}, self._changed_properties) @@ -918,6 +919,7 @@ class FPrintdVirtualStorageDeviceTests(FPrintdVirtualStorageDeviceBaseTest): self.assertEqual(set(prints), set(garbage_collect + list(enrolled_prints.keys()))) def trigger_garbagecollect(): + self.send_image('some-other-print') self.send_command('ERROR', int(FPrint.DeviceError.DATA_FULL)) self.device.EnrollStart('(s)', 'right-thumb') self.device.EnrollStop() @@ -1003,6 +1005,10 @@ class FPrintdVirtualNoStorageDeviceBaseTest(FPrintdVirtualStorageDeviceBaseTest) device_driver = 'virtual_device' driver_name = 'Virtual device for debugging' + def enroll_image(self, img, device=None, finger='right-index-finger', + expected_result='enroll-completed', claim_user=None): + self.skipTest('Identification not supported, thus is the enrolling') + class FPrintdVirtualNoStorageDeviceTest(FPrintdVirtualNoStorageDeviceBaseTest): def check_verify_finger_match(self, image, expect_match, finger): @@ -1107,7 +1113,7 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest): self.driver_name) def test_enroll_stages_property(self): - self.assertEqual(self.device.get_cached_property('num-enroll-stages').unpack(), 5) + self.assertEqual(self.device.get_cached_property('num-enroll-stages').unpack(), 6) def test_scan_type(self): self.assertEqual(self.device.get_cached_property('scan-type').unpack(), @@ -2206,6 +2212,8 @@ class FPrintdVirtualDeviceEnrollTests(FPrintdVirtualDeviceBaseTest): self.assertEnrollError(FPrint.DeviceError.DATA_INVALID, 'enroll-unknown-error') def test_enroll_error_data_not_found(self): + self.assertEnrollError( + FPrint.DeviceError.DATA_NOT_FOUND, 'enroll-stage-passed') self.assertEnrollError(FPrint.DeviceError.DATA_NOT_FOUND, 'enroll-unknown-error') def test_enroll_error_data_full(self): @@ -3009,6 +3017,9 @@ class FPrintdUtilsTest(FPrintdVirtualStorageDeviceBaseTest): self.send_image('print-id') out.check_line('Enroll result: enroll-stage-passed', get_timeout()) + self.send_image('print-id') + out.check_line('Enroll result: enroll-stage-passed', get_timeout()) + self.send_retry(FPrint.DeviceRetry.CENTER_FINGER) out.check_line('Enroll result: enroll-finger-not-centered', get_timeout())