<commit_before>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

/**
 * SECTION:gusb-context
 * @short_description: Per-thread instance integration for libusb
 *
 * This object is used to get a context that is thread safe.
 */

#include "config.h"

#include <libusb.h>

#include "gusb-context-private.h"
#include "gusb-device-private.h"
#include "gusb-util.h"

enum { PROP_0, PROP_LIBUSB_CONTEXT, PROP_DEBUG_LEVEL, N_PROPERTIES };

enum { DEVICE_ADDED_SIGNAL, DEVICE_REMOVED_SIGNAL, LAST_SIGNAL };

#define G_USB_CONTEXT_HOTPLUG_POLL_INTERVAL_DEFAULT 1000 /* ms */

#define GET_PRIVATE(o) (g_usb_context_get_instance_private(o))

/**
 * GUsbContextPrivate:
 *
 * Private #GUsbContext data
 **/
typedef struct {
	GMainContext *main_ctx;
	GPtrArray *devices;
	GPtrArray *devices_removed;
	GHashTable *dict_usb_ids;
	GHashTable *dict_replug;
	GThread *thread_event;
	gboolean done_enumerate;
	volatile gint thread_event_run;
	guint hotplug_poll_id;
	guint hotplug_poll_interval;
	int debug_level;
	GUsbContextFlags flags;
	libusb_context *ctx;
	libusb_hotplug_callback_handle hotplug_id;
	GPtrArray *idle_events;
	GMutex idle_events_mutex;
	guint idle_events_id;
} GUsbContextPrivate;

/* not defined in FreeBSD */
#ifndef HAVE_LIBUSB_CAP_HAS_HOTPLUG
#define LIBUSB_CAP_HAS_HOTPLUG 0x0001
#endif

typedef struct {
	GMainLoop *loop;
	GUsbDevice *device;
	guint timeout_id;
} GUsbContextReplugHelper;

static guint signals[LAST_SIGNAL] = {0};
static GParamSpec *pspecs[N_PROPERTIES] = {
    NULL,
};

static void
g_usb_context_initable_iface_init(GInitableIface *iface);

G_DEFINE_TYPE_WITH_CODE(GUsbContext,
			g_usb_context,
			G_TYPE_OBJECT,
			G_ADD_PRIVATE(GUsbContext)
			    G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE,
						  g_usb_context_initable_iface_init))

/* not defined in FreeBSD */
#ifndef HAVE_LIBUSB_HAS_CAPABILITY
static gboolean
libusb_has_capability(int cap)
{
	if (cap == LIBUSB_CAP_HAS_HOTPLUG)
		return TRUE;
	return FALSE;
}
#endif

static void
g_usb_context_replug_helper_free(GUsbContextReplugHelper *replug_helper)
{
	if (replug_helper->timeout_id != 0)
		g_source_remove(replug_helper->timeout_id);
	g_main_loop_unref(replug_helper->loop);
	g_object_unref(replug_helper->device);
	g_free(replug_helper);
}

/* clang-format off */
/**
 * g_usb_context_error_quark:
 *
 * Return value: Our personal error quark.
 *
 * Since: 0.1.0
 **/
G_DEFINE_QUARK (g-usb-context-error-quark, g_usb_context_error)
/* clang-format on */

