[gnome-software/wip/iainl/ubuntu-xenial: 3/56] Actually access snapd for packages



commit f9ae67473cb72572957855d7106d333046615301
Author: Robert Ancell <robert ancell canonical com>
Date:   Thu Jan 21 20:56:13 2016 +1300

    Actually access snapd for packages

 configure.ac                   |    1 +
 src/plugins/Makefile.am        |    3 +-
 src/plugins/gs-plugin-snappy.c |  283 +++++++++++++++++++++++++++++++++-------
 3 files changed, 241 insertions(+), 46 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0b90b36..6666264 100644
--- a/configure.ac
+++ b/configure.ac
@@ -67,6 +67,7 @@ PKG_CHECK_MODULES(SQLITE, sqlite3)
 PKG_CHECK_MODULES(SOUP, libsoup-2.4 >= 2.51.92)
 PKG_CHECK_MODULES(GSETTINGS_DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.11.5)
 PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0 >= 3.17.92)
+PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0 >= 0.12)
 AC_PATH_PROG(APPSTREAM_UTIL, [appstream-util], [unfound])
 AC_ARG_ENABLE(man,
               [AS_HELP_STRING([--enable-man],
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 2831c74..48f63dc 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -12,6 +12,7 @@ AM_CPPFLAGS =                                         \
        $(FWUPD_CFLAGS)                                 \
        $(LIMBA_CFLAGS)                                 \
        $(XDG_APP_CFLAGS)                               \
+       $(JSON_GLIB_CFLAGS)                             \
        -DBINDIR=\"$(bindir)\"                          \
        -DDATADIR=\"$(datadir)\"                        \
        -DGS_MODULESETDIR=\"$(datadir)/gnome-software/modulesets.d\" \
@@ -223,7 +224,7 @@ libgs_plugin_packagekit_proxy_la_LDFLAGS = -module -avoid-version
 libgs_plugin_packagekit_proxy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 
 libgs_plugin_snappy_la_SOURCES = gs-plugin-snappy.c
-libgs_plugin_snappy_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_snappy_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS)
 libgs_plugin_snappy_la_LDFLAGS = -module -avoid-version
 libgs_plugin_snappy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 
diff --git a/src/plugins/gs-plugin-snappy.c b/src/plugins/gs-plugin-snappy.c
index e142e95..047033b 100644
--- a/src/plugins/gs-plugin-snappy.c
+++ b/src/plugins/gs-plugin-snappy.c
@@ -21,43 +21,214 @@
 
 #include <config.h>
 
-#include <glib/gi18n.h>
-
+#include <stdlib.h>
+#include <string.h>
 #include <gs-plugin.h>
+#include <gio/gunixsocketaddress.h>
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
 
 struct GsPluginPrivate {
+       GSocket *snapd_socket;
 };
 
-/**
- * gs_plugin_get_name:
- */
+#define SNAPD_SOCKET_PATH "/run/snapd.socket"
+
+typedef gboolean (*AppFilterFunc)(const gchar *id, JsonObject *object);
+
 const gchar *
 gs_plugin_get_name (void)
 {
        return "snappy";
 }
 
-/**
- * gs_plugin_initialize:
- */
 void
 gs_plugin_initialize (GsPlugin *plugin)
 {
+       g_autoptr (GSocketAddress) address = NULL;
+       GError *error = NULL;
+
        /* create private area */
        plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+
+       /* Create socket to snapd */
+       plugin->priv->snapd_socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, 
G_SOCKET_PROTOCOL_DEFAULT, &error); // FIXME: Handle error
+       address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
+       g_socket_connect (plugin->priv->snapd_socket, address, NULL, &error); // FIXME: Handle error
+}
+
+static gboolean
+read_from_snapd (GsPlugin *plugin, gchar *buffer, gsize buffer_length, gsize *read_offset, GError **error)
+{
+       gssize n_read;
+       n_read = g_socket_receive (plugin->priv->snapd_socket, buffer + *read_offset, buffer_length - 
*read_offset, NULL, error);
+       if (n_read < 0)
+               return FALSE;
+       *read_offset += n_read;
+       buffer[*read_offset] = '\0';
+
+       return TRUE;
+}
+
+static gboolean
+send_snapd_request (GsPlugin *plugin, const gchar *request, guint *status_code, gchar **response_type, gchar 
**response, gsize *response_length, GError **error)
+{
+       gsize max_data_length = 65535, data_length = 0, header_length;
+       gchar data[max_data_length + 1], *body = NULL;
+       g_autoptr (SoupMessageHeaders) headers = NULL;
+       g_autofree gchar *reason_phrase = NULL;
+       gsize chunk_length, n_required;
+       gchar *chunk_start = NULL;
+
+       // NOTE: Would love to use libsoup but it doesn't support unix sockets
+       // https://bugzilla.gnome.org/show_bug.cgi?id=727563
+
+       /* Send HTTP request */
+       gssize n_written;
+       n_written = g_socket_send (plugin->priv->snapd_socket, request, strlen (request), NULL, error);
+       if (n_written < 0)
+               return FALSE;
+
+       /* Read HTTP headers */
+       while (data_length < max_data_length && !body) {
+               if (!read_from_snapd (plugin, data, max_data_length, &data_length, error))
+                       return FALSE;
+               body = strstr (data, "\r\n\r\n");
+       }
+       if (!body)
+               return FALSE; // FIXME: Set error
+
+       /* Body starts after header divider */
+       body += 4;
+       header_length = body - data;
+
+       /* Parse headers */
+       headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+       if (!soup_headers_parse_response (data, header_length, headers, NULL, status_code, &reason_phrase))
+               return FALSE; // FIXME: Set error
+
+       /* Work out how much data to follow */
+       if (g_strcmp0 (soup_message_headers_get_one (headers, "Transfer-Encoding"), "chunked") == 0) {
+               while (data_length < max_data_length) {
+                       chunk_start = strstr (body, "\r\n");
+                       if (chunk_start)
+                               break;
+                       if (!read_from_snapd (plugin, data, max_data_length, &data_length, error))
+                               return FALSE;
+               }
+               if (!chunk_start)
+                       return FALSE; // FIXME: Set error
+               chunk_length = strtoul (body, NULL, 16);
+               chunk_start += 2;
+               // FIXME: Support multiple chunks
+       }
+       else {
+               const gchar *value;
+               value = soup_message_headers_get_one (headers, "Content-Length");
+               if (!value)
+                       return FALSE; // FIXME: Set error
+               chunk_length = atoi (value);
+               chunk_start = body;
+       }
+
+       /* Check if enough space to read chunk */
+       n_required = (chunk_start - data) + chunk_length;
+       if (n_required > max_data_length)
+               return FALSE; // FIXME: Set error
+
+       /* Read chunk content */
+       while (data_length < n_required)
+               if (!read_from_snapd (plugin, data, n_required - data_length, &data_length, error))
+                       return FALSE;
+
+       if (response_type)
+               *response_type = g_strdup (soup_message_headers_get_one (headers, "Content-Type"));
+       if (response) {
+               *response = g_malloc (chunk_length + 1);
+               memcpy (*response, chunk_start, chunk_length + 1);
+       }
+       if (response_length)
+               *response_length = chunk_length;
+
+       return TRUE;
+}
+
+static gboolean
+get_apps (GsPlugin *plugin, GList **list, AppFilterFunc filter_func, GError **error)
+{
+       guint status_code;
+       g_autofree gchar *response_type = NULL, *response = NULL;
+       JsonParser *parser;
+       JsonObject *root, *result, *packages;
+       GList *package_list, *link;
+
+       /* Get all the apps */
+       if (!send_snapd_request (plugin, "GET /1.0/packages HTTP/1.1\n\n", &status_code, &response_type, 
&response, NULL, error))
+               return FALSE;
+
+       if (status_code != 200)
+               return FALSE; // FIXME: Set error
+
+       if (g_strcmp0 (response_type, "application/json") != 0)
+               return FALSE; // FIXME: Set error
+
+       parser = json_parser_new ();
+       if (!json_parser_load_from_data (parser, response, -1, NULL))
+               return 1;
+       if (!JSON_NODE_HOLDS_OBJECT (json_parser_get_root (parser)))
+               return 1;
+       root = json_node_get_object (json_parser_get_root (parser));
+       result = json_object_get_object_member (root, "result");
+       packages = json_object_get_object_member (result, "packages");
+       package_list = json_object_get_members (packages);
+       for (link = package_list; link; link = link->next) {
+               const gchar *id = link->data;
+               JsonObject *package;
+               const gchar *status;
+               g_autoptr(GsApp) app = NULL;
+
+               package = json_object_get_object_member (packages, id);
+               if (!filter_func (id, package))
+                       continue;
+
+               app = gs_app_new (id);
+               gs_app_set_management_plugin (app, "snappy");
+               gs_app_set_kind (app, GS_APP_KIND_NORMAL);
+               status = json_object_get_string_member (package, "status");
+               if (g_strcmp0 (status, "installed") == 0 || g_strcmp0 (status, "active") == 0) {
+                       const gchar *update_available;
+                       update_available = json_object_get_string_member (package, "update_available");
+                       if (update_available)
+                               gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
+                       else
+                               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               }
+               else if (g_strcmp0 (status, "not installed") == 0)
+                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+               gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, json_object_get_string_member (package, 
"name"));
+               gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, json_object_get_string_member (package, 
"description"));
+               gs_app_set_version (app, json_object_get_string_member (package, "version"));
+               gs_plugin_add_app (list, app);
+               g_printerr ("SNAPPY: +%s\n", gs_app_to_string (app));
+       }
+       g_object_unref (parser);
+
+       return TRUE;
 }
 
