[gnome-software/wip/hughsie/shell-extensions] f



commit dffba7750c7bcae3c7b6000b4bb4c6572f1ad9b2
Author: Richard Hughes <richard hughsie com>
Date:   Thu Feb 25 16:03:34 2016 +0000

    f

 src/plugins/gs-plugin-appstream.c        |    4 +
 src/plugins/gs-plugin-shell-extensions.c |  511 +++++++++++++++++++++++++-----
 2 files changed, 439 insertions(+), 76 deletions(-)
---
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index f1e063a..659ab99 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -544,6 +544,10 @@ gs_plugin_refine_item_management_plugin (GsApp *app, AsApp *item)
                }
        }
 
+       /* sneaky bodge for shell extensions */
+       if (management_plugin == NULL)
+               management_plugin = as_app_get_metadata_item (item, "ManagementPlugin");
+
        /* fall back to PackageKit */
        if (management_plugin == NULL &&
            as_app_get_pkgname_default (item) != NULL)
diff --git a/src/plugins/gs-plugin-shell-extensions.c b/src/plugins/gs-plugin-shell-extensions.c
index dcecc6c..62dcc43 100644
--- a/src/plugins/gs-plugin-shell-extensions.c
+++ b/src/plugins/gs-plugin-shell-extensions.c
@@ -28,8 +28,7 @@
 #include <gs-os-release.h>
 #include <gs-utils.h>
 
-#define SHELL_EXTENSIONS_CACHE_AGE_MAX         237000 /* 1 week */
-#define SHELL_EXTENSIONS_API_URI               "https://extensions.gnome.org/extension-query/";
+#define SHELL_EXTENSIONS_API_URI               "https://extensions.gnome.org/";
 
 // See https://git.gnome.org/browse/extensions-web/tree/sweettooth/extensions/views.py for src
 