static void
g_usb_context_dispose(GObject *object)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* this is safe to call even when priv->hotplug_id is unset */
	if (g_atomic_int_dec_and_test(&priv->thread_event_run)) {
		libusb_hotplug_deregister_callback(priv->ctx, priv->hotplug_id);
		g_thread_join(priv->thread_event);
	}

	if (priv->hotplug_poll_id > 0) {
		g_source_remove(priv->hotplug_poll_id);
		priv->hotplug_poll_id = 0;
	}
	if (priv->idle_events_id > 0) {
		g_source_remove(priv->idle_events_id);
		priv->idle_events_id = 0;
	}

	g_clear_pointer(&priv->main_ctx, g_main_context_unref);
	g_clear_pointer(&priv->devices, g_ptr_array_unref);
	g_clear_pointer(&priv->devices_removed, g_ptr_array_unref);
	g_clear_pointer(&priv->dict_usb_ids, g_hash_table_unref);
	g_clear_pointer(&priv->dict_replug, g_hash_table_unref);
	g_clear_pointer(&priv->ctx, libusb_exit);
	g_clear_pointer(&priv->idle_events, g_ptr_array_unref);
	g_mutex_clear(&priv->idle_events_mutex);

	G_OBJECT_CLASS(g_usb_context_parent_class)->dispose(object);
}

static void
g_usb_context_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	switch (prop_id) {
	case PROP_LIBUSB_CONTEXT:
		g_value_set_pointer(value, priv->ctx);
		break;
	case PROP_DEBUG_LEVEL:
		g_value_set_int(value, priv->debug_level);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
g_usb_context_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	switch (prop_id) {
	case PROP_DEBUG_LEVEL:
		priv->debug_level = g_value_get_int(value);
#ifdef HAVE_LIBUSB_SET_OPTION
		libusb_set_option(priv->ctx, LIBUSB_OPTION_LOG_LEVEL, priv->debug_level);
#else
		libusb_set_debug(priv->ctx, priv->debug_level);
#endif
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
g_usb_context_class_init(GUsbContextClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	object_class->dispose = g_usb_context_dispose;
	object_class->get_property = g_usb_context_get_property;
	object_class->set_property = g_usb_context_set_property;

	/**
	 * GUsbContext:libusb_context:
	 */
	pspecs[PROP_LIBUSB_CONTEXT] =
	    g_param_spec_pointer("libusb_context", NULL, NULL, G_PARAM_READABLE);

	/**
	 * GUsbContext:debug_level:
	 */
	pspecs[PROP_DEBUG_LEVEL] =
	    g_param_spec_int("debug_level", NULL, NULL, 0, 3, 0, G_PARAM_READWRITE);

	g_object_class_install_properties(object_class, N_PROPERTIES, pspecs);

	/**
	 * GUsbContext::device-added:
	 * @self: the #GUsbContext instance that emitted the signal
	 * @device: A #GUsbDevice
	 *
	 * This signal is emitted when a USB device is added.
	 **/
	signals[DEVICE_ADDED_SIGNAL] = g_signal_new("device-added",
						    G_TYPE_FROM_CLASS(klass),
						    G_SIGNAL_RUN_LAST,
						    G_STRUCT_OFFSET(GUsbContextClass, device_added),
						    NULL,
						    NULL,
						    g_cclosure_marshal_VOID__OBJECT,
						    G_TYPE_NONE,
						    1,
						    G_USB_TYPE_DEVICE);

	/**
	 * GUsbContext::device-removed:
	 * @self: the #GUsbContext instance that emitted the signal
	 * @device: A #GUsbDevice
	 *
	 * This signal is emitted when a USB device is removed.
	 **/
	signals[DEVICE_REMOVED_SIGNAL] =
	    g_signal_new("device-removed",
			 G_TYPE_FROM_CLASS(klass),
			 G_SIGNAL_RUN_LAST,
			 G_STRUCT_OFFSET(GUsbContextClass, device_removed),
			 NULL,
			 NULL,
			 g_cclosure_marshal_VOID__OBJECT,
			 G_TYPE_NONE,
			 1,
			 G_USB_TYPE_DEVICE);
}

static void
g_usb_context_emit_device_add(GUsbContext *self, GUsbDevice *device)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* emitted directly by g_usb_context_enumerate */
	if (!priv->done_enumerate)
		return;

	g_signal_emit(self, signals[DEVICE_ADDED_SIGNAL], 0, device);
}

static void
g_usb_context_emit_device_remove(GUsbContext *self, GUsbDevice *device)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* should not happen, if it does we would not fire any signal */
	if (!priv->done_enumerate)
		return;

	g_signal_emit(self, signals[DEVICE_REMOVED_SIGNAL], 0, device);
}

