[gnome-software/wip/ubuntu-3-20] snap: Backport fixes from master



commit e04b2784d03a2ac400693880bde456e3dadaab5f
Author: Robert Ancell <robert ancell canonical com>
Date:   Fri Jun 16 11:57:13 2017 +1200

    snap: Backport fixes from master
    
    Fixes the following:
    - Refactor refining code to be more efficient and reliable
    - Cache icons
    - Set screenshot dimensions
    - Set install date
    - Use gs_plugin_add_popular to show featured snaps

 src/plugins/gs-plugin-snap.c |  469 +++++++++++++++++++++++++++---------------
 src/plugins/gs-snapd.c       |  165 ++++++++++-----
 src/plugins/gs-snapd.h       |   10 +-
 3 files changed, 424 insertions(+), 220 deletions(-)
---
diff --git a/src/plugins/gs-plugin-snap.c b/src/plugins/gs-plugin-snap.c
index db2aefd..71940b5 100644
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@ -27,6 +27,7 @@
 #include "gs-ubuntuone.h"
 
 struct GsPluginPrivate {
+       GHashTable      *store_snaps;
 };
 
 typedef gboolean (*AppFilterFunc)(const gchar *id, JsonObject *object, gpointer data);
@@ -48,131 +49,74 @@ gs_plugin_initialize (GsPlugin *plugin)
                         gs_plugin_get_name ());
                gs_plugin_set_enabled (plugin, FALSE);
        }
+
+       plugin->priv->store_snaps = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                          g_free, (GDestroyNotify) json_object_unref);
 }
 
-static void
-refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_search, GCancellable 
*cancellable)
+static gboolean
+gs_plugin_snap_set_app_pixbuf_from_data (GsApp *app, const gchar *buf, gsize count, GError **error)
 {
-       const gchar *status, *icon_url, *launch_name = NULL;
-       g_autoptr(GdkPixbuf) icon_pixbuf = NULL;
-       gint64 size = -1;
-
-       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_has_member (package, "update_available") ?
-                       json_object_get_string_member (package, "update_available") : NULL;
-               if (update_available)
-                       gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
-               else {
-                       if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE)
-                               gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
-                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-               }
-               size = json_object_get_int_member (package, "installed-size");
-       } else if (g_strcmp0 (status, "not installed") == 0 || g_strcmp0 (status, "available") == 0) {
-               gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-               size = json_object_get_int_member (package, "download-size");
-       }
-       else if (g_strcmp0 (status, "removed") == 0) {
-               // A removed app is only available if it can be downloaded (it might have been sideloaded)
-               size = json_object_get_int_member (package, "download-size");
-               if (size > 0)
-                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       g_autoptr(GdkPixbufLoader) loader = NULL;
+       g_autoptr(GError) error_local = NULL;
+
+       loader = gdk_pixbuf_loader_new ();
+       if (!gdk_pixbuf_loader_write (loader, buf, count, &error_local)) {
+               g_debug ("icon_data[%" G_GSIZE_FORMAT "]=%s", count, buf);
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to write: %s",
+                            error_local->message);
+               return FALSE;
        }
-
-       gs_app_set_name (app, GS_APP_QUALITY_HIGHEST,
-                        json_object_get_string_member (package, "name"));
-       gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST,
-                           json_object_get_string_member (package, "summary"));
-       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"));
-       if (size > 0)
-               gs_app_set_size (app, size);
-       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_data = NULL;
-               gsize icon_data_length;
-               g_autoptr(GError) error = NULL;
-
-               icon_data = gs_snapd_get_resource (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_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: %s\n", error->message);
+       if (!gdk_pixbuf_loader_close (loader, &error_local)) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to close: %s",
+                            error_local->message);
+               return FALSE;
        }
-       else {
-               g_autoptr(SoupMessage) message = NULL;
-               g_autoptr(GdkPixbufLoader) loader = NULL;
+       gs_app_set_pixbuf (app, gdk_pixbuf_loader_get_pixbuf (loader));
+       return TRUE;
+}
 
