[gnome-software/wip/william/cherry-pick] Ubuntu reviews plugin fixes



commit 851c4ce4b689455f977f4437fb3c97bc5a369f32
Author: William Hua <william hua canonical com>
Date:   Tue May 24 09:58:11 2016 -0400

    Ubuntu reviews plugin fixes

 configure.ac                           |    1 +
 src/plugins/Makefile.am                |    3 +-
 src/plugins/gs-plugin-ubuntu-reviews.c |  434 ++++++++++++++++++++++++++++++--
 3 files changed, 420 insertions(+), 18 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index efc7cf7..8979551 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,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(OAUTH, oauth)
 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 1579025..f6bed73 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -16,6 +16,7 @@ AM_CPPFLAGS =                                         \
        $(XDG_APP_CFLAGS)                               \
        $(FLATPAK_CFLAGS)                               \
        $(RPM_CFLAGS)                                   \
+       $(OAUTH_CFLAGS)                                 \
        -DBINDIR=\"$(bindir)\"                          \
        -DDATADIR=\"$(datadir)\"                        \
        -DG_LOG_DOMAIN=\"GsPlugin\"                     \
@@ -236,7 +237,7 @@ if HAVE_UBUNTU_REVIEWS
 libgs_plugin_ubuntu_reviews_la_SOURCES =               \
        gs-plugin-ubuntu-reviews.c                      \
        ubuntu-login-dialog.c
-libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(SQLITE_LIBS)
+libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(OAUTH_LIBS) 
$(SQLITE_LIBS)
 libgs_plugin_ubuntu_reviews_la_LDFLAGS = -module -avoid-version
 libgs_plugin_ubuntu_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 endif
diff --git a/src/plugins/gs-plugin-ubuntu-reviews.c b/src/plugins/gs-plugin-ubuntu-reviews.c
index f7024fe..bbb7c9a 100644
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@ -23,14 +23,25 @@
 
 #include <string.h>
 #include <math.h>
+#include <libsoup/soup.h>
 #include <json-glib/json-glib.h>
+#include <oauth.h>
 #include <sqlite3.h>
-#include <gnome-software.h>
+
+#include <gs-plugin.h>
+#include <gs-utils.h>
+
+#include "gs-ubuntuone.h"
+#include "gs-os-release.h"
 
 struct GsPluginData {
        gchar           *db_path;
        sqlite3         *db;
        gsize            db_loaded;
+       gchar           *consumer_key;
+       gchar           *consumer_secret;
+       gchar           *token_key;
+       gchar           *token_secret;
 };
 
 typedef struct {
@@ -90,6 +101,11 @@ void
 gs_plugin_destroy (GsPlugin *plugin)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
+
+       g_clear_pointer (&priv->token_secret, g_free);
+       g_clear_pointer (&priv->token_key, g_free);
+       g_clear_pointer (&priv->consumer_secret, g_free);
+       g_clear_pointer (&priv->consumer_key, g_free);
        g_clear_pointer (&priv->db, sqlite3_close);
        g_free (priv->db_path);
 }
@@ -138,6 +154,7 @@ set_timestamp (GsPlugin *plugin,
               const gchar *type,
               GError **error)
 {
+       GsPluginData *priv = gs_plugin_get_data (plugin);
        char *error_msg = NULL;
        gint result;
        g_autofree gchar *statement = NULL;
@@ -346,9 +363,34 @@ parse_review_entries (GsPlugin *plugin, JsonParser *parser, GError **error)
        return TRUE;
 }
 