static void
g_usb_context_add_device(GUsbContext *self, struct libusb_device *dev)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	GUsbContextReplugHelper *replug_helper;
	const gchar *platform_id;
	guint8 bus;
	guint8 address;
	g_autoptr(GError) error = NULL;
	g_autoptr(GUsbDevice) device = NULL;

	/* does any existing device exist */
	bus = libusb_get_bus_number(dev);
	address = libusb_get_device_address(dev);

	if (priv->done_enumerate)
		device = g_usb_context_find_by_bus_address(self, bus, address, NULL);
	if (device != NULL)
		return;

	/* add the device */
	device = _g_usb_device_new(self, dev, &error);
	if (device == NULL) {
		g_debug("There was a problem creating the device: %s", error->message);
		return;
	}

	/* auto-open */
	if (priv->flags & G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES) {
		if (!_g_usb_device_open_internal(device, &error)) {
			g_warning("cannot open the device: %s", error->message);
			return;
		}
	}

	/* add to enumerated list */
	g_ptr_array_add(priv->devices, g_object_ref(device));

	/* if we're waiting for replug, suppress the signal */
	platform_id = g_usb_device_get_platform_id(device);
	replug_helper = g_hash_table_lookup(priv->dict_replug, platform_id);
	if (replug_helper != NULL) {
		g_debug("%s is in replug, ignoring add", platform_id);
		g_object_unref(replug_helper->device);
		replug_helper->device = g_object_ref(device);
		g_main_loop_quit(replug_helper->loop);
		return;
	}

	/* emit signal */
	g_usb_context_emit_device_add(self, device);
}

static void
g_usb_context_remove_device(GUsbContext *self, struct libusb_device *dev)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	GUsbContextReplugHelper *replug_helper;
	const gchar *platform_id;
	guint8 bus;
	guint8 address;
	g_autoptr(GUsbDevice) device = NULL;

	/* does any existing device exist */
	bus = libusb_get_bus_number(dev);
	address = libusb_get_device_address(dev);
	device = g_usb_context_find_by_bus_address(self, bus, address, NULL);
	if (device == NULL) {
		g_debug("%i:%i does not exist", bus, address);
		return;
	}

	/* save this to a lookaside */
	if (priv->flags & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) {
		g_ptr_array_add(priv->devices_removed, g_object_ref(device));
	}

	/* remove from enumerated list */
	g_ptr_array_remove(priv->devices, device);

	/* if we're waiting for replug, suppress the signal */
	platform_id = g_usb_device_get_platform_id(device);
	replug_helper = g_hash_table_lookup(priv->dict_replug, platform_id);
	if (replug_helper != NULL) {
		g_debug("%s is in replug, ignoring remove", platform_id);
		return;
	}

	/* emit signal */
	g_usb_context_emit_device_remove(self, device);
}

/**
 * g_usb_context_load:
 * @self: a #GUsbContext
 * @json_object: a #JsonObject
 * @error: a #GError, or %NULL
 *
 * Loads the context from a JSON object.
 *
 * Return value: %TRUE on success
 *
 * Since: 0.4.0
 **/
gboolean
g_usb_context_load(GUsbContext *self, JsonObject *json_object, GError **error)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	JsonArray *json_array;

	g_return_val_if_fail(G_USB_IS_CONTEXT(self), FALSE);
	g_return_val_if_fail(json_object != NULL, FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, -1);

	/* remove existing devices */<commit_msg>trivial: Don't return -1 for failure for a gboolean<commit_after>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

/**
 * SECTION:gusb-context
 * @short_description: Per-thread instance integration for libusb
 *
 * This object is used to get a context that is thread safe.
 */