@@ -75,7 +74,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(JsonParser, g_object_unref)
 #endif
 
 struct GsPluginPrivate {
-       GPtrArray       *distros;
+       GDBusProxy      *proxy;
+       gchar           *shell_version;
 };
 
 /**
@@ -88,17 +88,280 @@ gs_plugin_get_name (void)
 }
 
 /**
+ * gs_plugin_initialize:
+ */
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+       plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+}
+
+/**
+ * gs_plugin_destroy:
+ */
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+       g_free (plugin->priv->shell_version);
+       if (plugin->priv->proxy != NULL)
+               g_object_unref (plugin->priv->proxy);
+}
+
+/**
+ * gs_plugin_setup:
+ */
+static gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+       g_autoptr(GVariant) version = NULL;
+
+       if (plugin->priv->proxy != NULL)
+               return TRUE;
+       plugin->priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                            G_DBUS_PROXY_FLAGS_NONE,
+                                                            NULL,
+                                                            "org.gnome.Shell",
+                                                            "/org/gnome/Shell",
+                                                            "org.gnome.Shell.Extensions",
+                                                            cancellable,
+                                                            error);
+
+       /* get the GNOME Shell version */
+       version = g_dbus_proxy_get_cached_property (plugin->priv->proxy,
+                                                   "ShellVersion");
+       if (version != NULL)
+               plugin->priv->shell_version = g_variant_dup_string (version, NULL);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine:
+ */
+static gboolean
+gs_plugin_refine_item (GsPlugin *plugin,
+                      GsApp *app,
+                      GError **error)
+{
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "ShellExtensions") != 0)
+               return TRUE;
+
+       /* FIXME */
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       gs_app_set_size (app, 1024 * 4);
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine:
+ */
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+                 GList **list,
+                 GsPluginRefineFlags flags,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       GList *l;
+       GsApp *app;
+
+       for (l = *list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               if (!gs_plugin_refine_item (plugin, app, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_shell_extensions_parse_version:
+ */
+static gboolean
+gs_plugin_shell_extensions_parse_version (GsPlugin *plugin,
+                                         AsApp *app,
+                                         JsonObject *ver_map,
+                                         GError **error)
+{
+       JsonObject *json_ver = NULL;
+       gint64 version;
+       g_autofree gchar *shell_version = NULL;
+       g_autoptr(AsRelease) release = NULL;
+
+       /* look for version, FIXME: either major.minor or major.minor.micro */
+       if (json_object_has_member (ver_map, plugin->priv->shell_version)) {
+               json_ver = json_object_get_object_member (ver_map,
+                                                         plugin->priv->shell_version);
+       }
+       if (json_ver == NULL) {
+               g_warning ("no shell_version_map for %s",
+                          plugin->priv->shell_version);
+               return TRUE;
+       }
+
+       /* unknown :/ */
+//     json_object_get_int_member (json_ver, "pk");
+
+       /* parse the version */
+       version = json_object_get_int_member (json_ver, "version");
+       if (version == 0) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "no version in map!");
+               return FALSE;
+       }
+       shell_version = g_strdup_printf ("%" G_GINT64_FORMAT, version);
+
+       /* add a dummy release */
+       release = as_release_new ();
+       as_release_set_version (release, shell_version);
+       as_app_add_release (app, release);
+       return TRUE;
+}
+
+/**
+ * _as_markup_import:
+ */
+static gchar *
+_as_markup_import (const gchar *text)
+{
+       GString *str;
+       guint i;
+       g_auto(GStrv) lines = NULL;
+
+       /* just assume paragraphs */
+       str = g_string_new ("<p>");
+       lines = g_strsplit (text, "\n", -1);
+       for (i = 0; lines[i] != NULL; i++) {
+               g_autofree gchar *markup = NULL;
+               if (lines[i][0] == '\0') {
+                       if (g_str_has_suffix (str->str, " "))
+                               g_string_truncate (str, str->len - 1);
+                       g_string_append (str, "</p><p>");
+                       continue;
+               }
+               markup = g_markup_escape_text (lines[i], -1);
+               g_string_append (str, markup);
+               g_string_append (str, " ");
+       }
+       if (g_str_has_suffix (str->str, " "))
+               g_string_truncate (str, str->len - 1);
+       g_string_append (str, "</p>");
+       return g_string_free (str, FALSE);
+}
+
+/**
+ * gs_plugin_shell_extensions_parse_app:
+ */
+static AsApp *
+gs_plugin_shell_extensions_parse_app (GsPlugin *plugin,
+                                     JsonObject *json_app,
+                                     GError **error)
+{
+       AsApp *app;
+       JsonObject *json_ver_map;
+       const gchar *tmp;
+       guint64 pk;
+       g_autofree gchar *id = NULL;
+
+       app = as_app_new ();
+
+       /* not sure what this is for :/ */
+       pk = json_object_get_int_member (json_app, "pk");
+       id = g_strdup_printf ("org.gnome.extensions-%" G_GINT64_FORMAT ".ext", pk);
+       as_app_set_id (app, id);
+
+       tmp = json_object_get_string_member (json_app, "description");
+       if (tmp != NULL) {
+               g_autofree gchar *desc = NULL;
+               desc = _as_markup_import (tmp);
+               as_app_set_description (app, NULL, desc);
+       }
+       tmp = json_object_get_string_member (json_app, "name");
+       if (tmp != NULL)
+               as_app_set_name (app, NULL, tmp);
+       tmp = json_object_get_string_member (json_app, "uuid");
+       if (tmp != NULL)
+               as_app_add_metadata (app, "ShellExtensions::uuid", tmp);
+       tmp = json_object_get_string_member (json_app, "link");
+       if (tmp != NULL) {
+               g_autofree gchar *uri = NULL;
+               uri = g_build_filename (SHELL_EXTENSIONS_API_URI, tmp, NULL);
+               as_app_add_url (app, AS_URL_KIND_HOMEPAGE, uri);
+       }
+       tmp = json_object_get_string_member (json_app, "icon");
+       if (tmp != NULL) {
+               g_autofree gchar *uri = NULL;
+               g_autoptr(AsIcon) ic = NULL;
+
+               /* use stock icon for generic */
+               ic = as_icon_new ();
+               if (g_strcmp0 (tmp, "/static/images/plugin.png") == 0) {
+                       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
+                       as_icon_set_name (ic, "application-x-addon");
+               } else {
+                       uri = g_build_filename (SHELL_EXTENSIONS_API_URI, tmp, NULL);
+                       as_icon_set_kind (ic, AS_ICON_KIND_REMOTE);
+                       as_icon_set_url (ic, uri);
+               }
+               as_app_add_icon (app, ic);
+       }
+
+       /* try to get version */
+       json_ver_map = json_object_get_object_member (json_app, "shell_version_map");
+       if (json_ver_map != NULL) {
+               if (!gs_plugin_shell_extensions_parse_version (plugin,
+                                                              app,
+                                                              json_ver_map,
+                                                              error))
+                       return FALSE;
+       }
+
+       /* add a screenshot, which curiously isn't in the json */
+       if (1) {
+               g_autoptr(AsScreenshot) ss = NULL;
+               g_autoptr(AsImage) im = NULL;
+               g_autofree gchar *uri = NULL;
+               uri = g_strdup_printf ("%s/static/extension-data/"
+                                      "screenshots/"
+                                      "screenshot_%" G_GSIZE_FORMAT ".png",
+                                      SHELL_EXTENSIONS_API_URI, pk);
+               /* TODO: check if the URI exists */
+               im = as_image_new ();
+               as_image_set_kind (im, AS_IMAGE_KIND_SOURCE);
+               as_image_set_url (im, uri);
+               ss = as_screenshot_new ();
+               as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT);
+               as_screenshot_add_image (ss, im);
+               as_app_add_screenshot (app, ss);
+       }
+
+       /* required to match categories in gnome-software */
+       as_app_add_category (app, "Addons");
+       as_app_add_category (app, "ShellExtensions");
+
+       /* we have no data :/ */
+       as_app_set_comment (app, NULL, "GNOME Shell Extension");
+       as_app_add_metadata (app, "ManagementPlugin", "ShellExtensions");
+       return app;
+}
+
+/**
  * gs_plugin_shell_extensions_parse_apps:
  */
 static GPtrArray *