+static void
+sign_message (SoupMessage *message, OAuthMethod method,
+             const gchar *consumer_key, const gchar *consumer_secret,
+             const gchar *token_key, const gchar *token_secret)
+{
+       g_autofree gchar *url = NULL, *oauth_authorization_parameters = NULL, *authorization_text = NULL;
+       gchar **url_parameters = NULL;
+       int url_parameters_length;
+
+       url = soup_uri_to_string (soup_message_get_uri (message), FALSE);
+
+       url_parameters_length = oauth_split_url_parameters(url, &url_parameters);
+       oauth_sign_array2_process (&url_parameters_length, &url_parameters,
+                                  NULL,
+                                  method,
+                                  message->method,
+                                  consumer_key, consumer_secret,
+                                  token_key, token_secret);
+       oauth_authorization_parameters = oauth_serialize_url_sep (url_parameters_length, 1, url_parameters, 
", ", 6);
+       oauth_free_array (&url_parameters_length, &url_parameters);
+       authorization_text = g_strdup_printf ("OAuth realm=\"Ratings and Reviews\", %s", 
oauth_authorization_parameters);
+       soup_message_headers_append (message->request_headers, "Authorization", authorization_text);
+}
+
 static gboolean
-send_review_request (GsPlugin *plugin, const gchar *method, const gchar *path, JsonBuilder *request, 
JsonParser **result, GError **error)
+send_review_request (GsPlugin *plugin, const gchar *method, const gchar *path, JsonBuilder *request, 
gboolean do_sign, JsonParser **result, GError **error)
 {
+       GsPluginData *priv = gs_plugin_get_data (plugin);
        g_autofree gchar *uri = NULL;
        g_autoptr(SoupMessage) msg = NULL;
        guint status_code;
@@ -368,6 +410,14 @@ send_review_request (GsPlugin *plugin, const gchar *method, const gchar *path, J
                soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, data, length);
        }
 