#include "config.h"

#include <libusb.h>

#include "gusb-context-private.h"
#include "gusb-device-private.h"
#include "gusb-util.h"

enum { PROP_0, PROP_LIBUSB_CONTEXT, PROP_DEBUG_LEVEL, N_PROPERTIES };

enum { DEVICE_ADDED_SIGNAL, DEVICE_REMOVED_SIGNAL, LAST_SIGNAL };

#define G_USB_CONTEXT_HOTPLUG_POLL_INTERVAL_DEFAULT 1000 /* ms */

#define GET_PRIVATE(o) (g_usb_context_get_instance_private(o))

/**
 * GUsbContextPrivate:
 *
 * Private #GUsbContext data
 **/
typedef struct {
	GMainContext *main_ctx;
	GPtrArray *devices;
	GPtrArray *devices_removed;
	GHashTable *dict_usb_ids;
	GHashTable *dict_replug;
	GThread *thread_event;
	gboolean done_enumerate;
	volatile gint thread_event_run;
	guint hotplug_poll_id;
	guint hotplug_poll_interval;
	int debug_level;
	GUsbContextFlags flags;
	libusb_context *ctx;
	libusb_hotplug_callback_handle hotplug_id;
	GPtrArray *idle_events;
	GMutex idle_events_mutex;
	guint idle_events_id;
} GUsbContextPrivate;

/* not defined in FreeBSD */
#ifndef HAVE_LIBUSB_CAP_HAS_HOTPLUG
#define LIBUSB_CAP_HAS_HOTPLUG 0x0001
#endif

typedef struct {
	GMainLoop *loop;
	GUsbDevice *device;
	guint timeout_id;
} GUsbContextReplugHelper;

static guint signals[LAST_SIGNAL] = {0};
static GParamSpec *pspecs[N_PROPERTIES] = {
    NULL,
};

static void
g_usb_context_initable_iface_init(GInitableIface *iface);

G_DEFINE_TYPE_WITH_CODE(GUsbContext,
			g_usb_context,
			G_TYPE_OBJECT,
			G_ADD_PRIVATE(GUsbContext)
			    G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE,
						  g_usb_context_initable_iface_init))

/* not defined in FreeBSD */
#ifndef HAVE_LIBUSB_HAS_CAPABILITY
static gboolean
libusb_has_capability(int cap)
{
	if (cap == LIBUSB_CAP_HAS_HOTPLUG)
		return TRUE;
	return FALSE;
}
#endif

static void
g_usb_context_replug_helper_free(GUsbContextReplugHelper *replug_helper)
{
	if (replug_helper->timeout_id != 0)
		g_source_remove(replug_helper->timeout_id);
	g_main_loop_unref(replug_helper->loop);
	g_object_unref(replug_helper->device);
	g_free(replug_helper);
}

/* clang-format off */
/**
 * g_usb_context_error_quark:
 *
 * Return value: Our personal error quark.
 *
 * Since: 0.1.0
 **/
G_DEFINE_QUARK (g-usb-context-error-quark, g_usb_context_error)
/* clang-format on */

static void
g_usb_context_dispose(GObject *object)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* this is safe to call even when priv->hotplug_id is unset */
	if (g_atomic_int_dec_and_test(&priv->thread_event_run)) {
		libusb_hotplug_deregister_callback(priv->ctx, priv->hotplug_id);
		g_thread_join(priv->thread_event);
	}

	if (priv->hotplug_poll_id > 0) {
		g_source_remove(priv->hotplug_poll_id);
		priv->hotplug_poll_id = 0;
	}
	if (priv->idle_events_id > 0) {
		g_source_remove(priv->idle_events_id);
		priv->idle_events_id = 0;
	}

	g_clear_pointer(&priv->main_ctx, g_main_context_unref);
	g_clear_pointer(&priv->devices, g_ptr_array_unref);
	g_clear_pointer(&priv->devices_removed, g_ptr_array_unref);
	g_clear_pointer(&priv->dict_usb_ids, g_hash_table_unref);
	g_clear_pointer(&priv->dict_replug, g_hash_table_unref);
	g_clear_pointer(&priv->ctx, libusb_exit);
	g_clear_pointer(&priv->idle_events, g_ptr_array_unref);
	g_mutex_clear(&priv->idle_events_mutex);

	G_OBJECT_CLASS(g_usb_context_parent_class)->dispose(object);
}