-gs_plugin_shell_extensions_parse_apps (const gchar *data,
+gs_plugin_shell_extensions_parse_apps (GsPlugin *plugin,
+                                      const gchar *data,
                                       gsize data_len,
                                       GError **error)
 {
        GPtrArray *apps;
+       JsonArray *json_extensions_array;
+       JsonNode *json_extensions;
        JsonNode *json_root;
        JsonObject *json_item;
-//     guint i;
+       guint i;
        g_autoptr(JsonParser) json_parser = NULL;
 
        /* nothing */
@@ -119,14 +382,14 @@ gs_plugin_shell_extensions_parse_apps (const gchar *data,
                g_set_error_literal (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_FAILED,
-                                    "no error root");
+                                    "no data root");
                return NULL;
        }
        if (json_node_get_node_type (json_root) != JSON_NODE_OBJECT) {
                g_set_error_literal (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_FAILED,
-                                    "no error object");
+                                    "no data object");
                return NULL;
        }
        json_item = json_node_get_object (json_root);
@@ -134,10 +397,44 @@ gs_plugin_shell_extensions_parse_apps (const gchar *data,
                g_set_error_literal (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_FAILED,
-                                    "no error object");
+                                    "no data object");
                return NULL;
        }
+
+       /* load extensions */
        apps = g_ptr_array_new ();
+       json_extensions = json_object_get_member (json_item, "extensions");
+       if (json_extensions == NULL) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "no extensions object");
+               return NULL;
+       }
+       json_extensions_array = json_node_get_array (json_extensions);
+       if (json_extensions_array == NULL) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "no extensions array");
+               return NULL;
+       }
+
+       /* parse each app */
+       for (i = 0; i < json_array_get_length (json_extensions_array); i++) {
+               AsApp *app;
+               JsonNode *json_extension;
+               JsonObject *json_extension_obj;
+               json_extension = json_array_get_element (json_extensions_array, i);
+               json_extension_obj = json_node_get_object (json_extension);
+               app = gs_plugin_shell_extensions_parse_app (plugin,
+                                                           json_extension_obj,
+                                                           error);
+               if (app == NULL)
+                       return NULL;
+               g_ptr_array_add (apps, app);
+       }
+
        return apps;
 }
 
