[gnome-software/gnome-3-22] Refactor snapd handling code
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/gnome-3-22] Refactor snapd handling code
- Date: Wed, 26 Oct 2016 22:51:53 +0000 (UTC)
commit 7e7d36ce69bd96a4251d2230b9d084f9e9658c12
Author: Robert Ancell <robert ancell canonical com>
Date: Thu Oct 27 11:07:24 2016 +1300
Refactor snapd handling code
src/plugins/gs-plugin-snap.c | 538 +++++++++---------------------------------
src/plugins/gs-snapd.c | 522 +++++++++++++++++++++++++++++++++++++----
src/plugins/gs-snapd.h | 80 +++++--
3 files changed, 642 insertions(+), 498 deletions(-)
---
diff --git a/src/plugins/gs-plugin-snap.c b/src/plugins/gs-plugin-snap.c
index 528ca93..58cd34a 100644
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@ -70,47 +70,6 @@ gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
return TRUE;
}
-static JsonParser *
-parse_result (const gchar *response, const gchar *response_type, GError **error)
-{
- g_autoptr(JsonParser) parser = NULL;
- g_autoptr(GError) error_local = NULL;
-
- if (response_type == NULL) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned no content type");
- return NULL;
- }
- if (g_strcmp0 (response_type, "application/json") != 0) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned unexpected content type %s", response_type);
- return NULL;
- }
-
- parser = json_parser_new ();
- if (!json_parser_load_from_data (parser, response, -1, &error_local)) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "Unable to parse snapd response: %s",
- error_local->message);
- return NULL;
- }
- if (!JSON_NODE_HOLDS_OBJECT (json_parser_get_root (parser))) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd response does is not a valid JSON object");
- return NULL;
- }
-
- return g_object_ref (parser);
-}
-
static void
get_macaroon (GsPlugin *plugin, gchar **macaroon, gchar ***discharges)
{
@@ -183,26 +142,24 @@ refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_sea
gs_app_add_quirk (app, AS_APP_QUIRK_PROVENANCE);
icon_url = json_object_get_string_member (package, "icon");
if (g_str_has_prefix (icon_url, "/")) {
- g_autofree gchar *icon_response = NULL;
- gsize icon_response_length;
-
- if (gs_snapd_request ("GET", icon_url, NULL,
- macaroon, discharges,
- NULL, NULL,
- NULL, &icon_response, &icon_response_length,
- cancellable, NULL)) {
+ g_autofree gchar *icon_data = NULL;
+ gsize icon_data_length;
+ g_autoptr(GError) error = NULL;
+
+ icon_data = gs_snapd_get_resource (macaroon, discharges, icon_url, &icon_data_length,
cancellable, &error);
+ if (icon_data != NULL) {
g_autoptr(GdkPixbufLoader) loader = NULL;
loader = gdk_pixbuf_loader_new ();
gdk_pixbuf_loader_write (loader,
- (guchar *) icon_response,
- icon_response_length,
+ (guchar *) icon_data,
+ icon_data_length,
NULL);
gdk_pixbuf_loader_close (loader, NULL);
icon_pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
}
else
- g_printerr ("Failed to get icon\n");
+ g_printerr ("Failed to get icon: %s\n", error->message);
}
else {
g_autoptr(SoupMessage) message = NULL;
@@ -244,98 +201,41 @@ refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_sea
}
}
-static gboolean
-get_apps (GsPlugin *plugin,
- const gchar *sources,
- gchar **search_terms,
- GsAppList *list,
- AppFilterFunc filter_func,
- gpointer user_data,
- GCancellable *cancellable,
- GError **error)
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_clear_object (&priv->auth);
+}
+
+gboolean
+gs_plugin_add_installed (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
{
g_autofree gchar *macaroon = NULL;
g_auto(GStrv) discharges = NULL;
- guint status_code;
- GPtrArray *query_fields;
- g_autoptr (GString) path = NULL;
- g_autofree gchar *reason_phrase = NULL, *response_type = NULL, *response = NULL;
- g_autoptr(JsonParser) parser = NULL;
- JsonObject *root;
- JsonArray *result;
- GList *snaps;
- GList *l;
+ g_autoptr(JsonArray) result = NULL;
+ guint i;
get_macaroon (plugin, &macaroon, &discharges);
-
- /* Get all the apps */
- query_fields = g_ptr_array_new_with_free_func (g_free);
- if (sources != NULL) {
- g_autofree gchar *escaped = NULL;
- escaped = soup_uri_encode (sources, NULL);
- g_ptr_array_add (query_fields, g_strdup_printf ("sources=%s", escaped));
- }
- if (search_terms != NULL && search_terms[0] != NULL) {
- g_autoptr (GString) query = NULL;
- g_autofree gchar *escaped = NULL;
- gint i;
-
- query = g_string_new ("q=");
- escaped = soup_uri_encode (search_terms[0], NULL);
- g_string_append (query, escaped);
- for (i = 1; search_terms[i] != NULL; i++) {
- g_autofree gchar *e = soup_uri_encode (search_terms[0], NULL);
- g_string_append_printf (query, "+%s", e);
- }
- g_ptr_array_add (query_fields, g_strdup (query->str));
- path = g_string_new ("/v2/find");
- }
- else
- path = g_string_new ("/v2/snaps");
- g_ptr_array_add (query_fields, NULL);
- if (query_fields->len > 1) {
- g_autofree gchar *fields = NULL;
- g_string_append (path, "?");
- fields = g_strjoinv ("&", (gchar **) query_fields->pdata);
- g_string_append (path, fields);
- }
- g_ptr_array_free (query_fields, TRUE);
- if (!gs_snapd_request ("GET", path->str, NULL,
- macaroon, discharges,
- &status_code, &reason_phrase,
- &response_type, &response, NULL,
- cancellable, error))
+ result = gs_snapd_list (macaroon, discharges, cancellable, error);
+ if (result == NULL)
return FALSE;
- if (status_code != SOUP_STATUS_OK) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned status code %u: %s",
- status_code, reason_phrase);
- return FALSE;
- }
-
- parser = parse_result (response, response_type, error);
- if (parser == NULL)
- return FALSE;
-
- root = json_node_get_object (json_parser_get_root (parser));
- result = json_object_get_array_member (root, "result");
- snaps = json_array_get_elements (result);
-
- for (l = snaps; l != NULL; l = l->next) {
- JsonObject *package = json_node_get_object (l->data);
+ for (i = 0; i < json_array_get_length (result); i++) {
+ JsonObject *package = json_array_get_object_element (result, i);
g_autoptr(GsApp) app = NULL;
- const gchar *id;
-
- id = json_object_get_string_member (package, "name");
+ const gchar *status, *name;
- if (filter_func != NULL && !filter_func (id, package, user_data))
+ status = json_object_get_string_member (package, "status");
+ if (g_strcmp0 (status, "active") != 0)
continue;
/* create a unique ID for deduplication, TODO: branch? */
- app = gs_app_new (id);
+ name = json_object_get_string_member (package, "name");
+ app = gs_app_new (name);
gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_SNAP);
gs_app_set_management_plugin (app, "snap");
@@ -345,98 +245,46 @@ get_apps (GsPlugin *plugin,
gs_app_list_add (list, app);
}
- g_list_free (snaps);
-
return TRUE;
}
-static gboolean
-get_app (GsPlugin *plugin, GsApp *app, GCancellable *cancellable, GError **error)
+gboolean
+gs_plugin_add_search (GsPlugin *plugin,
+ gchar **values,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
{
g_autofree gchar *macaroon = NULL;
g_auto(GStrv) discharges = NULL;
- guint status_code;
- g_autofree gchar *path = NULL;
- g_autofree gchar *reason_phrase = NULL;
- g_autofree gchar *response = NULL;
- g_autofree gchar *response_type = NULL;
- g_autoptr(JsonParser) parser = NULL;
- JsonObject *root;
- JsonObject *result;
+ g_autoptr(JsonArray) result = NULL;
+ guint i;
get_macaroon (plugin, &macaroon, &discharges);
-
- path = g_strdup_printf ("/v2/snaps/%s", gs_app_get_id (app));
- if (!gs_snapd_request ("GET", path, NULL,
- macaroon, discharges,
- &status_code, &reason_phrase,
- &response_type, &response, NULL,
- cancellable, error))
+ result = gs_snapd_find (macaroon, discharges, values, cancellable, error);
+ if (result == NULL)
return FALSE;
- if (status_code == SOUP_STATUS_NOT_FOUND)
- return TRUE;
-
- if (status_code != SOUP_STATUS_OK) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned status code %u: %s",
- status_code, reason_phrase);
- return FALSE;
- }
+ for (i = 0; i < json_array_get_length (result); i++) {
+ JsonObject *package = json_array_get_object_element (result, i);
+ g_autoptr(GsApp) app = NULL;
+ const gchar *name;
- parser = parse_result (response, response_type, error);
- if (parser == NULL)
- return FALSE;
- root = json_node_get_object (json_parser_get_root (parser));
- result = json_object_get_object_member (root, "result");
- if (result == NULL) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned no results for %s", gs_app_get_id (app));
- return FALSE;
+ /* create a unique ID for deduplication, TODO: branch? */
+ name = json_object_get_string_member (package, "name");
+ app = gs_app_new (name);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_SNAP);
+ gs_app_set_management_plugin (app, "snap");
+ gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+ gs_app_add_quirk (app, AS_APP_QUIRK_NOT_REVIEWABLE);
+ refine_app (plugin, app, package, TRUE, cancellable);
+ gs_app_list_add (list, app);
}
- refine_app (plugin, app, result, FALSE, cancellable);
-
return TRUE;
}
-void
-gs_plugin_destroy (GsPlugin *plugin)
-{
- GsPluginData *priv = gs_plugin_get_data (plugin);
- g_clear_object (&priv->auth);
-}
-
-static gboolean
-is_active (const gchar *id, JsonObject *object, gpointer data)
-{
- const gchar *status = json_object_get_string_member (object, "status");
- return g_strcmp0 (status, "active") == 0;
-}
-
-gboolean
-gs_plugin_add_installed (GsPlugin *plugin,
- GsAppList *list,
- GCancellable *cancellable,
- GError **error)
-{
- return get_apps (plugin, "local", NULL, list, is_active, NULL, cancellable, error);
-}
-
-gboolean
-gs_plugin_add_search (GsPlugin *plugin,
- gchar **values,
- GsAppList *list,
- GCancellable *cancellable,
- GError **error)
-{
- return get_apps (plugin, NULL, values, list, NULL, values, cancellable, error);
-}
-
gboolean
gs_plugin_refine_app (GsPlugin *plugin,
GsApp *app,
@@ -444,149 +292,51 @@ gs_plugin_refine_app (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
+ g_autoptr(JsonObject) result = NULL;
+
/* not us */
if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
return TRUE;
- // Get info from snapd
- return get_app (plugin, app, cancellable, error);
-}
-
-static gboolean
-send_package_action (GsPlugin *plugin,
- GsApp *app,
- const gchar *id,
- const gchar *action,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree gchar *macaroon = NULL;
- g_auto(GStrv) discharges = NULL;
- g_autofree gchar *content = NULL, *path = NULL;
- guint status_code;
- g_autofree gchar *reason_phrase = NULL;
- g_autofree gchar *response_type = NULL;
- g_autofree gchar *response = NULL;
- g_autofree gchar *status = NULL;
- g_autoptr(JsonParser) parser = NULL;
- JsonObject *root, *result, *task, *progress;
- JsonArray *tasks;
- GList *task_list, *l;
- gint64 done, total, task_done, task_total;
- const gchar *resource_path;
- const gchar *type;
- const gchar *change_id;
-
get_macaroon (plugin, &macaroon, &discharges);
- content = g_strdup_printf ("{\"action\": \"%s\"}", action);
- path = g_strdup_printf ("/v2/snaps/%s", id);
- if (!gs_snapd_request ("POST", path, content,
- macaroon, discharges,
- &status_code, &reason_phrase,
- &response_type, &response, NULL,
- cancellable, error))
- return FALSE;
-
- if (status_code == SOUP_STATUS_UNAUTHORIZED) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_AUTH_REQUIRED,
- "Requires authentication with @snapd");
+ result = gs_snapd_list_one (macaroon, discharges, gs_app_get_id (app), cancellable, error);
+ if (result == NULL)
return FALSE;
- }
-
- if (status_code != SOUP_STATUS_ACCEPTED) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned status code %u: %s",
- status_code, reason_phrase);
- return FALSE;
- }
-
- parser = parse_result (response, response_type, error);
- if (parser == NULL)
- return FALSE;
-
- root = json_node_get_object (json_parser_get_root (parser));
- type = json_object_get_string_member (root, "type");
-
- if (g_strcmp0 (type, "async") == 0) {
- change_id = json_object_get_string_member (root, "change");
- resource_path = g_strdup_printf ("/v2/changes/%s", change_id);
-
- while (TRUE) {
- g_autofree gchar *status_reason_phrase = NULL;
- g_autofree gchar *status_response_type = NULL;
- g_autofree gchar *status_response = NULL;
- g_autoptr(JsonParser) status_parser = NULL;
-
- /* Wait for a little bit before polling */
- g_usleep (100 * 1000);
-
- if (!gs_snapd_request ("GET", resource_path, NULL,
- macaroon, discharges,
- &status_code, &status_reason_phrase,
- &status_response_type, &status_response, NULL,
- cancellable, error)) {
- return FALSE;
- }
-
- if (status_code != SOUP_STATUS_OK) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd returned status code %u: %s",
- status_code, status_reason_phrase);
- return FALSE;
- }
-
- status_parser = parse_result (status_response, status_response_type, error);
- if (status_parser == NULL)
- return FALSE;
-
- root = json_node_get_object (json_parser_get_root (status_parser));
- result = json_object_get_object_member (root, "result");
-
- g_free (status);
- status = g_strdup (json_object_get_string_member (result, "status"));
-
- if (g_strcmp0 (status, "Done") == 0)
- break;
+ refine_app (plugin, app, result, FALSE, cancellable);
- tasks = json_object_get_array_member (result, "tasks");
- task_list = json_array_get_elements (tasks);
+ return TRUE;
+}
- done = 0;
- total = 0;
+static void
+progress_cb (JsonObject *result, gpointer user_data)
+{
+ GsApp *app = user_data;
+ JsonArray *tasks;
+ GList *task_list, *l;
+ gint64 done = 0, total = 0;
- for (l = task_list; l != NULL; l = l->next) {
- task = json_node_get_object (l->data);
- progress = json_object_get_object_member (task, "progress");
- task_done = json_object_get_int_member (progress, "done");
- task_total = json_object_get_int_member (progress, "total");
+ tasks = json_object_get_array_member (result, "tasks");
+ task_list = json_array_get_elements (tasks);
- done += task_done;
- total += task_total;
- }
+ for (l = task_list; l != NULL; l = l->next) {
+ JsonObject *task, *progress;
+ gint64 task_done, task_total;
- if (total > 0)
- gs_app_set_progress (app, (guint) (100 * done / total));
+ task = json_node_get_object (l->data);
+ progress = json_object_get_object_member (task, "progress");
+ task_done = json_object_get_int_member (progress, "done");
+ task_total = json_object_get_int_member (progress, "total");
- g_list_free (task_list);
- }
+ done += task_done;
+ total += task_total;
}
- if (g_strcmp0 (status, "Done") != 0) {
- g_set_error (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_NOT_SUPPORTED,
- "snapd operation finished with status %s", status);
- return FALSE;
- }
+ gs_app_set_progress (app, (guint) (100 * done / total));
- return TRUE;
+ g_list_free (task_list);
}
gboolean
@@ -595,15 +345,18 @@ gs_plugin_app_install (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
- gboolean ret;
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
/* We can only install apps we know of */
if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
return TRUE;
+ get_macaroon (plugin, &macaroon, &discharges);
+
gs_app_set_state (app, AS_APP_STATE_INSTALLING);
- ret = send_package_action (plugin, app, gs_app_get_id (app), "install", cancellable, error);
- if (!ret) {
+ get_macaroon (plugin, &macaroon, &discharges);
+ if (!gs_snapd_install (macaroon, discharges, gs_app_get_id (app), progress_cb, app, cancellable,
error)) {
gs_app_set_state_recover (app);
return FALSE;
}
@@ -649,15 +402,17 @@ gs_plugin_app_remove (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
- gboolean ret;
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
/* We can only remove apps we know of */
if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
return TRUE;
+ get_macaroon (plugin, &macaroon, &discharges);
+
gs_app_set_state (app, AS_APP_STATE_REMOVING);
- ret = send_package_action (plugin, app, gs_app_get_id (app), "remove", cancellable, error);
- if (!ret) {
+ if (!gs_snapd_remove (macaroon, discharges, gs_app_get_id (app), progress_cb, app, cancellable,
error)) {
gs_app_set_state_recover (app);
return FALSE;
}
@@ -670,105 +425,24 @@ gs_plugin_auth_login (GsPlugin *plugin, GsAuth *auth,
GCancellable *cancellable, GError **error)
{
GsPluginData *priv = gs_plugin_get_data (plugin);
- g_autoptr(JsonBuilder) builder = NULL;
- g_autoptr(JsonNode) json_root = NULL;
- g_autoptr(JsonGenerator) json_generator = NULL;
- g_autofree gchar *data = NULL;
- guint status_code;
- g_autofree gchar *reason_phrase = NULL;
- g_autofree gchar *response_type = NULL;
- g_autofree gchar *response = NULL;
- g_autoptr(JsonObject) result = NULL;
- JsonArray *discharges;
- guint i;
- g_autoptr(GVariantBuilder) b = NULL;
+ g_autofree gchar *macaroon = NULL;
+ g_auto(GStrv) discharges = NULL;
g_autoptr(GVariant) macaroon_variant = NULL;
g_autofree gchar *serialized_macaroon = NULL;
+ g_autoptr(GError) local_error = NULL;
if (auth != priv->auth)
return TRUE;
- builder = json_builder_new ();
- json_builder_begin_object (builder);
- json_builder_set_member_name (builder, "username");
- json_builder_add_string_value (builder, gs_auth_get_username (auth));
- json_builder_set_member_name (builder, "password");
- json_builder_add_string_value (builder, gs_auth_get_password (auth));
- if (gs_auth_get_pin (auth)) {
- json_builder_set_member_name (builder, "otp");
- json_builder_add_string_value (builder, gs_auth_get_pin (auth));
- }
- json_builder_end_object (builder);
-
- json_root = json_builder_get_root (builder);
- json_generator = json_generator_new ();
- json_generator_set_pretty (json_generator, TRUE);
- json_generator_set_root (json_generator, json_root);
- data = json_generator_to_data (json_generator, NULL);
- if (data == NULL) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "Failed to generate JSON request");
+ if (!gs_snapd_login (gs_auth_get_username (auth),
+ gs_auth_get_password (auth),
+ gs_auth_get_pin (auth),
+ &macaroon,
+ &discharges,
+ cancellable, &local_error))
return FALSE;
- }
- if (!gs_snapd_request ("POST", "/v2/login", data,
- NULL, NULL,
- &status_code, &reason_phrase,
- &response_type, &response, NULL,
- cancellable, error))
- return FALSE;
-
- if (status_code != SOUP_STATUS_OK) {
- g_autofree gchar *error_message = NULL;
- g_autofree gchar *error_kind = NULL;
-
- if (!gs_snapd_parse_error (response_type, response, &error_message, &error_kind, error))
- return FALSE;
-
- if (g_strcmp0 (error_kind, "two-factor-required") == 0) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_PIN_REQUIRED,
- error_message);
- }
- else {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_NOT_SUPPORTED,
- error_message);
- }
- return FALSE;
- }
-
- if (!gs_snapd_parse_result (response_type, response, &result, error))
- return FALSE;
-
- if (!json_object_has_member (result, "macaroon")) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_AUTH_INVALID,
- "Login response missing macaroon");
- return FALSE;
- }
- discharges = json_object_get_array_member (result, "discharges");
- b = g_variant_builder_new (G_VARIANT_TYPE ("as"));
- for (i = 0; i < json_array_get_length (discharges); i++) {
- JsonNode *node;
- node = json_array_get_element (discharges, i);
- if (!JSON_NODE_HOLDS_VALUE (node) && json_node_get_value_type (node) != G_TYPE_STRING) {
- g_set_error_literal (error,
- GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_AUTH_INVALID,
- "Macaroon discharge contains unexpected value");
- return FALSE;
- }
- g_variant_builder_add (b, "s", json_node_get_string (node));
- }
- macaroon_variant = g_variant_new ("(sas)",
- json_object_get_string_member (result, "macaroon"),
- b);
+ macaroon_variant = g_variant_new ("(s^as)", macaroon, discharges);
serialized_macaroon = g_variant_print (macaroon_variant, FALSE);
gs_auth_add_metadata (auth, "macaroon", serialized_macaroon);
diff --git a/src/plugins/gs-snapd.c b/src/plugins/gs-snapd.c
index 808ba55..7b07413 100644
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@ -93,19 +93,19 @@ read_from_snapd (GSocket *socket,
return TRUE;
}
-gboolean
-gs_snapd_request (const gchar *method,
- const gchar *path,
- const gchar *content,
- const gchar *macaroon,
- gchar **discharges,
- guint *status_code,
- gchar **reason_phrase,
- gchar **response_type,
- gchar **response,
- gsize *response_length,
- GCancellable *cancellable,
- GError **error)
+static gboolean
+send_request (const gchar *method,
+ const gchar *path,
+ const gchar *content,
+ const gchar *macaroon,
+ gchar **discharges,
+ guint *status_code,
+ gchar **reason_phrase,
+ gchar **response_type,
+ gchar **response,
+ gsize *response_length,
+ GCancellable *cancellable,
+ GError **error)
{
g_autoptr (GSocket) socket = NULL;
g_autoptr (GString) request = NULL;
@@ -303,29 +303,25 @@ gs_snapd_request (const gchar *method,
return TRUE;
}
-gboolean
-gs_snapd_parse_result (const gchar *response_type,
- const gchar *response,
- JsonObject **result,
- GError **error)
+static JsonParser *
+parse_result (const gchar *response, const gchar *response_type, GError **error)
{
g_autoptr(JsonParser) parser = NULL;
g_autoptr(GError) error_local = NULL;
- JsonObject *root;
if (response_type == NULL) {
g_set_error_literal (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_INVALID_FORMAT,
"snapd returned no content type");
- return FALSE;
+ return NULL;
}
if (g_strcmp0 (response_type, "application/json") != 0) {
g_set_error (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_INVALID_FORMAT,
"snapd returned unexpected content type %s", response_type);
- return FALSE;
+ return NULL;
}
parser = json_parser_new ();
@@ -335,46 +331,490 @@ gs_snapd_parse_result (const gchar *response_type,
GS_PLUGIN_ERROR_INVALID_FORMAT,
"Unable to parse snapd response: %s",
error_local->message);
- return FALSE;
+ return NULL;
}
-
if (!JSON_NODE_HOLDS_OBJECT (json_parser_get_root (parser))) {
g_set_error_literal (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_INVALID_FORMAT,
"snapd response does is not a valid JSON object");
+ return NULL;
+ }
+
+ return g_object_ref (parser);
+}
+
+gboolean
+gs_snapd_login (const gchar *username, const gchar *password, const gchar *otp,
+ gchar **macaroon, gchar ***discharges,
+ GCancellable *cancellable, GError **error)
+{
+ g_autofree gchar *escaped_username = NULL;
+ g_autofree gchar *escaped_password = NULL;
+ g_autofree gchar *escaped_otp = NULL;
+ g_autofree gchar *content = NULL;
+ guint status_code;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root, *result;
+ const gchar *type;
+ g_autoptr(JsonArray) discharge_json_array = NULL;
+ g_autoptr(GPtrArray) discharge_array = NULL;
+ guint i;
+
+ escaped_username = g_strescape (username, NULL);
+ escaped_password = g_strescape (password, NULL);
+
+ if (otp != NULL) {
+ escaped_otp = g_strescape (otp, NULL);
+
+ content = g_strdup_printf ("{"
+ " \"username\" : \"%s\","
+ " \"password\" : \"%s\","
+ " \"otp\" : \"%s\""
+ "}",
+ escaped_username,
+ escaped_password,
+ escaped_otp);
+ } else {
+ content = g_strdup_printf ("{"
+ " \"username\" : \"%s\","
+ " \"password\" : \"%s\""
+ "}",
+ escaped_username,
+ escaped_password);
+ }
+
+ if (!send_request ("POST", "/v2/login", content,
+ NULL, NULL,
+ &status_code, NULL,
+ &response_type, &response, NULL,
+ cancellable, error))
return FALSE;
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return FALSE;
+ root = json_node_get_object (json_parser_get_root (parser));
+ type = json_object_get_string_member (root, "type");
+ if (g_strcmp0 (type, "error") == 0) {
+ const gchar *kind, *message;
+
+ kind = json_object_get_string_member (root, "kind");
+ message = json_object_get_string_member (root, "message");
+ if (g_strcmp0 (kind, "invalid-auth-data") == 0 ||
+ g_strcmp0 (kind, "two-factor-failed") == 0)
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_AUTH_INVALID,
+ message);
+ else if (g_strcmp0 (kind, "two-factor-required") == 0)
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_PIN_REQUIRED,
+ message);
+ else
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ message);
+
+ return FALSE;
+ }
+
+ result = json_object_get_object_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "snapd returned no login result");
+ return FALSE;
+ }
+ discharge_json_array = json_object_get_array_member (result, "discharges");
+ discharge_array = g_ptr_array_new ();
+ for (i = 0; i < json_array_get_length (discharge_json_array); i++) {
+ JsonNode *node = json_array_get_element (discharge_json_array, i);
+
+ if (json_node_get_value_type (node) != G_TYPE_STRING) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "Unexpected discharge type");
+ return FALSE;
+ }
+
+ g_ptr_array_add (discharge_array, (gpointer) json_node_get_string (node));
+ }
+ g_ptr_array_add (discharge_array, NULL);
+ *macaroon = g_strdup (json_object_get_string_member (result, "macaroon"));
+ *discharges = g_strdupv ((gchar **) discharge_array->pdata);
+
+ return TRUE;
+}
+
+JsonObject *
+gs_snapd_list_one (const gchar *macaroon, gchar **discharges,
+ const gchar *name,
+ GCancellable *cancellable, GError **error)
+{
+ g_autofree gchar *path = NULL;
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root, *result;
+
+ path = g_strdup_printf ("/v2/snaps/%s", name);
+ if (!send_request ("GET", path, NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return NULL;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
+ }
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return NULL;
+ root = json_node_get_object (json_parser_get_root (parser));
+ result = json_object_get_object_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "snapd returned no results for %s", name);
+ return NULL;
+ }
+
+ return json_object_ref (result);
+}
+
+JsonArray *
+gs_snapd_list (const gchar *macaroon, gchar **discharges,
+ GCancellable *cancellable, GError **error)
+{
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root;
+ JsonArray *result;
+
+ if (!send_request ("GET", "/v2/snaps", NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return NULL;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
+ }
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return NULL;
+ root = json_node_get_object (json_parser_get_root (parser));
+ result = json_object_get_array_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned no result");
+ return NULL;
+ }
+
+ return json_array_ref (result);
+}
+
+JsonArray *
+gs_snapd_find (const gchar *macaroon, gchar **discharges,
+ gchar **values,
+ GCancellable *cancellable, GError **error)
+{
+ g_autoptr(GString) path = NULL;
+ g_autofree gchar *query = NULL;
+ g_autofree gchar *escaped = NULL;
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root;
+ JsonArray *result;
+
+ path = g_string_new ("/v2/find?q=");
+ query = g_strjoinv (" ", values);
+ escaped = soup_uri_encode (query, NULL);
+ g_string_append (path, escaped);
+ if (!send_request ("GET", path->str, NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return NULL;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
}
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return NULL;
+ root = json_node_get_object (json_parser_get_root (parser));
+ result = json_object_get_array_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned no result");
+ return NULL;
+ }
+
+ return json_array_ref (result);
+}
+
+JsonObject *
+gs_snapd_get_interfaces (const gchar *macaroon, gchar **discharges, GCancellable *cancellable, GError
**error)
+{
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root;
+ JsonObject *result;
+
+ if (!send_request ("GET", "/v2/interfaces", NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return NULL;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
+ }
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return NULL;
root = json_node_get_object (json_parser_get_root (parser));
- if (!json_object_has_member (root, "result")) {
+ result = json_object_get_object_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned no result");
+ return NULL;
+ }
+
+ return json_object_ref (result);
+}
+
+static JsonObject *
+get_changes (const gchar *macaroon, gchar **discharges,
+ const gchar *change_id,
+ GCancellable *cancellable, GError **error)
+{
+ g_autofree gchar *path = NULL;
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root, *result;
+
+ path = g_strdup_printf ("/v2/changes/%s", change_id);
+ if (!send_request ("GET", path, NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return NULL;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
+ }
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return NULL;
+ root = json_node_get_object (json_parser_get_root (parser));
+ result = json_object_get_object_member (root, "result");
+ if (result == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned no result");
+ return NULL;
+ }
+
+ return json_object_ref (result);
+}
+
+static gboolean
+send_package_action (const gchar *macaroon,
+ gchar **discharges,
+ const gchar *name,
+ const gchar *action,
+ GsSnapdProgressCallback callback,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *content = NULL, *path = NULL;
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *response = NULL;
+ g_autofree gchar *status = NULL;
+ g_autoptr(JsonParser) parser = NULL;
+ JsonObject *root, *result;
+ const gchar *type;
+
+ content = g_strdup_printf ("{\"action\": \"%s\"}", action);
+ path = g_strdup_printf ("/v2/snaps/%s", name);
+ if (!send_request ("POST", path, content,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ &response_type, &response, NULL,
+ cancellable, error))
+ return FALSE;
+
+ if (status_code == SOUP_STATUS_UNAUTHORIZED) {
g_set_error_literal (error,
GS_PLUGIN_ERROR,
- GS_PLUGIN_ERROR_INVALID_FORMAT,
- "snapd response does not contain a \"result\" field");
+ GS_PLUGIN_ERROR_AUTH_REQUIRED,
+ "Requires authentication with @snapd");
+ return FALSE;
+ }
+
+ if (status_code != SOUP_STATUS_ACCEPTED) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return FALSE;
+ }
+
+ parser = parse_result (response, response_type, error);
+ if (parser == NULL)
+ return FALSE;
+
+ root = json_node_get_object (json_parser_get_root (parser));
+ type = json_object_get_string_member (root, "type");
+
+ if (g_strcmp0 (type, "async") == 0) {
+ const gchar *change_id;
+
+ change_id = json_object_get_string_member (root, "change");
+
+ while (TRUE) {
+ /* Wait for a little bit before polling */
+ g_usleep (100 * 1000);
+
+ result = get_changes (macaroon, discharges, change_id, cancellable, error);
+ if (result == NULL)
+ return FALSE;
+
+ status = g_strdup (json_object_get_string_member (result, "status"));
+
+ if (g_strcmp0 (status, "Done") == 0)
+ break;
+
+ callback (result, user_data);
+ }
+ }
+
+ if (g_strcmp0 (status, "Done") != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "snapd operation finished with status %s", status);
return FALSE;
}
- if (result != NULL)
- *result = json_object_ref (json_object_get_object_member (root, "result"));
return TRUE;
}
gboolean
-gs_snapd_parse_error (const gchar *response_type,
- const gchar *response,
- gchar **message,
- gchar **kind,
- GError **error)
+gs_snapd_install (const gchar *macaroon, gchar **discharges,
+ const gchar *name,
+ GsSnapdProgressCallback callback, gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return send_package_action (macaroon, discharges, name, "install", callback, user_data, cancellable,
error);
+}
+
+gboolean
+gs_snapd_remove (const gchar *macaroon, gchar **discharges,
+ const gchar *name,
+ GsSnapdProgressCallback callback, gpointer user_data,
+ GCancellable *cancellable, GError **error)
{
- g_autoptr(JsonObject) result = NULL;
+ return send_package_action (macaroon, discharges, name, "remove", callback, user_data, cancellable,
error);
+}
- if (!gs_snapd_parse_result (response_type, response, &result, error))
- return FALSE;
+gchar *
+gs_snapd_get_resource (const gchar *macaroon, gchar **discharges,
+ const gchar *path,
+ gsize *data_length,
+ GCancellable *cancellable, GError **error)
+{
+ guint status_code;
+ g_autofree gchar *reason_phrase = NULL;
+ g_autofree gchar *response_type = NULL;
+ g_autofree gchar *data = NULL;
+
+ if (!send_request ("GET", path, NULL,
+ macaroon, discharges,
+ &status_code, &reason_phrase,
+ NULL, &data, data_length,
+ cancellable, error))
+ return NULL;
- if (message != NULL)
- *message = g_strdup (json_object_get_string_member (result, "message"));
- if (kind != NULL)
- *kind = json_object_has_member (result, "kind") ? g_strdup (json_object_get_string_member
(result, "kind")) : NULL;
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "snapd returned status code %u: %s",
+ status_code, reason_phrase);
+ return NULL;
+ }
- return TRUE;
+ return g_steal_pointer (&data);
}
diff --git a/src/plugins/gs-snapd.h b/src/plugins/gs-snapd.h
index 7c0eb9c..c5e80ff 100644
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@ -25,31 +25,61 @@
#include <gio/gio.h>
#include <json-glib/json-glib.h>
-gboolean gs_snapd_exists (void);
-
-gboolean gs_snapd_request (const gchar *method,
- const gchar *path,
- const gchar *content,
- const gchar *macaroon,
- gchar **discharges,
- guint *status_code,
- gchar **reason_phrase,
- gchar **response_type,
- gchar **response,
- gsize *response_length,
- GCancellable *cancellable,
- GError **error);
-
-gboolean gs_snapd_parse_result (const gchar *response_type,
- const gchar *response,
- JsonObject **result,
- GError **error);
-
-gboolean gs_snapd_parse_error (const gchar *response_type,
- const gchar *response,
- gchar **message,
- gchar **kind,
- GError **error);
+typedef void (*GsSnapdProgressCallback) (JsonObject *object, gpointer user_data);
+gboolean gs_snapd_exists (void);
+
+gboolean gs_snapd_login (const gchar *username,
+ const gchar *password,
+ const gchar *otp,
+ gchar **macaroon,
+ gchar ***discharges,
+ GCancellable *cancellable,
+ GError **error);
+
+JsonObject *gs_snapd_list_one (const gchar *macaroon,
+ gchar **discharges,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error);
+
+JsonArray *gs_snapd_list (const gchar *macaroon,
+ gchar **discharges,
+ GCancellable *cancellable,
+ GError **error);
+
+JsonArray *gs_snapd_find (const gchar *macaroon,
+ gchar **discharges,
+ gchar **values,
+ GCancellable *cancellable,
+ GError **error);
+
+JsonObject *gs_snapd_get_interfaces (const gchar *macaroon,
+ gchar **discharges,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_snapd_install (const gchar *macaroon,
+ gchar **discharges,
+ const gchar *name,
+ GsSnapdProgressCallback callback,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_snapd_remove (const gchar *macaroon,
+ gchar **discharges,
+ const gchar *name,
+ GsSnapdProgressCallback callback,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar *gs_snapd_get_resource (const gchar *macaroon,
+ gchar **discharges,
+ const gchar *path,
+ gsize *data_length,
+ GCancellable *cancellable,
+ GError **error);
#endif /* __GS_SNAPD_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]