static void
g_usb_context_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	switch (prop_id) {
	case PROP_LIBUSB_CONTEXT:
		g_value_set_pointer(value, priv->ctx);
		break;
	case PROP_DEBUG_LEVEL:
		g_value_set_int(value, priv->debug_level);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
g_usb_context_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	GUsbContext *self = G_USB_CONTEXT(object);
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	switch (prop_id) {
	case PROP_DEBUG_LEVEL:
		priv->debug_level = g_value_get_int(value);
#ifdef HAVE_LIBUSB_SET_OPTION
		libusb_set_option(priv->ctx, LIBUSB_OPTION_LOG_LEVEL, priv->debug_level);
#else
		libusb_set_debug(priv->ctx, priv->debug_level);
#endif
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
g_usb_context_class_init(GUsbContextClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	object_class->dispose = g_usb_context_dispose;
	object_class->get_property = g_usb_context_get_property;
	object_class->set_property = g_usb_context_set_property;

	/**
	 * GUsbContext:libusb_context:
	 */
	pspecs[PROP_LIBUSB_CONTEXT] =
	    g_param_spec_pointer("libusb_context", NULL, NULL, G_PARAM_READABLE);

	/**
	 * GUsbContext:debug_level:
	 */
	pspecs[PROP_DEBUG_LEVEL] =
	    g_param_spec_int("debug_level", NULL, NULL, 0, 3, 0, G_PARAM_READWRITE);

	g_object_class_install_properties(object_class, N_PROPERTIES, pspecs);

	/**
	 * GUsbContext::device-added:
	 * @self: the #GUsbContext instance that emitted the signal
	 * @device: A #GUsbDevice
	 *
	 * This signal is emitted when a USB device is added.
	 **/
	signals[DEVICE_ADDED_SIGNAL] = g_signal_new("device-added",
						    G_TYPE_FROM_CLASS(klass),
						    G_SIGNAL_RUN_LAST,
						    G_STRUCT_OFFSET(GUsbContextClass, device_added),
						    NULL,
						    NULL,
						    g_cclosure_marshal_VOID__OBJECT,
						    G_TYPE_NONE,
						    1,
						    G_USB_TYPE_DEVICE);

	/**
	 * GUsbContext::device-removed:
	 * @self: the #GUsbContext instance that emitted the signal
	 * @device: A #GUsbDevice
	 *
	 * This signal is emitted when a USB device is removed.
	 **/
	signals[DEVICE_REMOVED_SIGNAL] =
	    g_signal_new("device-removed",
			 G_TYPE_FROM_CLASS(klass),
			 G_SIGNAL_RUN_LAST,
			 G_STRUCT_OFFSET(GUsbContextClass, device_removed),
			 NULL,
			 NULL,
			 g_cclosure_marshal_VOID__OBJECT,
			 G_TYPE_NONE,
			 1,
			 G_USB_TYPE_DEVICE);
}

static void
g_usb_context_emit_device_add(GUsbContext *self, GUsbDevice *device)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* emitted directly by g_usb_context_enumerate */
	if (!priv->done_enumerate)
		return;

	g_signal_emit(self, signals[DEVICE_ADDED_SIGNAL], 0, device);
}

static void
g_usb_context_emit_device_remove(GUsbContext *self, GUsbDevice *device)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);

	/* should not happen, if it does we would not fire any signal */
	if (!priv->done_enumerate)
		return;

	g_signal_emit(self, signals[DEVICE_REMOVED_SIGNAL], 0, device);
}

