[gnome-software/wip/rancell/ubuntu-ratings] Can finally download stats from server



commit cb53f10145f94728e00fde14faad2291650e1c6f
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Nov 24 22:44:52 2015 +1300

    Can finally download stats from server

 src/plugins/gs-plugin-ubuntu-reviews.c |  360 ++++++++++++++++++++++----------
 1 files changed, 245 insertions(+), 115 deletions(-)
---
diff --git a/src/plugins/gs-plugin-ubuntu-reviews.c b/src/plugins/gs-plugin-ubuntu-reviews.c
index 8a75284..6e5a203 100644
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@ -36,13 +36,21 @@ struct GsPluginPrivate {
        sqlite3                 *db;
 };
 
+typedef struct {
+       gint64           one_star_count;
+       gint64           two_star_count;
+       gint64           three_star_count;
+       gint64           four_star_count;
+       gint64           five_star_count;
+} Histogram;
+
 /**
  * gs_plugin_get_name:
  */
 const gchar *
 gs_plugin_get_name (void)
 {
-       return "ubuntu-ratings";
+       return "ubuntu-reviews";
 }
 
 #define GS_PLUGIN_UBUNTU_REVIEWS_SERVER                "https://reviews.ubuntu.com/reviews";
@@ -95,11 +103,8 @@ gs_plugin_destroy (GsPlugin *plugin)
                g_object_unref (plugin->priv->session);
 }
 
-/**
- * gs_plugin_setup_networking:
- */
 static gboolean