@@ -145,7 +442,9 @@ gs_plugin_shell_extensions_parse_apps (const gchar *data,
  * gs_plugin_shell_extensions_get_apps:
  */
 static GPtrArray *
-gs_plugin_shell_extensions_get_apps (GsPlugin *plugin, GError **error)
+gs_plugin_shell_extensions_get_apps (GsPlugin *plugin,
+                                    guint cache_age,
+                                    GError **error)
 {
        GPtrArray *apps;
        guint status_code;
@@ -162,18 +461,22 @@ gs_plugin_shell_extensions_get_apps (GsPlugin *plugin, GError **error)
                return NULL;
        cachefn = g_strdup_printf ("%s/gnome.json", cachedir);
        cachefn_file = g_file_new_for_path (cachefn);
-       if (gs_utils_get_file_age (cachefn_file) < SHELL_EXTENSIONS_CACHE_AGE_MAX) {
+       if (gs_utils_get_file_age (cachefn_file) < cache_age) {
                g_autofree gchar *json_data = NULL;
                if (!g_file_get_contents (cachefn, &json_data, NULL, error))
                        return NULL;
                g_debug ("got cached extension data from %s", cachefn);
-               return gs_plugin_shell_extensions_parse_apps (json_data, -1, error);
+               return gs_plugin_shell_extensions_parse_apps (plugin,
+                                                             json_data,
+                                                             -1, error);
        }
 
        /* create the GET data */
-       uri = g_strdup_printf ("%s/?shell_version=%s&page=1&n_per_page=1000",
+       uri = g_strdup_printf ("%s/extension-query/"
+                              "?shell_version=%s"
+                              "&page=1&n_per_page=1000",
                               SHELL_EXTENSIONS_API_URI,
-                              "3.20");
+                              plugin->priv->shell_version);
        msg = soup_message_new (SOUP_METHOD_GET, uri);
        status_code = soup_session_send_message (plugin->soup_session, msg);
        if (status_code != SOUP_STATUS_OK) {
@@ -184,10 +487,8 @@ gs_plugin_shell_extensions_get_apps (GsPlugin *plugin, GError **error)
                             msg->response_body->data);
                return NULL;
        }
-       g_debug ("%s returned: %s",
-                SHELL_EXTENSIONS_API_URI,
-                msg->response_body->data);
-       apps = gs_plugin_shell_extensions_parse_apps (msg->response_body->data,
+       apps = gs_plugin_shell_extensions_parse_apps (plugin,
+                                                     msg->response_body->data,
                                                      msg->response_body->length,
                                                      error);
        if (apps == NULL)
@@ -204,67 +505,56 @@ gs_plugin_shell_extensions_get_apps (GsPlugin *plugin, GError **error)
 }
 
 /**
- * _gs_plugin_startup:
+ * gs_plugin_refresh:
  */
-static gboolean
-_gs_plugin_startup (GsPlugin *plugin, GError **error)
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+                  guint cache_age,
+                  GsPluginRefreshFlags flags,
+                  GCancellable *cancellable,
+                  GError **error)
 {
        AsApp *app;
        guint i;
+       g_autofree gchar *fn = NULL;
        g_autoptr(GPtrArray) apps = NULL;
+       g_autoptr(AsStore) store = NULL;
+       g_autoptr(GFile) file = NULL;
+
+       /* connect to gnome-shell */
+       if (!gs_plugin_setup (plugin, cancellable, error))
+               return FALSE;
 
        /* get data */
-       apps = gs_plugin_shell_extensions_get_apps (plugin, error);
+       apps = gs_plugin_shell_extensions_get_apps (plugin, cache_age, error);
        if (apps == NULL)
                return FALSE;
 
-       /* add to global store */
+       /* add to local store */
+       store = as_store_new ();
+       as_store_set_origin (store, "extensions-web");
        for (i = 0; i < apps->len; i++) {
                app = g_ptr_array_index (apps, i);
-               g_debug ("Adding to global store %s", as_app_get_id (app));
+               g_debug ("adding to local store %s", as_app_get_id (app));
+               as_store_add_app (store, app);
        }
 
-       return TRUE;
-}
-
-/**
- * gs_plugin_initialize:
- */
-void
-gs_plugin_initialize (GsPlugin *plugin)
-{
-       plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
-
-       /* this really belongs in _startup() or something that runs as a 2nd phase */
-       _gs_plugin_startup (plugin, NULL);
-}
-
-/**
- * gs_plugin_destroy:
- */
-void
-gs_plugin_destroy (GsPlugin *plugin)
-{
-       if (plugin->priv->distros != NULL)
-               g_ptr_array_unref (plugin->priv->distros);
-}
-
-/**
- * gs_plugin_add_category_apps:
- */
-gboolean
-gs_plugin_add_category_apps (GsPlugin *plugin,
-                            GsCategory *category,
-                            GList **list,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       if (g_strcmp0 (gs_category_get_id (category), "shell-extensions") != 0)
-               return TRUE;
-
-       //FIXME: add apps with the ShellExtensions management plugin?
-       //FIXME or leave to the appstream plugin?
-       return TRUE;
+       /* save to disk */
+       fn = g_build_filename (g_get_user_data_dir (),
+                              "app-info",
+                              "xmls",
+                              "extensions-web.xml",
+                              NULL);
+       if (!gs_mkdir_parent (fn, error))
+               return FALSE;
+       file = g_file_new_for_path (fn);
+       g_debug ("saving to %s", fn);
+       return as_store_to_file (store, file,
+                                AS_NODE_TO_XML_FLAG_ADD_HEADER |
+                                AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
+                                AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
+                                cancellable,
+                                error);
 }
 
 /**
@@ -276,16 +566,34 @@ gs_plugin_app_remove (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
+       const gchar *uuid;
+       g_autoptr(GVariant) retval = NULL;
+
        /* only process this app if was created by this plugin */
        if (g_strcmp0 (gs_app_get_management_plugin (app), "ShellExtensions") != 0)
                return TRUE;
 
-       /* FIXME */
-       g_set_error_literal (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_FAILED,
-                            "Not yet supported");
-       return FALSE;
+       /* connect to gnome-shell */
+       if (!gs_plugin_setup (plugin, cancellable, error))
+               return FALSE;
+
+       /* install */
+       uuid = gs_app_get_metadata_item (app, "ShellExtensions::uuid");
+       retval = g_dbus_proxy_call_sync (plugin->priv->proxy,
+                                        "UninstallExtension",
+                                        g_variant_new ("(s)", uuid),
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1,
+                                        cancellable,
+                                        error);
+       if (retval == NULL)
+               return FALSE;
+
+       /* not sure why this would fail */
+       if (!g_variant_get_boolean (retval))
+               g_error ("failed, no message?");
+
+       return TRUE;
 }
 
 /**
@@ -297,14 +605,65 @@ gs_plugin_app_install (GsPlugin *plugin,
                       GCancellable *cancellable,
                       GError **error)
 {
+       const gchar *uuid;
+       g_autoptr(GVariant) retval = NULL;
+
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "ShellExtensions") != 0)
+               return TRUE;
+
+       /* connect to gnome-shell */
+       if (!gs_plugin_setup (plugin, cancellable, error))
+               return FALSE;
+
+       /* install */
+       uuid = gs_app_get_metadata_item (app, "ShellExtensions::uuid");
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+       retval = g_dbus_proxy_call_sync (plugin->priv->proxy,
+                                        "InstallRemoteExtension",
+                                        g_variant_new ("(s)", uuid),
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1,
+                                        cancellable,
+                                        error);
+       if (retval == NULL)
+               return FALSE;
+       g_warning ("%s", g_variant_get_type_string (retval));
+       g_debug ("shell returned: %s", g_variant_get_string (retval, NULL));
+       return TRUE;
+}
+
+/**
+ * gs_plugin_launch:
+ */
+gboolean
+gs_plugin_launch (GsPlugin *plugin,
+                 GsApp *app,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       const gchar *uuid;
+       g_autoptr(GVariant) retval = NULL;
+
        /* only process this app if was created by this plugin */
        if (g_strcmp0 (gs_app_get_management_plugin (app), "ShellExtensions") != 0)
                return TRUE;
 
-       /* FIXME */
-       g_set_error_literal (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_FAILED,
-                            "No yet supported");
-       return FALSE;
+       /* connect to gnome-shell */
+       if (!gs_plugin_setup (plugin, cancellable, error))
+               return FALSE;
+
+       /* install */
+       uuid = gs_app_get_metadata_item (app, "ShellExtensions::uuid");
+       retval = g_dbus_proxy_call_sync (plugin->priv->proxy,
+                                        "LaunchExtensionPrefs",
+                                        g_variant_new ("(s)", uuid),
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1,
+                                        cancellable,
+                                        error);
+       if (retval == NULL)
+               return FALSE;
+       g_debug ("shell returned: %s", g_variant_get_string (retval, NULL));
+       return TRUE;
 }


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