-               message = soup_message_new (SOUP_METHOD_GET, icon_url);
-               if (message != NULL) {
-                       soup_session_send_message (plugin->soup_session, message);
-                       loader = gdk_pixbuf_loader_new ();
-                       gdk_pixbuf_loader_write (loader,
-                                                (guint8 *) message->response_body->data,
-                                                message->response_body->length,
-                                                NULL);
-                       gdk_pixbuf_loader_close (loader, NULL);
-                       icon_pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
-               }
-       }
+static JsonArray *
+find_snaps (GsPlugin *plugin, const gchar *section, gboolean match_name, const gchar *query, GCancellable 
*cancellable, GError **error)
+{
+       g_autoptr(JsonArray) snaps = NULL;
+       guint i;
 
-       if (icon_pixbuf) {
-               gs_app_set_pixbuf (app, icon_pixbuf);
-       } else {
-               g_autoptr(AsIcon) icon = NULL;
+       snaps = gs_snapd_find (section, match_name, query, cancellable, error);
+       if (snaps == NULL)
+               return NULL;
 
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "package-x-generic");
-               gs_app_set_icon (app, icon);
+       /* cache results */
+       for (i = 0; i < json_array_get_length (snaps); i++) {
+               JsonObject *snap = json_array_get_object_element (snaps, i);
+               g_hash_table_insert (plugin->priv->store_snaps, g_strdup (json_object_get_string_member 
(snap, "name")), json_object_ref (snap));
        }
 
-       if (json_object_has_member (package, "screenshots") && gs_app_get_screenshots (app)->len <= 0) {
-               JsonArray *screenshots;
-               guint i;
-
-               screenshots = json_object_get_array_member (package, "screenshots");
-               for (i = 0; i < json_array_get_length (screenshots); i++) {
-                       JsonObject *screenshot = json_array_get_object_element (screenshots, i);
-                       g_autoptr(AsScreenshot) ss = NULL;
-                       g_autoptr(AsImage) image = NULL;
-
-                       ss = as_screenshot_new ();
-                       as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_NORMAL);
-                       image = as_image_new ();
-                       as_image_set_url (image, json_object_get_string_member (screenshot, "url"));
-                       as_image_set_kind (image, AS_IMAGE_KIND_SOURCE);
-                       as_screenshot_add_image (ss, image);
-                       gs_app_add_screenshot (app, ss);
-               }
-       }
+       return g_steal_pointer (&snaps);
+}
 
-       if (!from_search) {
-               JsonArray *apps;
+static GsApp *
+snap_to_app (GsPlugin *plugin, JsonObject *snap)
+{
+       GsApp *app;
 
-               apps = json_object_get_array_member (package, "apps");
-               if (apps && json_array_get_length (apps) > 0)
-                       launch_name = json_object_get_string_member (json_array_get_object_element (apps, 0), 
"name");
+       /* create a unique ID for deduplication, TODO: branch? */
+       app = gs_app_new (json_object_get_string_member (snap, "name"));
+       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+       gs_app_set_management_plugin (app, "snap");
+       gs_app_add_quirk (app, AS_APP_QUIRK_NOT_REVIEWABLE);
+       gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, json_object_get_string_member (snap, "name"));
+       gs_app_set_origin (app, _("Ubuntu Snappy Store")); // FIXME: Not necessarily from the snap store...
+       if (gs_plugin_check_distro_id (plugin, "ubuntu"))
+               gs_app_add_quirk (app, AS_APP_QUIRK_PROVENANCE);
 
-               if (launch_name)
-                       gs_app_set_metadata (app, "snap::launch-name", launch_name);
-               else
-                       gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE);
-       }
+       return app;
 }
 
 gboolean