+       if (do_sign)
+               sign_message (msg,
+                             OA_PLAINTEXT,
+                             priv->consumer_key,
+                             priv->consumer_secret,
+                             priv->token_key,
+                             priv->token_secret);
+
        status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin), msg);
        if (status_code != SOUP_STATUS_OK) {
                g_set_error (error,
@@ -409,7 +459,7 @@ download_review_stats (GsPlugin *plugin, GError **error)
        g_autoptr(SoupMessage) msg = NULL;
        g_autoptr(JsonParser) result = NULL;
 
-       if (!send_review_request (plugin, SOUP_METHOD_GET, "/api/1.0/review-stats/any/any/", NULL, &result, 
error))
+       if (!send_review_request (plugin, SOUP_METHOD_GET, "/api/1.0/review-stats/any/any/", NULL, FALSE, 
&result, error))
                return FALSE;
 
        /* Extract the stats from the data */
@@ -557,8 +607,9 @@ parse_date_time (const gchar *text)
 }
 
 static GsReview *
-parse_review (JsonNode *node)
+parse_review (GsPlugin *plugin, JsonNode *node)
 {
+       GsPluginData *priv = gs_plugin_get_data (plugin);
        GsReview *review;
        JsonObject *object;
        gint64 star_rating;
@@ -570,6 +621,8 @@ parse_review (JsonNode *node)
        object = json_node_get_object (node);
 
        review = gs_review_new ();
+       if (g_strcmp0 (priv->consumer_key, json_object_get_string_member (object, "reviewer_username")) == 0)
+               gs_review_add_flags (review, GS_REVIEW_FLAG_SELF);
        gs_review_set_reviewer (review, json_object_get_string_member (object, "reviewer_displayname"));
        gs_review_set_summary (review, json_object_get_string_member (object, "summary"));
        gs_review_set_text (review, json_object_get_string_member (object, "review_text"));
@@ -597,7 +650,7 @@ parse_reviews (GsPlugin *plugin, JsonParser *parser, GsApp *app, GError **error)
                g_autoptr(GsReview) review = NULL;
 
                /* Read in from JSON... (skip bad entries) */
-               review = parse_review (json_array_get_element (array, i));
+               review = parse_review (plugin, json_array_get_element (array, i));
                if (review != NULL)
                        gs_app_add_review (app, review);
        }
@@ -629,7 +682,7 @@ download_reviews (GsPlugin *plugin, GsApp *app, const gchar *package_name, GErro
        // FIXME: This will only get the first page of reviews
        language = get_language (plugin);
        path = g_strdup_printf ("/api/1.0/reviews/filter/%s/any/any/any/%s/", language, package_name);
-       if (!send_review_request (plugin, SOUP_METHOD_GET, path, NULL, &result, error))
+       if (!send_review_request (plugin, SOUP_METHOD_GET, path, NULL, FALSE, &result, error))
                return FALSE;
 
        /* Extract the stats from the data */
@@ -688,11 +741,45 @@ refine_rating (GsPlugin *plugin, GsApp *app, GError **error)
 }
 
 static gboolean
+get_ubuntuone_credentials (GsPlugin  *plugin,
+                          gboolean   required,
+                          GError   **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+
+       /* Use current credentials if already available */
+       if (priv->consumer_key != NULL &&
+           priv->consumer_secret != NULL &&
+           priv->token_key != NULL &&
+           priv->token_secret != NULL)
+               return TRUE;
+
+       /* Otherwise start with a clean slate */
+       g_clear_pointer (&priv->token_secret, g_free);
+       g_clear_pointer (&priv->token_key, g_free);
+       g_clear_pointer (&priv->consumer_secret, g_free);
+       g_clear_pointer (&priv->consumer_key, g_free);
+
+       /* Use credentials if we have them */
+       if (gs_ubuntuone_get_credentials (&priv->consumer_key, &priv->consumer_secret, &priv->token_key, 
&priv->token_secret))
+               return TRUE;
+
+       /* Otherwise log in to get them */
+       if (required)
+               return gs_ubuntuone_sign_in (&priv->consumer_key, &priv->consumer_secret, &priv->token_key, 
&priv->token_secret, error);
+       else
+               return TRUE;
+}
+
+static gboolean
 refine_reviews (GsPlugin *plugin, GsApp *app, GError **error)
 {
        GPtrArray *sources;
        guint i;
 
+       if (!get_ubuntuone_credentials (plugin, FALSE, error))
+               return FALSE;
+
        /* Skip if already has reviews */
        if (gs_app_get_reviews (app)->len > 0)
                return TRUE;
@@ -712,21 +799,334 @@ refine_reviews (GsPlugin *plugin, GsApp *app, GError **error)
 }
 
 gboolean
-gs_plugin_refine_app (GsPlugin *plugin,
-                     GsApp *app,
-                     GsPluginRefineFlags flags,
-                     GCancellable *cancellable,
-                     GError **error)
-{
-       if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)) 
!= 0) {
-               if (!refine_rating (plugin, app, error))
-                       return FALSE;
+gs_plugin_refine (GsPlugin *plugin,
+                 GList **list,
+                 GsPluginRefineFlags flags,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       GList *l;
+       gboolean ret = TRUE;
+
+       for (l = *list; l != NULL; l = l->next) {
+               GsApp *app = GS_APP (l->data);
+
+               if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)) != 0) {
+                       if (!refine_rating (plugin, app, error))
+                               return FALSE;
+               }
+               if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
+                       if (!refine_reviews (plugin, app, error))
+                               return FALSE;
+               }
        }