-gs_plugin_setup_networking (GsPlugin *plugin, GError **error)
+setup_networking (GsPlugin *plugin, GError **error)
 {
        /* already set up */
        if (plugin->priv->session != NULL)
@@ -132,38 +137,59 @@ gs_plugin_app_set_rating (GsPlugin *plugin,
        return FALSE;
 }
 
-/**
- * gs_plugin_ubuntu_reviews_timestamp_cb:
- **/
 static gint
-gs_plugin_ubuntu_reviews_timestamp_cb (void *data, gint argc,
-                                       gchar **argv, gchar **col_name)
+get_timestamp_sqlite_cb (void *data, gint argc,
+                        gchar **argv, gchar **col_name)
 {
        gint64 *timestamp = (gint64 *) data;
        *timestamp = g_ascii_strtoll (argv[0], NULL, 10);
        return 0;
 }
 
+static gboolean
+set_package_stats (GsPlugin *plugin,
+                  const gchar *package_name,
+                  Histogram *histogram,
+                  GError **error)
+{
+       char *error_msg = NULL;
+       gint result;
+       g_autofree gchar *statement = NULL;
+
+       statement = g_strdup_printf ("INSERT OR REPLACE INTO review_stats (package_name, "
+                                    "one_star_count, two_star_count, three_star_count, "
+                                     "four_star_count, five_star_count) "
+                                    "VALUES ('%s', '%" G_GINT64_FORMAT "', '%" G_GINT64_FORMAT"', '%" 
G_GINT64_FORMAT "', '%" G_GINT64_FORMAT "', '%" G_GINT64_FORMAT "');",
+                                    package_name, histogram->one_star_count, histogram->two_star_count,
+                                    histogram->three_star_count, histogram->four_star_count, 
histogram->five_star_count);
+       result = sqlite3_exec (plugin->priv->db, statement, NULL, NULL, &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;
+       }
+
+       return TRUE;
+}
 
-/**
- * gs_plugin_ubuntu_reviews_set_timestamp:
- */
 static gboolean
-gs_plugin_ubuntu_reviews_set_timestamp (GsPlugin *plugin,
-                                        const gchar *type,
-                                        GError **error)
+set_timestamp (GsPlugin *plugin,
+              const gchar *type,
+              GError **error)
 {
        char *error_msg = NULL;
-       gint rc;
+       gint result;
        g_autofree gchar *statement = NULL;
 
-       /* insert the entry */
        statement = g_strdup_printf ("INSERT OR REPLACE INTO timestamps (key, value) "
                                     "VALUES ('%s', '%" G_GINT64_FORMAT "');",
                                     type,
                                     g_get_real_time () / G_USEC_PER_SEC);
-       rc = sqlite3_exec (plugin->priv->db, statement, NULL, NULL, &error_msg);
-       if (rc != SQLITE_OK) {
+       result = sqlite3_exec (plugin->priv->db, statement, NULL, NULL, &error_msg);
+       if (result != SQLITE_OK) {
                g_set_error (error,
                             GS_PLUGIN_ERROR,
                             GS_PLUGIN_ERROR_FAILED,
@@ -174,32 +200,106 @@ gs_plugin_ubuntu_reviews_set_timestamp (GsPlugin *plugin,
        return TRUE;
 }
 
-/**
- * gs_plugin_ubuntu_review_stats_download:
- **/
 static gboolean
-gs_plugin_ubuntu_review_stats_download (GsPlugin *plugin, GError **error)
+parse_histogram (const gchar *text, Histogram *histogram)
+{
+       JsonParser *parser = NULL;
+       JsonArray *array;
+       gboolean result = FALSE;
+
+       /* Histogram is a five element JSON array, e.g. "[1, 3, 5, 8, 4]" */
+       parser = json_parser_new ();
+       if (!json_parser_load_from_data (parser, text, -1, NULL))
+               goto out;
+       if (!JSON_NODE_HOLDS_ARRAY (json_parser_get_root (parser)))
+               goto out;
+       array = json_node_get_array (json_parser_get_root (parser));
+       if (json_array_get_length (array) != 5)
+               goto out;
+       histogram->one_star_count = json_array_get_int_element (array, 0);
+       histogram->two_star_count = json_array_get_int_element (array, 1);
+       histogram->three_star_count = json_array_get_int_element (array, 2);
+       histogram->four_star_count = json_array_get_int_element (array, 3);
+       histogram->five_star_count = json_array_get_int_element (array, 4);
+       result = TRUE;
+
+out:
+       g_clear_object (&parser);
+
+       return result;
+}
+
+static gboolean
+parse_review_entry (JsonNode *node, const gchar **package_name, Histogram *histogram)
+{
+       JsonObject *object;
+       const gchar *name = NULL, *histogram_text = NULL;
+
+       if (!JSON_NODE_HOLDS_OBJECT (node))
+               return FALSE;
+
+       object = json_node_get_object (node);
+
+       name = json_object_get_string_member (object, "package_name");
+       histogram_text = json_object_get_string_member (object, "histogram");
+       if (!name || !histogram_text)
+               return FALSE;
+
+       if (!parse_histogram (histogram_text, histogram))
+               return FALSE;
+       *package_name = name;
+
+       return TRUE;
+}
+
+static gboolean
+parse_review_entries (GsPlugin *plugin, const gchar *text, GError **error)
+{
+       JsonParser *parser = NULL;
+       JsonArray *array;
+       gint i;
+       gboolean result = FALSE;
+
+       parser = json_parser_new ();
+       if (!json_parser_load_from_data (parser, text, -1, error))
+               goto out;
+       if (!JSON_NODE_HOLDS_ARRAY (json_parser_get_root (parser)))
+               goto out;
+       array = json_node_get_array (json_parser_get_root (parser));
+       for (i = 0; i < json_array_get_length (array); i++) {
+               const gchar *package_name;
+               Histogram histogram;
+
+               /* Read in from JSON... (skip bad entries) */
+               if (!parse_review_entry (json_array_get_element (array, i), &package_name, &histogram))
+                       continue;
+
+               /* ...write into the database (abort everything if can't write) */
+               if (!set_package_stats (plugin, package_name, &histogram, error))
+                       goto out;
+       }
+       result = TRUE;
+
+out:
+       g_clear_object (&parser);
+
+       return result;
+}
+
+static gboolean
+download_review_stats (GsPlugin *plugin, GError **error)
 {
-       JsonParser *parser;
-       JsonReader *reader;
-       gdouble count_sum = 0;
-       guint i;
        guint status_code;
        g_autofree gchar *uri = NULL;
        g_autoptr(SoupMessage) msg = NULL;
-       g_autoptr(GPtrArray) items = NULL;
        g_auto(GStrv) split = NULL;
 
-       /* create the GET data */
+       /* Get the review stats using HTTP */
        uri = g_strdup_printf ("%s/api/1.0/review-stats/any/any/",
                               GS_PLUGIN_UBUNTU_REVIEWS_SERVER);
        msg = soup_message_new (SOUP_METHOD_GET, uri);
-
-       /* ensure networking is set up */
-       if (!gs_plugin_setup_networking (plugin, error))
+       if (!setup_networking (plugin, error))
                return FALSE;
-
-       /* set sync request */
        status_code = soup_session_send_message (plugin->priv->session, msg);
        if (status_code != SOUP_STATUS_OK) {
                g_set_error (error,
@@ -210,59 +310,21 @@ gs_plugin_ubuntu_review_stats_download (GsPlugin *plugin, GError **error)
                return FALSE;
        }
 
-       /* process the JSON data */
-       parser = json_parser_new ();
-       if (!json_parser_load_from_data (parser, msg->response_body->data, -1, error)) {
-               g_object_unref (parser);
+       /* Extract the stats from the data */
+       if (!parse_review_entries (plugin, msg->response_body->data, error))
                return FALSE;
-       }
-       reader = json_reader_new (json_parser_get_root (parser));
-       if (!json_reader_is_array (reader)) {
-               g_object_unref (reader);
-               g_object_unref (parser);
-               return FALSE;
-       }
-       for (i = 0; i < json_reader_count_elements (reader); i++) {
-               json_reader_read_element (reader, i);
-               if (json_reader_is_object (reader)) {
-                       const gchar *package_name;
-                       gint rating;
-
-                       json_reader_read_member (reader, "package_name");
-                       package_name = json_reader_get_string_value (reader);
-                       json_reader_end_member (reader);
 
-                       json_reader_read_member (reader, "ratings_average");
-                       /* convert star ratings *->10, **->30, ***->50, ****->70, *****->90 */
-                       rating = round (20 * g_ascii_strtod (json_reader_get_string_value (reader), NULL) - 
10);
-                       json_reader_end_member (reader);
-               }
-               json_reader_end_element (reader);
-       }
-       g_object_unref (reader);
-
-       /* no suitable data? */
-       if (items->len == 0) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
-                                    "Failed to get data from Ubuntu reviews");
-               return FALSE;
-       }
-
-       return FALSE;
+       /* Record the time we downloaded it */
+       return set_timestamp (plugin, "mtime", error);
 }
 
-/**
- * gs_plugin_ubuntu_ratings_load_db
- **/
 static gboolean
-gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
+load_database (GsPlugin *plugin, GError **error)
 {
        const gchar *statement;
        gboolean rebuild_ratings = FALSE;
        char *error_msg = NULL;
-       gint rc;
+       gint result;
        gint64 mtime = 0;
        gint64 now;
        g_autoptr(GError) error_local = NULL;
@@ -270,8 +332,8 @@ gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
        g_debug ("trying to open database '%s'", plugin->priv->db_path);
        if (!gs_mkdir_parent (plugin->priv->db_path, error))
                return FALSE;
-       rc = sqlite3_open (plugin->priv->db_path, &plugin->priv->db);
-       if (rc != SQLITE_OK) {
+       result = sqlite3_open (plugin->priv->db_path, &plugin->priv->db);
+       if (result != SQLITE_OK) {
                g_set_error (error,
                             GS_PLUGIN_ERROR,
                             GS_PLUGIN_ERROR_FAILED,
@@ -280,36 +342,37 @@ gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
                return FALSE;
        }
 
-       /* we don't need to keep doing fsync */
+       /* We don't need to keep doing fsync */
        sqlite3_exec (plugin->priv->db, "PRAGMA synchronous=OFF",
                      NULL, NULL, NULL);
 
-       /* create ratings if required */
-       rc = sqlite3_exec (plugin->priv->db,
-                          "SELECT vote_count FROM ratings LIMIT 1",
-                          gs_plugin_ubuntu_reviews_timestamp_cb, &mtime,
-                          &error_msg);
-       if (rc != SQLITE_OK) {
+       /* Create a table to store the stats */
+       result = sqlite3_exec (plugin->priv->db,
+                              "SELECT one_star_count FROM review_stats LIMIT 1",
+                              NULL, NULL,
+                              &error_msg);
+       if (result != SQLITE_OK) {
                g_debug ("creating table to repair: %s", error_msg);
                sqlite3_free (error_msg);
-               statement = "DROP TABLE IF EXISTS ratings;";
+               statement = "DROP TABLE IF EXISTS review_stats;";
                sqlite3_exec (plugin->priv->db, statement, NULL, NULL, NULL);
-               statement = "CREATE TABLE ratings ("
-                           "pkgname TEXT PRIMARY KEY,"
-                           "rating INTEGER DEFAULT 0,"
-                           "vote_count INTEGER DEFAULT 0,"
-                           "user_count INTEGER DEFAULT 0,"
-                           "confidence INTEGER DEFAULT 0);";
+               statement = "CREATE TABLE review_stats ("
+                           "package_name TEXT PRIMARY KEY,"
+                           "one_star_count INTEGER DEFAULT 0,"
+                           "two_star_count INTEGER DEFAULT 0,"
+                           "three_star_count INTEGER DEFAULT 0,"
+                           "four_star_count INTEGER DEFAULT 0,"
+                           "five_star_count INTEGER DEFAULT 0);";
                sqlite3_exec (plugin->priv->db, statement, NULL, NULL, NULL);
                rebuild_ratings = TRUE;
        }
 
-       /* create timestamps if required */
-       rc = sqlite3_exec (plugin->priv->db,
-                          "SELECT value FROM timestamps WHERE key = 'mtime' LIMIT 1",
-                          gs_plugin_ubuntu_reviews_timestamp_cb, &mtime,
-                          &error_msg);
-       if (rc != SQLITE_OK) {
+       /* Create a table to store timestamps */
+       result = sqlite3_exec (plugin->priv->db,
+                              "SELECT value FROM timestamps WHERE key = 'mtime' LIMIT 1",
+                              get_timestamp_sqlite_cb, &mtime,
+                              &error_msg);
+       if (result != SQLITE_OK) {
                g_debug ("creating table to repair: %s", error_msg);
                sqlite3_free (error_msg);
                statement = "CREATE TABLE timestamps ("
@@ -317,17 +380,16 @@ gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
                            "value INTEGER DEFAULT 0);";
                sqlite3_exec (plugin->priv->db, statement, NULL, NULL, NULL);
 
-               /* reset the timestamp */
-               if (!gs_plugin_ubuntu_reviews_set_timestamp (plugin, "ctime", error))
+               /* Set the time of database creation */
+               if (!set_timestamp (plugin, "ctime", error))
                        return FALSE;
        }
 
-       /* no data */
+       /* Download data if we have none or it is out of date */
        now = g_get_real_time () / G_USEC_PER_SEC;
        if (mtime == 0 || rebuild_ratings) {
                g_debug ("No ubuntu-reviews data");
-               /* this should not be fatal */
-               if (!gs_plugin_ubuntu_review_stats_download (plugin, &error_local)) {
+               if (!download_review_stats (plugin, &error_local)) {
                        g_warning ("Failed to get ubuntu-reviews data: %s",
                                   error_local->message);
                        return TRUE;
@@ -336,7 +398,7 @@ gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
                g_debug ("ubuntu-reviews data was %" G_GINT64_FORMAT
                         " days old, so regetting",
                         (now - mtime) / ( 60 * 60 * 24));
-               if (!gs_plugin_ubuntu_review_stats_download (plugin, error))
+               if (!download_review_stats (plugin, error))
                        return FALSE;
        } else {
                g_debug ("ubuntu-reviews data %" G_GINT64_FORMAT
@@ -346,6 +408,60 @@ gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
        return TRUE;
 }
 
+static gint
+get_rating_sqlite_cb (void *data,
+                     gint argc,
+                     gchar **argv,
+                     gchar **col_name)
+{
+       Histogram *histogram = (Histogram *) data;
+       histogram->one_star_count = g_ascii_strtoll (argv[0], NULL, 10);
+       histogram->two_star_count = g_ascii_strtoll (argv[1], NULL, 10);
+       histogram->three_star_count = g_ascii_strtoll (argv[2], NULL, 10);
+       histogram->four_star_count = g_ascii_strtoll (argv[3], NULL, 10);
+       histogram->five_star_count = g_ascii_strtoll (argv[4], NULL, 10);
+       return 0;
+}
+
+static gboolean
+get_rating (GsPlugin *plugin,
+           const gchar *package_name,
+           gint *rating,
+           GError **error)
+{
+       Histogram histogram = { 0, 0, 0, 0, 0 };
+       gchar *error_msg = NULL;
+       gint result, n_ratings;
+       g_autofree gchar *statement = NULL;
+
+       /* Get histogram from the database */
+       statement = g_strdup_printf ("SELECT one_star_count, two_star_count, three_star_count, 
four_star_count, five_star_count FROM review_stats "
+                                    "WHERE package_name = '%s'", package_name);
+       result = sqlite3_exec (plugin->priv->db,
+                              statement,
+                              get_rating_sqlite_cb,
+                              &histogram,
+                              &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;
+       }
+
+       /* Convert to a rating */
+       // FIXME: Convert to a Wilson score
+       n_ratings = histogram.one_star_count + histogram.two_star_count + histogram.three_star_count + 
histogram.four_star_count + histogram.five_star_count;
+       if (n_ratings == 0)
+               *rating = -1;
+       else
+               *rating = ((histogram.one_star_count * 10) + (histogram.two_star_count * 30) + 
(histogram.three_star_count * 50) + (histogram.four_star_count * 70) + (histogram.five_star_count * 90)) / 
n_ratings;
+
+       return TRUE;
+}
+
 /**
  * gs_plugin_refine:
  */
@@ -358,18 +474,13 @@ gs_plugin_refine (GsPlugin *plugin,
 {
        GList *l;
 
-       for (l = *list; l != NULL; l = l->next) {
-               GsApp *app = GS_APP (l->data);
-                g_printerr ("  %s\n", gs_app_get_id (app));
-        }
-
        /* We only update ratings */
        if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING) == 0)
                return TRUE;
 
        /* Load once */
        if (g_once_init_enter (&plugin->priv->loaded)) {
-               gboolean ret = gs_plugin_ubuntu_review_stats_load_db (plugin, error);
+               gboolean ret = load_database (plugin, error);
                g_once_init_leave (&plugin->priv->loaded, TRUE);
                if (!ret)
                        return FALSE;
@@ -377,10 +488,29 @@ gs_plugin_refine (GsPlugin *plugin,
 
        for (l = *list; l != NULL; l = l->next) {
                GsApp *app = GS_APP (l->data);
-               if (gs_app_get_id (app) == NULL)
-                       continue;
+               GPtrArray *sources;
+               guint i;
+
                if (gs_app_get_rating (app) != -1)
                        continue;
+
+               sources = gs_app_get_sources (app);
+               for (i = 0; i < sources->len; i++) {
+                       const gchar *package_name;
+                       gint rating;
+                       gboolean ret;
+
+                       package_name = g_ptr_array_index (sources, i);
+                       ret = get_rating (plugin, package_name, &rating, error);
+                       if (!ret)
+                               return FALSE;
+                       if (rating != -1) {
+                               g_debug ("ubuntu-reviews setting rating on %s to %i%%",
+                                        package_name, rating);
+                               gs_app_set_rating (app, rating);
+                               gs_app_set_rating_kind (app, GS_APP_RATING_KIND_SYSTEM);
+                       }
+               }
        }
 
        return TRUE;


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