-/**
- * gs_plugin_destroy:
- */
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
+       g_object_unref (plugin->priv->snapd_socket);
+}
+
+static gboolean
+is_installed (const gchar *id, JsonObject *object)
+{
+       const gchar *status = json_object_get_string_member (object, "status");
+       return status && (strcmp (status, "installed") == 0 || strcmp (status, "active") == 0);
 }
 
-/**
- * gs_plugin_add_installed:
- */
 gboolean
 gs_plugin_add_installed (GsPlugin *plugin,
                         GList **list,
@@ -65,25 +236,24 @@ gs_plugin_add_installed (GsPlugin *plugin,
                         GError **error)
 {
        g_printerr ("SNAPPY: gs_plugin_add_installed\n");
-       return FALSE;
+       return get_apps (plugin, list, is_installed, error);
 }
 
-/**
- * gs_plugin_add_sources:
- */
 gboolean
-gs_plugin_add_sources (GsPlugin *plugin,
-                      GList **list,
-                      GCancellable *cancellable,
-                      GError **error)
+gs_plugin_add_search (GsPlugin *plugin,
+                     gchar **values,
+                     GList **list,
+                     GCancellable *cancellable,
+                     GError **error)
 {
-       g_printerr ("SNAPPY: gs_plugin_add_sources\n");
-       return FALSE;
+       gint i;
+       g_printerr ("SNAPPY: gs_plugin_add_search (%d)\n", g_strv_length (values));
+       for (i = 0; values[i]; i++)
+               g_printerr ("  %s\n", values[i]);
+       //gs_plugin_add_app (list, app);
+       return TRUE;
 }
 
-/**
- * gs_plugin_app_install:
- */
 gboolean
 gs_plugin_app_install (GsPlugin *plugin,
                       GsApp *app,
@@ -91,12 +261,14 @@ gs_plugin_app_install (GsPlugin *plugin,
                       GError **error)
 {
        g_printerr ("SNAPPY: gs_plugin_app_install\n");
-       return FALSE;
+
+       /* We can only install apps we know of */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "snappy") != 0)
+               return TRUE;
+
+       return TRUE;
 }
 