-       if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
-               if (!refine_reviews (plugin, app, error))
+
+       return ret;
+}
+
+static void
+add_string_member (JsonBuilder *builder, const gchar *name, const gchar *value)
+{
+       json_builder_set_member_name (builder, name);
+       json_builder_add_string_value (builder, value);
+}
+
+static void
+add_int_member (JsonBuilder *builder, const gchar *name, gint64 value)
+{
+       json_builder_set_member_name (builder, name);
+       json_builder_add_int_value (builder, value);
+}
+
+static gboolean
+set_package_review (GsPlugin *plugin,
+                   GsReview *review,
+                   const gchar *package_name,
+                   GError **error)
+{
+       gint rating;
+       gint n_stars;
+       g_autofree gchar *os_id = NULL, *os_ubuntu_codename = NULL, *language = NULL, *architecture = NULL;
+       g_autoptr(JsonBuilder) request = NULL;
+
+       /* Ubuntu reviews require a summary and description - just make one up for now */
+       rating = gs_review_get_rating (review);
+       if (rating > 80)
+               n_stars = 5;
+       else if (rating > 60)
+               n_stars = 4;
+       else if (rating > 40)
+               n_stars = 3;
+       else if (rating > 20)
+               n_stars = 2;
+       else
+               n_stars = 1;
+
+       os_id = gs_os_release_get_id (error);
+       if (os_id == NULL)
+               return FALSE;
+       os_ubuntu_codename = gs_os_release_get ("UBUNTU_CODENAME", error);
+       if (os_ubuntu_codename == NULL)
+               return FALSE;
+
+       language = get_language (plugin);
+
+       // FIXME: Need to get Apt::Architecture configuration value from APT
+       architecture = g_strdup ("amd64");
+
+       /* Create message for reviews.ubuntu.com */
+       request = json_builder_new ();
+       json_builder_begin_object (request);
+       add_string_member (request, "package_name", package_name);
+       add_string_member (request, "summary", gs_review_get_summary (review));
+       add_string_member (request, "review_text", gs_review_get_text (review));
+       add_string_member (request, "language", language);
+       add_string_member (request, "origin", os_id);
+       add_string_member (request, "distroseries", os_ubuntu_codename);
+       add_string_member (request, "version", gs_review_get_version (review));
+       add_int_member (request, "rating", n_stars);
+       add_string_member (request, "arch_tag", architecture);
+       json_builder_end_object (request);
+
+       return send_review_request (plugin, SOUP_METHOD_POST, "/api/1.0/reviews/", request, TRUE, NULL, 
error);
+}
+
+static gboolean
+set_review_usefulness (GsPlugin *plugin,
+                      const gchar *review_id,
+                      gboolean is_useful,
+                      GError **error)
+{
+       g_autofree gchar *path = NULL;
+
+       if (!get_ubuntuone_credentials (plugin, TRUE, error))
+               return FALSE;
+
+       /* Create message for reviews.ubuntu.com */
+       path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?useful=%s", review_id, is_useful ? 
"True" : "False");
+       return send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, error);
+}
+
+static gboolean
+report_review (GsPlugin *plugin,
+              const gchar *review_id,
+              const gchar *reason,
+              const gchar *text,
+              GError **error)
+{
+       g_autofree gchar *path = NULL;
+
+       if (!get_ubuntuone_credentials (plugin, TRUE, error))
+               return FALSE;
+
+       /* Create message for reviews.ubuntu.com */
+       // FIXME: escape reason / text properly
+       path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?reason=%s&text=%s", review_id, reason, 
text);
+       return send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, error);
+}
+
+static gboolean
+remove_review (GsPlugin *plugin,
+              const gchar *review_id,
+              GError **error)
+{
+       g_autofree gchar *path = NULL;
+
+       if (!get_ubuntuone_credentials (plugin, TRUE, error))
+               return FALSE;
+
+       /* Create message for reviews.ubuntu.com */
+       path = g_strdup_printf ("/api/1.0/reviews/delete/%s/", review_id);
+       return send_review_request (plugin, SOUP_METHOD_POST, path, NULL, TRUE, NULL, error);
+}
+
+gboolean
+gs_plugin_review_submit (GsPlugin *plugin,
+                        GsApp *app,
+                        GsReview *review,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+
+       /* Load database once */
+       if (g_once_init_enter (&priv->db_loaded)) {
+               gboolean ret = load_database (plugin, error);
+               g_once_init_leave (&priv->db_loaded, TRUE);
+               if (!ret)
                        return FALSE;
        }
 
+       if (!get_ubuntuone_credentials (plugin, TRUE, error))
+               return FALSE;
+
+       return set_package_review (plugin,
+                                  review,
+                                  gs_app_get_source_default (app),
+                                  error);
+}
+
+gboolean
+gs_plugin_review_report (GsPlugin *plugin,
+                        GsApp *app,
+                        GsReview *review,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       const gchar *review_id;
+
+       /* Can only modify Ubuntu reviews */
+       review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+       if (review_id == NULL)
+               return TRUE;
+
+       if (!report_review (plugin, review_id, "FIXME: gnome-software", "FIXME: gnome-software", error))
+               return FALSE;
+       gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+       return TRUE;
+}
+
+gboolean
+gs_plugin_review_upvote (GsPlugin *plugin,
+                        GsApp *app,
+                        GsReview *review,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       const gchar *review_id;
+
+       /* Can only modify Ubuntu reviews */
+       review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+       if (review_id == NULL)
+               return TRUE;
+
+       if (!set_review_usefulness (plugin, review_id, TRUE, error))
+               return FALSE;
+       gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+       return TRUE;
+}
+
+gboolean
+gs_plugin_review_downvote (GsPlugin *plugin,
+                          GsApp *app,
+                          GsReview *review,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       const gchar *review_id;
+
+       /* Can only modify Ubuntu reviews */
+       review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+       if (review_id == NULL)
+               return TRUE;
+
+       if (!set_review_usefulness (plugin, review_id, FALSE, error))
+               return FALSE;
+       gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
        return TRUE;
 }
 
