[gnome-software/wip/rancell/ubuntu-ratings] Download some ratings
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/rancell/ubuntu-ratings] Download some ratings
- Date: Thu, 12 Nov 2015 02:30:04 +0000 (UTC)
commit 971b9a392085e2064eb2edfca5c4304dca74ec67
Author: Robert Ancell <robert ancell canonical com>
Date: Thu Nov 12 11:45:08 2015 +1300
Download some ratings
configure.ac | 1 +
src/plugins/Makefile.am | 11 +-
src/plugins/gs-plugin-ubuntu-ratings.c | 120 ----------
src/plugins/gs-plugin-ubuntu-reviews.c | 385 ++++++++++++++++++++++++++++++++
4 files changed, 392 insertions(+), 125 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 233133f..b001017 100644
--- a/configure.ac
+++ b/configure.ac
@@ -67,6 +67,7 @@ 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(POLKIT, polkit-gobject-1)
+PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0 >= 0.12)
AC_PATH_PROG(APPSTREAM_UTIL, [appstream-util], [unfound])
AC_ARG_ENABLE(man,
[AS_HELP_STRING([--enable-man],
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 12bff16..ba38d96 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -11,6 +11,7 @@ AM_CPPFLAGS = \
$(SQLITE_CFLAGS) \
$(FWUPD_CFLAGS) \
$(LIMBA_CFLAGS) \
+ $(JSON_GLIB_CFLAGS) \
-DBINDIR=\"$(bindir)\" \
-DDATADIR=\"$(datadir)\" \
-DGS_MODULESETDIR=\"$(datadir)/gnome-software/modulesets.d\" \
@@ -34,7 +35,7 @@ plugin_LTLIBRARIES = \
libgs_plugin_menu-spec-categories.la \
libgs_plugin_menu-spec-refine.la \
libgs_plugin_local-ratings.la \
- libgs_plugin_ubuntu-ratings.la \
+ libgs_plugin_ubuntu-reviews.la \
libgs_plugin_fedora_tagger_ratings.la \
libgs_plugin_fedora_tagger_usage.la \
libgs_plugin_epiphany.la \
@@ -131,10 +132,10 @@ libgs_plugin_local_ratings_la_LIBADD = $(GS_PLUGIN_LIBS) $(SQLITE_LIBS)
libgs_plugin_local_ratings_la_LDFLAGS = -module -avoid-version
libgs_plugin_local_ratings_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
-libgs_plugin_ubuntu_ratings_la_SOURCES = gs-plugin-ubuntu-ratings.c
-libgs_plugin_ubuntu_ratings_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS)
-libgs_plugin_ubuntu_ratings_la_LDFLAGS = -module -avoid-version
-libgs_plugin_ubuntu_ratings_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARNINGFLAGS_C)
+libgs_plugin_ubuntu_reviews_la_SOURCES = gs-plugin-ubuntu-reviews.c
+libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(SQLITE_LIBS) $(JSON_GLIB_LIBS)
+libgs_plugin_ubuntu_reviews_la_LDFLAGS = -module -avoid-version
+libgs_plugin_ubuntu_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARNINGFLAGS_C)
libgs_plugin_packagekit_la_SOURCES = \
gs-plugin-packagekit.c \
diff --git a/src/plugins/gs-plugin-ubuntu-reviews.c b/src/plugins/gs-plugin-ubuntu-reviews.c
new file mode 100644
index 0000000..cee24c4
--- /dev/null
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@ -0,0 +1,385 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Robert Ancell <robert ancell canonical com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <libsoup/soup.h>
+#include <sqlite3.h>
+#include <json-glib/json-glib.h>
+
+#include <gs-plugin.h>
+#include <gs-utils.h>
+
+struct GsPluginPrivate {
+ SoupSession *session;
+ gchar *db_path;
+ gsize loaded;
+ sqlite3 *db;
+};
+
+/**
+ * gs_plugin_get_name:
+ */
+const gchar *
+gs_plugin_get_name (void)
+{
+ return "ubuntu-ratings";
+}
+
+#define GS_PLUGIN_UBUNTU_REVIEWS_SERVER "https://reviews.ubuntu.com/reviews"
+
+/* 3 months */
+#define GS_PLUGIN_UBUNTU_REVIEWS_AGE_MAX (60 * 60 * 24 * 7 * 4 * 3)
+
+/**
+ * gs_plugin_initialize:
+ */
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ /* create private area */
+ plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+
+ plugin->priv->db_path = g_build_filename (g_get_user_data_dir (),
+ "gnome-software",
+ "ubuntu-reviews.db",
+ NULL);
+
+ /* check that we are running on Ubuntu */
+ if (!gs_plugin_check_distro_id (plugin, "ubuntu")) {
+ gs_plugin_set_enabled (plugin, FALSE);
+ g_debug ("disabling '%s' as we're not Ubuntu", plugin->name);
+ return;
+ }
+}
+
+/**
+ * gs_plugin_get_deps:
+ */
+const gchar **
+gs_plugin_get_deps (GsPlugin *plugin)
+{
+ static const gchar *deps[] = { NULL };
+ return deps;
+}
+
+/**
+ * gs_plugin_destroy:
+ */
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ g_free (plugin->priv->db_path);
+ if (plugin->priv->db != NULL)
+ sqlite3_close (plugin->priv->db);
+ if (plugin->priv->session != NULL)
+ g_object_unref (plugin->priv->session);
+}
+
+/**
+ * gs_plugin_setup_networking:
+ */
+static gboolean
+gs_plugin_setup_networking (GsPlugin *plugin, GError **error)
+{
+ /* already set up */
+ if (plugin->priv->session != NULL)
+ return TRUE;
+
+ /* set up a session */
+ plugin->priv->session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
+ "gnome-software",
+ NULL);
+ if (plugin->priv->session == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "%s: failed to setup networking",
+ plugin->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * gs_plugin_app_set_rating:
+ */
+gboolean
+gs_plugin_app_set_rating (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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)
+{
+ gint64 *timestamp = (gint64 *) data;
+ *timestamp = g_ascii_strtoll (argv[0], NULL, 10);
+ return 0;
+}
+
+
+/**
+ * gs_plugin_ubuntu_reviews_set_timestamp:
+ */
+static gboolean
+gs_plugin_ubuntu_reviews_set_timestamp (GsPlugin *plugin,
+ const gchar *type,
+ GError **error)
+{
+ char *error_msg = NULL;
+ gint rc;
+ 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) {
+ 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_review_stats_download:
+ **/
+static gboolean
+gs_plugin_ubuntu_review_stats_download (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 */
+ 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))
+ 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,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Failed to download Ubuntu reviews dump: %s",
+ soup_status_get_phrase (status_code));
+ 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);
+ 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;
+ gdouble 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");
+ rating = g_ascii_strtod (json_reader_get_string_value (reader), NULL);
+ 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;
+}
+
+/**
+ * gs_plugin_ubuntu_ratings_load_db
+ **/
+static gboolean
+gs_plugin_ubuntu_review_stats_load_db (GsPlugin *plugin, GError **error)
+{
+ const gchar *statement;
+ gboolean rebuild_ratings = FALSE;
+ char *error_msg = NULL;
+ gint rc;
+ gint64 mtime = 0;
+ gint64 now;
+ g_autoptr(GError) error_local = NULL;
+
+ 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) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Can't open ubuntu-reviews database: %s",
+ sqlite3_errmsg (plugin->priv->db));
+ return FALSE;
+ }
+
+ /* 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) {
+ g_debug ("creating table to repair: %s", error_msg);
+ sqlite3_free (error_msg);
+ statement = "DROP TABLE IF EXISTS ratings;";
+ 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);";
+ 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) {
+ g_debug ("creating table to repair: %s", error_msg);
+ sqlite3_free (error_msg);
+ statement = "CREATE TABLE timestamps ("
+ "key TEXT PRIMARY KEY,"
+ "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))
+ return FALSE;
+ }
+
+ /* no data */
+ 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)) {
+ g_warning ("Failed to get ubuntu-reviews data: %s",
+ error_local->message);
+ return TRUE;
+ }
+ } else if (now - mtime > GS_PLUGIN_UBUNTU_REVIEWS_AGE_MAX) {
+ 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))
+ return FALSE;
+ } else {
+ g_debug ("ubuntu-reviews data %" G_GINT64_FORMAT
+ " days old, so no need to redownload",
+ (now - mtime) / ( 60 * 60 * 24));
+ }
+ return TRUE;
+}
+
+/**
+ * gs_plugin_refine:
+ */
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+ GList **list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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);
+ g_once_init_leave (&plugin->priv->loaded, TRUE);
+ if (!ret)
+ return FALSE;
+ }
+
+ for (l = *list; l != NULL; l = l->next) {
+ GsApp *app = GS_APP (l->data);
+ if (gs_app_get_id (app) == NULL)
+ continue;
+ if (gs_app_get_rating (app) != -1)
+ continue;
+ }
+
+ return TRUE;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]