@@ -195,16 +139,12 @@ gs_plugin_url_to_app (GsPlugin *plugin,
 
        /* create app */
        path = gs_utils_get_url_path (url);
-       snaps = gs_snapd_find_name (path, cancellable, NULL);
+       snaps = find_snaps (plugin, NULL, TRUE, path, cancellable, NULL);
        if (snaps == NULL || json_array_get_length (snaps) < 1)
                return TRUE;
 
        snap = json_array_get_object_element (snaps, 0);
-       app = gs_app_new (json_object_get_string_member (snap, "name"));
-       gs_app_set_management_plugin (app, "snap");
-       gs_app_add_quirk (app, AS_APP_QUIRK_NOT_REVIEWABLE);
-       refine_app (plugin, app, snap, TRUE, cancellable);
-       gs_plugin_add_app (list, app);
+       gs_plugin_add_app (list, snap_to_app (plugin, snap));
 
        return TRUE;
 }
@@ -212,6 +152,28 @@ gs_plugin_url_to_app (GsPlugin *plugin,
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
+       g_hash_table_unref (plugin->priv->store_snaps);
+}
+
+gboolean
+gs_plugin_add_popular (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       g_autoptr(JsonArray) snaps = NULL;
+       guint i;
+
+       snaps = find_snaps (plugin, "featured", FALSE, NULL, cancellable, error);
+       if (snaps == NULL)
+               return FALSE;
+
+       for (i = 0; i < json_array_get_length (snaps); i++) {
+               JsonObject *snap = json_array_get_object_element (snaps, i);
+               gs_plugin_add_app (list, snap_to_app (plugin, snap));
+       }
+
+       return TRUE;
 }
 
 gboolean