static void
g_usb_context_add_device(GUsbContext *self, struct libusb_device *dev)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	GUsbContextReplugHelper *replug_helper;
	const gchar *platform_id;
	guint8 bus;
	guint8 address;
	g_autoptr(GError) error = NULL;
	g_autoptr(GUsbDevice) device = NULL;

	/* does any existing device exist */
	bus = libusb_get_bus_number(dev);
	address = libusb_get_device_address(dev);

	if (priv->done_enumerate)
		device = g_usb_context_find_by_bus_address(self, bus, address, NULL);
	if (device != NULL)
		return;

	/* add the device */
	device = _g_usb_device_new(self, dev, &error);
	if (device == NULL) {
		g_debug("There was a problem creating the device: %s", error->message);
		return;
	}

	/* auto-open */
	if (priv->flags & G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES) {
		if (!_g_usb_device_open_internal(device, &error)) {
			g_warning("cannot open the device: %s", error->message);
			return;
		}
	}

	/* add to enumerated list */
	g_ptr_array_add(priv->devices, g_object_ref(device));

	/* if we're waiting for replug, suppress the signal */
	platform_id = g_usb_device_get_platform_id(device);
	replug_helper = g_hash_table_lookup(priv->dict_replug, platform_id);
	if (replug_helper != NULL) {
		g_debug("%s is in replug, ignoring add", platform_id);
		g_object_unref(replug_helper->device);
		replug_helper->device = g_object_ref(device);
		g_main_loop_quit(replug_helper->loop);
		return;
	}

	/* emit signal */
	g_usb_context_emit_device_add(self, device);
}

static void
g_usb_context_remove_device(GUsbContext *self, struct libusb_device *dev)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	GUsbContextReplugHelper *replug_helper;
	const gchar *platform_id;
	guint8 bus;
	guint8 address;
	g_autoptr(GUsbDevice) device = NULL;

	/* does any existing device exist */
	bus = libusb_get_bus_number(dev);
	address = libusb_get_device_address(dev);
	device = g_usb_context_find_by_bus_address(self, bus, address, NULL);
	if (device == NULL) {
		g_debug("%i:%i does not exist", bus, address);
		return;
	}

	/* save this to a lookaside */
	if (priv->flags & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) {
		g_ptr_array_add(priv->devices_removed, g_object_ref(device));
	}

	/* remove from enumerated list */
	g_ptr_array_remove(priv->devices, device);

	/* if we're waiting for replug, suppress the signal */
	platform_id = g_usb_device_get_platform_id(device);
	replug_helper = g_hash_table_lookup(priv->dict_replug, platform_id);
	if (replug_helper != NULL) {
		g_debug("%s is in replug, ignoring remove", platform_id);
		return;
	}

	/* emit signal */
	g_usb_context_emit_device_remove(self, device);
}

/**
 * g_usb_context_load:
 * @self: a #GUsbContext
 * @json_object: a #JsonObject
 * @error: a #GError, or %NULL
 *
 * Loads the context from a JSON object.
 *
 * Return value: %TRUE on success
 *
 * Since: 0.4.0
 **/
gboolean
g_usb_context_load(GUsbContext *self, JsonObject *json_object, GError **error)
{
	GUsbContextPrivate *priv = GET_PRIVATE(self);
	JsonArray *json_array;

	g_return_val_if_fail(G_USB_IS_CONTEXT(self), FALSE);
	g_return_val_if_fail(json_object != NULL, FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	/* remove existing devices */
{'fraction_non_alphanumeric': 0.11056016495655162, 'fraction_numerical': 0.00451666748490353, 'mean_word_length': 5.407675369613086, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3783866', 'n_tokens_mistral': 8066, 'n_tokens_neox': 7711, 'n_words': 1446}