-/**
- * gs_plugin_app_remove:
- */
 gboolean
 gs_plugin_app_remove (GsPlugin *plugin,
                      GsApp *app,
@@ -104,23 +276,25 @@ gs_plugin_app_remove (GsPlugin *plugin,
                      GError **error)
 {
        g_printerr ("SNAPPY: gs_plugin_app_remove\n");
-       return FALSE;
+
+       /* We can only remove apps we know of */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "snappy") != 0)
+               return TRUE;
+
+       return TRUE;
 }
 
+#if 0
 gboolean
-gs_plugin_add_search (GsPlugin *plugin,
-                     gchar **values,
-                     GList **list,
-                     GCancellable *cancellable,
-                     GError **error)
+gs_plugin_add_sources (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
 {
-       g_printerr ("SNAPPY: gs_plugin_add_search (%d)\n", g_strv_length (values));
-       return TRUE;
+       g_printerr ("SNAPPY: gs_plugin_add_sources\n");
+       return FALSE;
 }
 
-/**
- * gs_plugin_add_search_files:
- */
 gboolean
 gs_plugin_add_search_files (GsPlugin *plugin,
                             gchar **search,
@@ -132,9 +306,6 @@ gs_plugin_add_search_files (GsPlugin *plugin,
        return FALSE;
 }
 
-/**
- * gs_plugin_add_search_what_provides:
- */
 gboolean
 gs_plugin_add_search_what_provides (GsPlugin *plugin,
                                     gchar **search,
@@ -145,3 +316,25 @@ gs_plugin_add_search_what_provides (GsPlugin *plugin,
        g_printerr ("SNAPPY: gs_plugin_add_search_what_provides\n");
        return FALSE;
 }
+
+gboolean
+gs_plugin_add_categories (GsPlugin *plugin,
+                         GList **list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       g_printerr ("SNAPPY: gs_plugin_add_categories\n");
+       return FALSE;
+}
+
+gboolean
+gs_plugin_add_category_apps (GsPlugin *plugin,
+                            GsCategory *category,
+                            GList **list,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       g_printerr ("SNAPPY: gs_plugin_add_category_apps\n");
+       return FALSE;
+}
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]