@@ -220,30 +182,22 @@ gs_plugin_add_installed (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
-       g_autoptr(JsonArray) result = NULL;
+       g_autoptr(JsonArray) snaps = NULL;
        guint i;
 
-       result = gs_snapd_list (cancellable, error);
-       if (result == NULL)
+       snaps = gs_snapd_list (cancellable, error);
+       if (snaps == NULL)
                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 *status, *name;
+       for (i = 0; i < json_array_get_length (snaps); i++) {
+               JsonObject *snap = json_array_get_object_element (snaps, i);
+               const gchar *status;
 
-               status = json_object_get_string_member (package, "status");
+               status = json_object_get_string_member (snap, "status");
                if (g_strcmp0 (status, "active") != 0)
                        continue;
 
-               name = json_object_get_string_member (package, "name");
-               app = gs_app_new (name);
-               gs_app_set_management_plugin (app, "snap");
-               gs_app_set_origin (app, _("Ubuntu Snappy Store")); // FIXME: Not necessarily from the snap 
store...
-               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_plugin_add_app (list, app);
+               gs_plugin_add_app (list, snap_to_app (plugin, snap));
        }
 
        return TRUE;
@@ -256,26 +210,225 @@ gs_plugin_add_search (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
-       g_autoptr(JsonArray) result = NULL;
+       g_autofree gchar *query = NULL;
+       g_autoptr(JsonArray) snaps = NULL;
        guint i;
 
-       result = gs_snapd_find (values, cancellable, error);
-       if (result == NULL)
+       query = g_strjoinv (" ", values);
+       snaps = find_snaps (plugin, NULL, FALSE, query, cancellable, error);
+       if (snaps == NULL)
                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;
-
-               name = json_object_get_string_member (package, "name");
-               app = gs_app_new (name);
-               gs_app_set_management_plugin (app, "snap");
-               gs_app_set_origin (app, _("Ubuntu Snappy Store")); // FIXME: Not necessarily from the snap 
store...
-               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_plugin_add_app (list, app);
+       for (i = 0; i < json_array_get_length (snaps); i++) {
+               JsonObject *snap = json_array_get_object_element (snaps, i);
+               gs_plugin_add_app (list, snap_to_app (plugin, snap));
+       }
+
+       return TRUE;
+}
+
+static gboolean
+load_icon (GsPlugin *plugin, GsApp *app, const gchar *icon_url, GCancellable *cancellable, GError **error)
+{
+       if (icon_url == NULL || g_strcmp0 (icon_url, "") == 0) {
+               g_autoptr(AsIcon) icon = as_icon_new ();
+               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+               as_icon_set_name (icon, "package-x-generic");
+               gs_app_set_icon (app, icon);
+               return TRUE;
+       }
+
+       /* icon is optional, either loaded from snapd or from a URL */
+       if (g_str_has_prefix (icon_url, "/")) {
+               g_autofree gchar *icon_data = NULL;
+               gsize icon_data_length;
+
+               icon_data = gs_snapd_get_resource (icon_url, &icon_data_length, cancellable, error);
+               if (icon_data == NULL)
+                       return FALSE;
+
+               if (!gs_plugin_snap_set_app_pixbuf_from_data (app,
+                                                             icon_data, icon_data_length,
+                                                             error)) {
+                       g_prefix_error (error, "Failed to load %s: ", icon_url);
+                       return FALSE;
+               }
+       } else {
+               g_autofree gchar *basename_tmp = NULL;
+               g_autofree gchar *hash = NULL;
+               g_autofree gchar *basename = NULL;
+               g_autofree gchar *cache_dir = NULL;
+               g_autofree gchar *cache_fn = NULL;
+               g_autoptr(SoupMessage) message = NULL;
+               g_autoptr(GdkPixbufLoader) loader = NULL;
+               g_autoptr(GError) local_error = NULL;
+
+               /* attempt to load from cache */
+               basename_tmp = g_path_get_basename (icon_url);
+               hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, icon_url, -1);
+               basename = g_strdup_printf ("%s-%s", hash, basename_tmp);
+               cache_dir = gs_utils_get_cachedir ("snap-icons", error);
+               cache_fn = g_build_filename (cache_dir, basename, NULL);
+               if (cache_fn == NULL)
+                       return FALSE;
+               if (g_file_test (cache_fn, G_FILE_TEST_EXISTS)) {
+                       g_autofree gchar *data = NULL;
+                       gsize data_len;
+
+                       if (g_file_get_contents (cache_fn, &data, &data_len, &local_error) &&
+                           gs_plugin_snap_set_app_pixbuf_from_data (app,
+                                                                    data, data_len,
+                                                                    error))
+                               return TRUE;
+
+                       g_warning ("Failed to load cached icon: %s", local_error->message);
+               }
+
+               /* load from URL */
+               message = soup_message_new (SOUP_METHOD_GET, icon_url);
+               if (message == NULL) {
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "Failed to parse icon URL: %s",
+                                    icon_url);
+                       return FALSE;
+               }
+               soup_session_send_message (plugin->soup_session, message);
+               if (!gs_plugin_snap_set_app_pixbuf_from_data (app,
+                                       (const gchar *) message->response_body->data,
+                                       message->response_body->length,
+                                       error)) {
+                       g_prefix_error (error, "Failed to load %s: ", icon_url);
+                       return FALSE;
+               }
+
+               /* write to cache */
+               if (!g_file_set_contents (cache_fn, message->response_body->data, 
message->response_body->length, &local_error))
+                       g_warning ("Failed to save icon to cache: %s", local_error->message);
+       }
+
+       return TRUE;
+}
+
+static JsonObject *
+get_store_snap (GsPlugin *plugin, const gchar *name, GCancellable *cancellable, GError **error)
+{
+       JsonObject *snap = NULL;
+       g_autoptr(JsonArray) snaps = NULL;
+
+       /* use cached version if available */
+       snap = g_hash_table_lookup (plugin->priv->store_snaps, name);
+       if (snap != NULL)
+               return json_object_ref (snap);
+
+       snaps = find_snaps (plugin, NULL, TRUE, name, cancellable, error);
+       if (snaps == NULL || json_array_get_length (snaps) < 1)
+               return NULL;
+
+       return json_object_ref (json_array_get_object_element (snaps, 0));
+}
+
+static gboolean
+gs_plugin_refine_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GsPluginRefineFlags flags,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       const gchar *id, *icon_url = NULL;
+       g_autoptr(JsonObject) local_snap = NULL;
+       g_autoptr(JsonObject) store_snap = NULL;
+
+       /* not us */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
+               return TRUE;
+
+       id = gs_app_get_id (app);
+       if (id == NULL)
+               id = gs_app_get_source_default (app);
+
+       /* get information from installed snaps */
+       local_snap = gs_snapd_list_one (id, cancellable, NULL);
+       if (local_snap != NULL) {
+               JsonArray *apps;
+               g_autoptr(GDateTime) install_date = NULL;
+               const gchar *launch_name = NULL;
+
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member (local_snap, 
"name"));
+               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member (local_snap, 
"summary"));
+               gs_app_set_description (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member 
(local_snap, "description"));
+               gs_app_set_version (app, json_object_get_string_member (local_snap, "version"));
+               if (json_object_has_member (local_snap, "installed-size"))
+                       gs_app_set_size (app, json_object_get_int_member (local_snap, "installed-size"));
+               if (json_object_has_member (local_snap, "install-date"))
+                       install_date = gs_snapd_parse_date (json_object_get_string_member (local_snap, 
"install-date"));
+               if (install_date != NULL)
+                       gs_app_set_install_date (app, g_date_time_to_unix (install_date));
+               icon_url = json_object_get_string_member (local_snap, "icon");
+               if (g_strcmp0 (icon_url, "") == 0)
+                       icon_url = NULL;
+
+               apps = json_object_get_array_member (local_snap, "apps");
+               if (apps && json_array_get_length (apps) > 0)
+                       launch_name = json_object_get_string_member (json_array_get_object_element (apps, 0), 
"name");
+
+               if (launch_name)
+                       gs_app_set_metadata (app, "snap::launch-name", launch_name);
+               else
+                       gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE);
+       }
+
+       /* get information from snap store */
+       store_snap = get_store_snap (plugin, id, cancellable, NULL);
+       if (store_snap != NULL) {
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member (store_snap, 
"name"));
+               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member (store_snap, 
"summary"));
+               gs_app_set_description (app, GS_APP_QUALITY_NORMAL, json_object_get_string_member 
(store_snap, "description"));
+               gs_app_set_version (app, json_object_get_string_member (store_snap, "version"));
+               if (gs_app_get_size (app) == GS_APP_SIZE_UNKNOWN && json_object_has_member (store_snap, 
"download-size"))
+                       gs_app_set_size (app, json_object_get_int_member (store_snap, "download-size"));
+               if (icon_url == NULL) {
+                       icon_url = json_object_get_string_member (store_snap, "icon");
+                       if (g_strcmp0 (icon_url, "") == 0)
+                               icon_url = NULL;
+               }
+
+               if (json_object_has_member (store_snap, "screenshots") && gs_app_get_screenshots (app)->len 
== 0) {
+                       JsonArray *screenshots;
+                       guint i;
+
+                       screenshots = json_object_get_array_member (store_snap, "screenshots");
+                       for (i = 0; i < json_array_get_length (screenshots); i++) {
+                               JsonObject *screenshot = json_array_get_object_element (screenshots, i);
+                               g_autoptr(AsScreenshot) ss = NULL;
+                               g_autoptr(AsImage) image = NULL;
+
+                               ss = as_screenshot_new ();
+                               as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_NORMAL);
+                               image = as_image_new ();
+                               as_image_set_url (image, json_object_get_string_member (screenshot, "url"));
+                               as_image_set_kind (image, AS_IMAGE_KIND_SOURCE);
+                               if (json_object_has_member (screenshot, "width"))
+                                       as_image_set_width (image, json_object_get_int_member (screenshot, 
"width"));
+                               if (json_object_has_member (screenshot, "height"))
+                                       as_image_set_height (image, json_object_get_int_member (screenshot, 
"height"));
+                               as_screenshot_add_image (ss, image);
+                               gs_app_add_screenshot (app, ss);
+                       }
+               }
+       }
+
+       /* load icon if requested */
+       if (gs_app_get_pixbuf (app) == NULL && gs_app_get_icon (app) == NULL) {
+               if (!load_icon (plugin, app, icon_url, cancellable, error))
+                       return FALSE;
        }
 
        return TRUE;