+gboolean
+gs_plugin_review_remove (GsPlugin *plugin,
+                        GsApp *app,
+                        GsReview *review,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       const gchar *review_id;
+
+       /* Can only modify Ubuntu reviews */
+       review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+       if (review_id == NULL)
+               return TRUE;
+
+       return remove_review (plugin, review_id, error);
+}
+
+typedef struct {
+       gchar           *package_name;
+       gint             rating;
+} PopularEntry;
+
+static gint
+popular_sqlite_cb (void *data,
+                  gint argc,
+                  gchar **argv,
+                  gchar **col_name)
+{
+       GList **list = data;
+       PopularEntry *entry;
+
+       entry = g_slice_new (PopularEntry);
+       entry->package_name = g_strdup (argv[0]);
+       entry->rating = get_rating (g_ascii_strtoll (argv[1], NULL, 10), g_ascii_strtoll (argv[2], NULL, 10), 
g_ascii_strtoll (argv[3], NULL, 10), g_ascii_strtoll (argv[4], NULL, 10), g_ascii_strtoll (argv[5], NULL, 
10));
+       *list = g_list_prepend (*list, entry);
+
+       return 0;
+}
+
+static gint
+compare_popular_entry (gconstpointer a, gconstpointer b)
+{
+       PopularEntry *ea = a, *eb = b;
+       return eb->rating - ea->rating;
+}
+
+static void
+free_popular_entry (gpointer data)
+{
+       PopularEntry *entry = data;
+       g_free (entry->package_name);
+       g_slice_free (PopularEntry, entry);
+}
+
+gboolean
+gs_plugin_add_popular (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       gint result;
+       GList *entries = NULL, *link;
+       char *error_msg = NULL;
+
+       /* Load database once */
+       if (g_once_init_enter (&priv->db_loaded)) {
+               gboolean ret = load_database (plugin, error);
+               g_once_init_leave (&priv->db_loaded, TRUE);
+               if (!ret)
+                       return FALSE;
+       }
+
+       result = sqlite3_exec (priv->db,
+                              "SELECT package_name, one_star_count, two_star_count, three_star_count, 
four_star_count, five_star_count FROM review_stats",
+                              popular_sqlite_cb,
+                              &entries,
+                              &error_msg);
+       if (result != SQLITE_OK) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "SQL error: %s", error_msg);
+               sqlite3_free (error_msg);
+               return FALSE;
+       }
+
+       entries = g_list_sort (entries, compare_popular_entry);
+       for (link = entries; link; link = link->next) {
+               PopularEntry *entry = link->data;
+               g_autoptr(GsApp) app = NULL;
+
+               /* Need four stars to show */
+               if (entry->rating < 80)
+                       break;
+
+               app = gs_app_new (entry->package_name);
+               gs_app_add_source (app, entry->package_name);
+               gs_app_list_add (list, app);
+       }
+       g_list_free_full (entries, free_popular_entry);
+
+       return TRUE;
+}


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