@@ -292,15 +445,10 @@ gs_plugin_refine (GsPlugin *plugin,
 
        for (link = *list; link; link = link->next) {
                GsApp *app = link->data;
-               g_autoptr(JsonObject) result = NULL;
+               g_autoptr(GError) local_error = NULL;
 
-               if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
-                       continue;
-
-               result = gs_snapd_list_one (gs_app_get_id (app), cancellable, NULL);
-               if (result == NULL)
-                       continue;
-               refine_app (plugin, app, result, FALSE, cancellable);
+               if (!gs_plugin_refine_app (plugin, app, flags, cancellable, &local_error))
+                       g_warning ("Failed to refine snap: %s", local_error->message);
        }
 
        return TRUE;
@@ -394,7 +542,7 @@ is_graphical (GsApp *app, GCancellable *cancellable)
                if (interface == NULL)
                        continue;
 
-               if (strcmp (interface, "unity7") == 0 || strcmp (interface, "x11") == 0 || strcmp (interface, 
"mir") == 0)
+               if (g_strcmp0 (interface, "unity7") == 0 || g_strcmp0 (interface, "x11") == 0 || g_strcmp0 
(interface, "mir") == 0)
                        return TRUE;
        }
 
@@ -412,7 +560,6 @@ gs_plugin_launch (GsPlugin *plugin,
        GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE;
        g_autoptr(GAppInfo) info = NULL;
 
-
        /* We can only launch apps we know of */
        if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
                return TRUE;
@@ -421,7 +568,7 @@ gs_plugin_launch (GsPlugin *plugin,
        if (!launch_name)
                return TRUE;
 
-       if (strcmp (launch_name, gs_app_get_id (app)) == 0)
+       if (g_strcmp0 (launch_name, gs_app_get_id (app)) == 0)
                binary_name = g_strdup_printf ("/snap/bin/%s", launch_name);
        else
                binary_name = g_strdup_printf ("/snap/bin/%s.%s", gs_app_get_id (app), launch_name);
diff --git a/src/plugins/gs-snapd.c b/src/plugins/gs-snapd.c
index 0a1d042..7e561a2 100644
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@ -436,12 +436,10 @@ gs_snapd_list (GCancellable *cancellable, GError **error)
 }
 
 JsonArray *
-gs_snapd_find (gchar **values,
+gs_snapd_find (const gchar *section, gboolean match_name, const gchar *query,
               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;
@@ -450,60 +448,23 @@ gs_snapd_find (gchar **values,
        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,
-                          TRUE, NULL, NULL,
-                          TRUE, NULL, NULL,
-                          &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;
+       path = g_string_new ("/v2/find?");
+       if (section != NULL) {
+               g_string_append_printf (path, "section=%s", section);
        }
+       if (query != NULL) {
+               g_autofree gchar *escaped = 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;
+               escaped = soup_uri_encode (query, NULL);
+               if (section != NULL)
+                       g_string_append (path, "&");
+               if (match_name)
+                       g_string_append (path, "name=");
+               else
+                       g_string_append (path, "q=");
+               g_string_append (path, escaped);
        }
-
-       return json_array_ref (result);
-}
-
-JsonArray *
-gs_snapd_find_name (const gchar *name,
-                   GCancellable *cancellable, GError **error)
-{
-       g_autofree gchar *escaped = NULL;
-       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;
-       JsonArray *result;
-
-       escaped = soup_uri_encode (name, NULL);
-       path = g_strdup_printf ("/v2/find?name=%s", escaped);
-       if (!send_request ("GET", path, NULL,
+       if (!send_request ("GET", path->str, NULL,
                           TRUE, NULL, NULL,
                           TRUE, NULL, NULL,
                           &status_code, &reason_phrase,
@@ -752,3 +713,99 @@ gs_snapd_get_resource (const gchar *path,
 
        return g_steal_pointer (&data);
 }
+
+static gboolean
+parse_date (const gchar *date_string, gint *year, gint *month, gint *day)
+{
+       /* Example: 2016-05-17 */
+       if (strchr (date_string, '-') != NULL) {
+               g_auto(GStrv) tokens = NULL;
+
+               tokens = g_strsplit (date_string, "-", -1);
+               if (g_strv_length (tokens) != 3)
+                       return FALSE;
+
+               *year = atoi (tokens[0]);
+               *month = atoi (tokens[1]);
+               *day = atoi (tokens[2]);
+
+               return TRUE;
+       }
+       /* Example: 20160517 */
+       else if (strlen (date_string) == 8) {
+               // FIXME: Implement
+               return FALSE;
+       }
+       else
+               return FALSE;
+}
+
+static gboolean
+parse_time (const gchar *time_string, gint *hour, gint *minute, gdouble *seconds)
+{
+       /* Example: 09:36:53.682 or 09:36:53 or 09:36 */
+       if (strchr (time_string, ':') != NULL) {
+               g_auto(GStrv) tokens = NULL;
+
+               tokens = g_strsplit (time_string, ":", 3);
+               *hour = atoi (tokens[0]);
+               if (tokens[1] == NULL)
+                       return FALSE;
+               *minute = atoi (tokens[1]);
+               if (tokens[2] != NULL)
+                       *seconds = g_ascii_strtod (tokens[2], NULL);
+               else
+                       *seconds = 0.0;
+
+               return TRUE;
+       }
+       /* Example: 093653.682 or 093653 or 0936 */
+       else {
+               // FIXME: Implement
+               return FALSE;
+       }
+}
+
+static gboolean
+is_timezone_prefix (gchar c)
+{
+       return c == '+' || c == '-' || c == 'Z';
+}
+
+GDateTime *
+gs_snapd_parse_date (const gchar *value)
+{
+       g_auto(GStrv) tokens = NULL;
+       g_autoptr(GTimeZone) timezone = NULL;
+       gint year = 0, month = 0, day = 0, hour = 0, minute = 0;
+       gdouble seconds = 0.0;
+
+       if (value == NULL)
+               return NULL;
+
+       /* Example: 2016-05-17T09:36:53+12:00 */
+       tokens = g_strsplit (value, "T", 2);
+       if (!parse_date (tokens[0], &year, &month, &day))
+               return NULL;
+       if (tokens[1] != NULL) {
+               gchar *timezone_start;
+
+               /* Timezone is either Z (UTC) +hh:mm or -hh:mm */
+               timezone_start = tokens[1];
+               while (*timezone_start != '\0' && !is_timezone_prefix (*timezone_start))
+                       timezone_start++;
+               if (*timezone_start != '\0')
+                       timezone = g_time_zone_new (timezone_start);
+
+               /* Strip off timezone */
+               *timezone_start = '\0';
+
+               if (!parse_time (tokens[1], &hour, &minute, &seconds))
+                       return NULL;
+       }
+
+       if (timezone == NULL)
+               timezone = g_time_zone_new_local ();
+
+       return g_date_time_new (timezone, year, month, day, hour, minute, seconds);
+}
diff --git a/src/plugins/gs-snapd.h b/src/plugins/gs-snapd.h
index 366af72..9017f93 100644
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@ -36,11 +36,9 @@ JsonObject *gs_snapd_list_one                (const gchar    *name,
 JsonArray *gs_snapd_list               (GCancellable   *cancellable,
                                         GError         **error);
 
-JsonArray *gs_snapd_find               (gchar          **values,
-                                        GCancellable   *cancellable,
-                                        GError         **error);
-
-JsonArray *gs_snapd_find_name          (const gchar    *name,
+JsonArray *gs_snapd_find               (const gchar    *section,
+                                        gboolean        match_name,
+                                        const gchar    *query,
                                         GCancellable   *cancellable,
                                         GError         **error);
 
@@ -64,4 +62,6 @@ gchar *gs_snapd_get_resource          (const gchar    *path,
                                         GCancellable   *cancellable,
                                         GError         **error);
 
+GDateTime *gs_snapd_parse_date         (const gchar    *value);
+
 #endif /* __GS_SNAPD_H__ */


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