[gnome-software/wip/hughsie/steam: 2/2] Show all installed and available Steam games



commit 1d0049e53dbcd5a37278402345e238862de1f017
Author: Richard Hughes <richard hughsie com>
Date:   Thu Oct 1 14:20:32 2015 +0100

    Show all installed and available Steam games
    
    This requires the user have already installed, and registered with, the linux
    steam client.

 src/plugins/Makefile.am       |    9 +
 src/plugins/gs-plugin-steam.c |  801 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 810 insertions(+), 0 deletions(-)
---
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 713f3c7..b821b30 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -28,6 +28,7 @@ noinst_LTLIBRARIES =                                  \
 
 plugindir = $(libdir)/gs-plugins-${GS_PLUGIN_API_VERSION}
 plugin_LTLIBRARIES =                                   \
+       libgs_plugin_steam.la                   \
        libgs_plugin_appstream.la                       \
        libgs_plugin_hardcoded-featured.la              \
        libgs_plugin_moduleset.la                       \
@@ -58,6 +59,14 @@ libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_dummy_la_LDFLAGS = -module -avoid-version
 libgs_plugin_dummy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 
+libgs_plugin_steam_la_SOURCES =                                \
+       gs-html-utils.c                                 \
+       gs-html-utils.h                                 \
+       gs-plugin-steam.c
+libgs_plugin_steam_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_steam_la_LDFLAGS = -module -avoid-version
+libgs_plugin_steam_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+
 libgs_plugin_fedora_tagger_ratings_la_SOURCES = gs-plugin-fedora-tagger-ratings.c
 libgs_plugin_fedora_tagger_ratings_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(SQLITE_LIBS)
 libgs_plugin_fedora_tagger_ratings_la_LDFLAGS = -module -avoid-version
diff --git a/src/plugins/gs-plugin-steam.c b/src/plugins/gs-plugin-steam.c
new file mode 100644
index 0000000..8e17757
--- /dev/null
+++ b/src/plugins/gs-plugin-steam.c
@@ -0,0 +1,801 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Richard Hughes <richard hughsie 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 <gs-plugin.h>
+#include <string.h>
+#include <libsoup/soup.h>
+
+#include "gs-html-utils.h"
+#include "gs-utils.h"
+
+struct GsPluginPrivate {
+       SoupSession             *session;
+};
+
+/**
+ * gs_plugin_get_name:
+ */
+const gchar *
+gs_plugin_get_name (void)
+{
+       return "steam";
+}
+
+/**
+ * gs_plugin_initialize:
+ */
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+       plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+}
+
+/**
+ * gs_plugin_destroy:
+ */
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+       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_steam_html_download:
+ */
+static gchar *
+gs_plugin_steam_html_download (GsPlugin *plugin, const gchar *uri, GError **error)
+{
+       guint status_code;
+       g_autoptr(GInputStream) stream = NULL;
+       g_autoptr(SoupMessage) msg = NULL;
+
+       /* create the GET data */
+       msg = soup_message_new (SOUP_METHOD_GET, uri);
+       if (msg == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "%s is not a valid URL", uri);
+               return NULL;
+       }
+
+       /* ensure networking is set up */
+       if (!gs_plugin_setup_networking (plugin, error))
+               return NULL;
+
+       /* 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 icon %s: %s",
+                            uri, soup_status_get_phrase (status_code));
+               return NULL;
+       }
+
+       /* return raw HTML */
+       return g_strndup (msg->response_body->data, msg->response_body->length);
+}
+
+/**
+ * gs_plugin_get_deps:
+ */
+const gchar **
+gs_plugin_get_deps (GsPlugin *plugin)
+{
+       static const gchar *deps[] = {
+               "appstream",            /* need metadata */
+               NULL };
+       return deps;
+}
+
+typedef enum {
+       GS_PLUGIN_STEAM_TOKEN_START             = 0x00,
+       GS_PLUGIN_STEAM_TOKEN_STRING            = 0x01,
+       GS_PLUGIN_STEAM_TOKEN_INTEGER           = 0x02,
+       GS_PLUGIN_STEAM_TOKEN_END               = 0x08,
+       GS_PLUGIN_STEAM_TOKEN_LAST,
+} GsPluginSteamToken;
+
+/**
+ * gs_plugin_steam_token_kind_to_str:
+ **/
+static const gchar *
+gs_plugin_steam_token_kind_to_str (guint8 data)
+{
+       static gchar tmp[2] = { 0x00, 0x00 };
+
+       if (data == GS_PLUGIN_STEAM_TOKEN_START)
+               return "[SRT]";
+       if (data == GS_PLUGIN_STEAM_TOKEN_STRING)
+               return "[STR]";
+       if (data == GS_PLUGIN_STEAM_TOKEN_INTEGER)
+               return "[INT]";
+       if (data == GS_PLUGIN_STEAM_TOKEN_END)
+               return "[END]";
+
+       /* guess */
+       if (data == 0x03)
+               return "[ETX]";
+       if (data == 0x04)
+               return "[EOT]";
+       if (data == 0x05)
+               return "[ENQ]";
+       if (data == 0x06)
+               return "[ACK]";
+       if (data == 0x07)
+               return "[BEL]";
+       if (data == 0x09)
+               return "[SMI]";
+
+       /* printable */
+       if (g_ascii_isprint (data)) {
+               tmp[0] = data;
+               return tmp;
+       }
+       return "[?]";
+}
+
+/**
+ * gs_plugin_steam_consume_uint32:
+ **/
+static guint32
+gs_plugin_steam_consume_uint32 (guint8 *data, gsize data_len, guint *idx)
+{
+       guint32 tmp = *((guint32 *) &data[*idx + 1]);
+       *idx += 4;
+       return tmp;
+}
+
+/**
+ * gs_plugin_steam_consume_string:
+ **/
+static const gchar *
+gs_plugin_steam_consume_string (guint8 *data, gsize data_len, guint *idx)
+{
+       const gchar *tmp;
+
+       /* this may be an empty string */
+       tmp = (const gchar *) &data[*idx+1];
+       if (tmp[0] == '\0') {
+               (*idx)++;
+               return NULL;
+       }
+       *idx += strlen (tmp) + 1;
+       return tmp;
+}
+
+/**
+ * gs_plugin_steam_find_next_sync_point:
+ **/
+static void
+gs_plugin_steam_find_next_sync_point (guint8 *data, gsize data_len, guint *idx)
+{
+       guint i;
+       for (i = *idx; i < data_len; i++) {
+               if (memcmp (&data[i], "\0\x02\0common\0", 8) == 0) {
+                       *idx = i - 1;
+                       return;
+               }
+       }
+       *idx = 0xfffffffe;
+}
+
+/**
+ * gs_plugin_steam_add_app:
+ **/
+static GHashTable *
+gs_plugin_steam_add_app (GPtrArray *apps)
+{
+       GHashTable *app;
+       app = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    g_free, (GDestroyNotify) g_variant_unref);
+       g_ptr_array_add (apps, app);
+       return app;
+}
+
+/**
+ * gs_plugin_steam_parse_appinfo_file:
+ **/
+static GPtrArray *
+gs_plugin_steam_parse_appinfo_file (const gchar *filename, GError **error)
+{
+       GPtrArray *apps;
+       GHashTable *app = NULL;
+       const gchar *tmp;
+       guint8 *data = NULL;
+       gsize data_len = 0;
+       guint i = 0;
+       gboolean debug =  g_getenv ("GS_PLUGIN_STEAM_DEBUG") != NULL;
+
+       /* load file */
+       if (!g_file_get_contents (filename, (gchar **) &data, &data_len, error))
+               return NULL;
+
+       /* a GPtrArray of GHashTable */
+       apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_hash_table_unref);
+
+       /* find the first application and avoid header */
+       gs_plugin_steam_find_next_sync_point (data, data_len, &i);
+       for (i = i + 1; i < data_len; i++) {
+               if (debug)
+                       g_debug ("%04x {0x%02x} %s", i, data[i], gs_plugin_steam_token_kind_to_str (data[i]));
+               if (data[i] == GS_PLUGIN_STEAM_TOKEN_START) {
+
+                       /* this is a new application/game */
+                       if (data[i+1] == 0x02) {
+                               /* reset */
+                               app = gs_plugin_steam_add_app (apps);
+                               i++;
+                               continue;
+                       }
+
+                       /* new group */
+                       if (g_ascii_isprint (data[i+1])) {
+                               tmp = gs_plugin_steam_consume_string (data, data_len, &i);
+                               if (debug)
+                                       g_debug ("[%s] {", tmp);
+                               continue;
+                       }
+
+                       /* something went wrong */
+                       if (debug)
+                               g_debug ("CORRUPTION DETECTED");
+                       gs_plugin_steam_find_next_sync_point (data, data_len, &i);
+                       continue;
+               }
+               if (data[i] == GS_PLUGIN_STEAM_TOKEN_END) {
+                       if (debug)
+                               g_debug ("}");
+                       continue;
+               }
+               if (data[i] == GS_PLUGIN_STEAM_TOKEN_STRING) {
+                       const gchar *value;
+                       tmp = gs_plugin_steam_consume_string (data, data_len, &i);
+                       value = gs_plugin_steam_consume_string (data, data_len, &i);
+                       if (debug)
+                               g_debug ("\t%s=%s", tmp, value);
+                       if (tmp != NULL && value != NULL) {
+                               if (g_hash_table_lookup (app, tmp) != NULL)
+                                       continue;
+                               g_hash_table_insert (app,
+                                                    g_strdup (tmp),
+                                                    g_variant_new_string (value));
+                       }
+                       continue;
+               }
+               if (data[i] == GS_PLUGIN_STEAM_TOKEN_INTEGER) {
+                       guint32 value;
+                       tmp = gs_plugin_steam_consume_string (data, data_len, &i);
+                       value = gs_plugin_steam_consume_uint32 (data, data_len, &i);
+                       if (debug)
+                               g_debug ("\t%s=%i", tmp, value);
+                       if (tmp != NULL) {
+                               if (g_hash_table_lookup (app, tmp) != NULL)
+                                       continue;
+                               g_hash_table_insert (app,
+                                                    g_strdup (tmp),
+                                                    g_variant_new_uint32 (value));
+                       }
+                       continue;
+               }
+       }
+
+       return apps;
+}
+
+/**
+ * gs_plugin_steam_dump_apps:
+ **/
+static void
+gs_plugin_steam_dump_apps (GPtrArray *apps)
+{
+       guint i;
+       GHashTable *app;
+
+       for (i = 0; i < apps->len; i++) {
+               g_autoptr(GList) keys = NULL;
+               GList *l;
+               app = g_ptr_array_index (apps, i);
+               keys = g_hash_table_get_keys (app);
+               for (l = keys; l != NULL; l = l->next) {
+                       const gchar *tmp;
+                       GVariant *value;
+                       tmp = l->data;
+                       value = g_hash_table_lookup (app, tmp);
+                       if (g_strcmp0 (g_variant_get_type_string (value), "s") == 0)
+                               g_print ("%s=%s\n", tmp, g_variant_get_string (value, NULL));
+                       else if (g_strcmp0 (g_variant_get_type_string (value), "u") == 0)
+                               g_print ("%s=%u\n", tmp, g_variant_get_uint32 (value));
+               }
+               g_print ("\n");
+       }
+}
+
+/**
+ * gs_plugin_steam_capture:
+ *
+ * Returns: A string between @start and @end, or %NULL
+ **/
+static gchar *
+gs_plugin_steam_capture (const gchar *html,
+                        const gchar *start,
+                        const gchar *end,
+                        guint *offset)
+{
+       guint i;
+       guint j;
+       guint start_len;
+       guint end_len;
+
+       /* find @start */
+       start_len = strlen (start);
+       for (i = *offset; html[i] != '\0'; i++) {
+               if (memcmp (&html[i], start, start_len) != 0)
+                       continue;
+               /* find @end */
+               end_len = strlen (end);
+               for (j = i + start_len; html[j] != '\0'; j++) {
+                       if (memcmp (&html[j], end, end_len) != 0)
+                               continue;
+                       *offset = j + end_len;
+                       return g_strndup (&html[i + start_len],
+                                         j - i - start_len);
+               }
+       }
+       return NULL;
+}
+
+/**
+ * gs_plugin_steam_update_screenshots:
+ **/
+static gboolean
+gs_plugin_steam_update_screenshots (AsApp *app, const gchar *html, GError **error)
+{
+       const gchar *gameid_str;
+       gchar *tmp1;
+       guint i = 0;
+       guint idx = 0;
+
+       /* find all the screenshots */
+       gameid_str = as_app_get_metadata_item (app, "X-Steam-GameID");
+       while ((tmp1 = gs_plugin_steam_capture (html, "data-screenshotid=\"", "\"", &i))) {
+               g_autoptr(AsImage) im = NULL;
+               g_autoptr(AsScreenshot) ss = NULL;
+               g_autofree gchar *cdn_uri = NULL;
+
+               /* create an image */
+               im = as_image_new ();
+               as_image_set_kind (im, AS_IMAGE_KIND_SOURCE);
+               cdn_uri = g_strdup_printf ("http://cdn.akamai.steamstatic.com/steam/apps/%s/%s";, gameid_str, 
tmp1);
+               as_image_set_url (im, cdn_uri);
+
+               /* create screenshot with no caption */
+               ss = as_screenshot_new ();
+               as_screenshot_set_kind (ss, idx == 0 ? AS_SCREENSHOT_KIND_DEFAULT :
+                                                      AS_SCREENSHOT_KIND_NORMAL);
+               as_screenshot_add_image (ss, im);
+               as_app_add_screenshot (app, ss);
+               g_free (tmp1);
+
+               /* limit this to a sane number */
+               if (idx++ >= 4)
+                       break;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_steam_update_description:
+ **/
+static gboolean
+gs_plugin_steam_update_description (AsApp *app, const gchar *html, GError **error)
+{
+       guint i = 0;
+       g_autofree gchar *desc = NULL;
+       g_autofree gchar *subsect = NULL;
+       g_autoptr(GError) error_local = NULL;
+
+       /* get the game description div section */
+       subsect = gs_plugin_steam_capture (html,
+                       "<div id=\"game_area_description\" class=\"game_area_description\">",
+                       "</div>", &i);
+
+       /* fall back gracefully */
+       if (subsect == NULL) {
+               subsect = gs_plugin_steam_capture (html,
+                               "<meta name=\"Description\" content=\"",
+                               "\">", &i);
+       }
+       if (subsect == NULL) {
+               g_warning ("Failed to get description for %s [%s]",
+                          as_app_get_name (app, NULL),
+                          as_app_get_id (app));
+               return TRUE;
+       }
+       desc = gs_html_utils_parse_description (subsect, &error_local);
+       if (desc == NULL) {
+               g_warning ("Failed to parse description for %s [%s]: %s",
+                          as_app_get_name (app, NULL),
+                          as_app_get_id (app),
+                          error_local->message);
+               return TRUE;
+       }
+       as_app_set_description (app, NULL, desc);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_steam_update_store_app:
+ **/
+static gboolean
+gs_plugin_steam_update_store_app (GsPlugin *plugin,
+                                 AsStore *store,
+                                 GHashTable *app,
+                                 GError **error)
+{
+       GVariant *tmp;
+       guint32 gameid;
+       gchar *app_id;
+       g_autofree gchar *cache_fn = NULL;
+       g_autofree gchar *gameid_str = NULL;
+       g_autofree gchar *html = NULL;
+       g_autofree gchar *uri = NULL;
+       g_autoptr(AsApp) item = NULL;
+
+       /* this is the key */
+       tmp = g_hash_table_lookup (app, "gameid");
+       if (tmp == NULL)
+               return TRUE;
+       gameid = g_variant_get_uint32 (tmp);
+       app_id = g_strdup_printf ("com.valve.steam-%u.desktop", gameid);
+       g_debug ("processing %s", app_id);
+
+       /* already exists */
+       if (as_store_get_app_by_id (store, app_id) != NULL) {
+               g_debug ("%s already exists, skipping", app_id);
+               return TRUE;
+       }
+
+       /* create application with the gameid as the key */
+       item = as_app_new ();
+       as_app_set_project_license (item, "Steam");
+       as_app_set_id (item, app_id);
+       as_app_add_category (item, "Game");
+       as_app_add_kudo_kind (item, AS_KUDO_KIND_MODERN_TOOLKIT);
+       as_app_set_comment (item, NULL, "Available on Steam");
+
+       /* this is for the GNOME Software plugin */
+       gameid_str = g_strdup_printf ("%" G_GUINT32_FORMAT, gameid);
+       as_app_add_metadata (item, "X-Steam-GameID", gameid_str);
+
+       /* name */
+       tmp = g_hash_table_lookup (app, "name");
+       if (tmp != NULL) {
+               const gchar *name = g_variant_get_string (tmp, NULL);
+               if (g_strstr_len (name, -1, "Dedicated Server") != NULL) {
+                       as_app_add_veto (item, "Dedicated Server");
+               } else {
+                       as_app_set_name (item, NULL, name);
+               }
+       } else {
+               as_app_add_veto (item, "No name");
+       }
+
+       /* oslist */
+       tmp = g_hash_table_lookup (app, "oslist");
+       if (tmp == NULL) {
+               as_app_add_veto (item, "No operating systems listed");
+       } else if (g_strstr_len (g_variant_get_string (tmp, NULL), -1, "linux") == NULL) {
+               as_app_add_veto (item, "No Linux support");
+       }
+
+       /* url: homepage */
+       tmp = g_hash_table_lookup (app, "homepage");
+       if (tmp != NULL)
+               as_app_add_url (item, AS_URL_KIND_HOMEPAGE, g_variant_get_string (tmp, NULL));
+
+       /* developer name */
+       tmp = g_hash_table_lookup (app, "developer");
+       if (tmp != NULL)
+               as_app_set_developer_name (item, NULL, g_variant_get_string (tmp, NULL));
+
+       /* type */
+       tmp = g_hash_table_lookup (app, "type");
+       if (tmp != NULL) {
+               const gchar *kind = g_variant_get_string (tmp, NULL);
+               if (g_strcmp0 (kind, "DLC") == 0 ||
+                   g_strcmp0 (kind, "Config") == 0 ||
+                   g_strcmp0 (kind, "Tool") == 0)
+                       as_app_add_veto (item, "type is %s", kind);
+       }
+
+       /* icons */
+       tmp = g_hash_table_lookup (app, "logo");
+       if (tmp != NULL) {
+               AsIcon *icon = NULL;
+               g_autofree gchar *ic_uri = NULL;
+               ic_uri = g_strdup_printf 
("http://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/%i/%s.jpg";,
+                                         gameid, g_variant_get_string (tmp, NULL));
+               icon = as_icon_new ();
+               as_icon_set_kind (icon, AS_ICON_KIND_REMOTE);
+               as_icon_set_url (icon, ic_uri);
+               as_app_add_icon (item, icon);
+       }
+
+       /* size */
+       tmp = g_hash_table_lookup (app, "maxsize");
+       if (tmp != NULL) {
+               /* string when over 16Gb... :/ */
+               if (g_strcmp0 (g_variant_get_type_string (tmp), "u") == 0) {
+                       g_autofree gchar *val = NULL;
+                       val = g_strdup_printf ("%" G_GUINT32_FORMAT,
+                                              g_variant_get_uint32 (tmp));
+                       as_app_add_metadata (item, "X-Steam-Size", val);
+               } else {
+                       as_app_add_metadata (item, "X-Steam-Size",
+                                            g_variant_get_string (tmp, NULL));
+               }
+       }
+
+       /* don't bother saving apps with failures */
+       if (as_app_get_vetos(item)->len > 0)
+               return TRUE;
+
+       /* download page from the store */
+       cache_fn = g_build_filename (g_get_user_cache_dir (),
+                                    "gnome-software",
+                                    "steam",
+                                    gameid_str,
+                                    NULL);
+       if (g_file_test (cache_fn, G_FILE_TEST_EXISTS)) {
+               if (!g_file_get_contents (cache_fn, &html, NULL, error))
+                       return FALSE;
+       } else {
+               uri = g_strdup_printf ("http://store.steampowered.com/app/%s/";, gameid_str);
+               html = gs_plugin_steam_html_download (plugin, uri, error);
+               if (html == NULL)
+                       return FALSE;
+               if (!gs_mkdir_parent (cache_fn, error))
+                       return FALSE;
+               if (!g_file_set_contents (cache_fn, html, -1, error))
+                       return FALSE;
+       }
+
+       /* get screenshots and descriptions */
+       if (!gs_plugin_steam_update_screenshots (item, html, error))
+               return FALSE;
+       if (!gs_plugin_steam_update_description (item, html, error))
+               return FALSE;
+
+       /* add */
+       as_store_add_app (store, item);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_steam_update_store:
+ */
+static gboolean
+gs_plugin_steam_update_store (GsPlugin *plugin, AsStore *store, GPtrArray *apps, GError **error)
+{
+       guint i;
+       GHashTable *app;
+
+       for (i = 0; i < apps->len; i++) {
+               app = g_ptr_array_index (apps, i);
+               if (!gs_plugin_steam_update_store_app (plugin, store, app, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refresh:
+ */
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+                  guint cache_age,
+                  GsPluginRefreshFlags flags,
+                  GCancellable *cancellable,
+                  GError **error)
+{
+       g_autoptr(AsStore) store = NULL;
+       g_autoptr(GFile) file = NULL;
+       g_autoptr(GPtrArray) apps = NULL;
+       g_autofree gchar *fn = NULL;
+       g_autofree gchar *fn_xml = NULL;
+
+       /* check if exists */
+       fn = g_build_filename (g_get_user_data_dir (),
+                              "Steam", "appcache", "appinfo.vdf", NULL);
+       if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
+               g_debug ("no %s, so skipping", fn);
+               return TRUE;
+       }
+
+       /* test cache age */
+       fn_xml = g_build_filename (g_get_user_data_dir (),
+                                  "app-info", "xmls", "steam.xml.gz", NULL);
+       if (cache_age > 0) {
+               guint tmp;
+               tmp = gs_utils_get_file_age (fn_xml);
+               if (tmp < cache_age) {
+                       g_debug ("%s is only %i seconds old, so ignoring refresh",
+                                fn_xml, tmp);
+                       return TRUE;
+               }
+       }
+
+       /* parse it */
+       apps = gs_plugin_steam_parse_appinfo_file (fn, error);
+       if (apps == NULL)
+               return FALSE;
+
+       /* debug */
+       if (g_getenv ("GS_PLUGIN_STEAM_DEBUG") != NULL)
+               gs_plugin_steam_dump_apps (apps);
+
+       /* load existing AppStream XML */
+       store = as_store_new ();
+       file = g_file_new_for_path (fn_xml);
+       if (g_file_query_exists (file, cancellable)) {
+               if (!as_store_from_file (store, file, NULL, cancellable, error))
+                       return FALSE;
+       }
+
+       /* update any new applications */
+       if (!gs_plugin_steam_update_store (plugin, store, apps, error))
+               return FALSE;
+
+       /* save new file */
+       return as_store_to_file (store, file,
+                                AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
+                                AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
+                                NULL,
+                                error);
+}
+
+/**
+ * gs_plugin_steam_refine_app:
+ */
+static gboolean
+gs_plugin_steam_refine_app (GsPlugin *plugin,
+                           GsApp *app,
+                           GsPluginRefineFlags flags,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       const gchar *gameid;
+       const gchar *tmp;
+
+       /* check is us */
+       gameid = gs_app_get_metadata_item (app, "X-Steam-GameID");
+       if (gameid == NULL)
+               return TRUE;
+
+       /* size */
+       tmp = gs_app_get_metadata_item (app, "X-Steam-Size");
+       if (tmp != NULL) {
+               guint64 sz;
+               sz = g_ascii_strtoull (tmp, NULL, 10);
+               if (sz > 0)
+                       gs_app_set_size (app, sz);
+       }
+
+       /* FIXME */
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine:
+ */
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+                 GList **list,
+                 GsPluginRefineFlags flags,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       GList *l;
+       GsApp *app;
+
+       for (l = *list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               if (!gs_plugin_steam_refine_app (plugin, app, flags,
+                                                cancellable, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_app_install:
+ */
+gboolean
+gs_plugin_app_install (GsPlugin *plugin, GsApp *app,
+                      GCancellable *cancellable, GError **error)
+{
+       const gchar *gameid;
+       g_autofree gchar *cmdline = NULL;
+
+       /* check is us */
+       gameid = gs_app_get_metadata_item (app, "X-Steam-GameID");
+       if (gameid == NULL)
+               return TRUE;
+
+       /* this is async as steam is a different process: FIXME: use D-Bus */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+       cmdline = g_strdup_printf ("steam steam://install/%s", gameid);
+       return g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, error);
+}
+
+/**
+ * gs_plugin_app_remove:
+ */
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin, GsApp *app,
+                     GCancellable *cancellable, GError **error)
+{
+       const gchar *gameid;
+       g_autofree gchar *cmdline = NULL;
+
+       /* check is us */
+       gameid = gs_app_get_metadata_item (app, "X-Steam-GameID");
+       if (gameid == NULL)
+               return TRUE;
+
+       /* this is async as steam is a different process: FIXME: use D-Bus */
+       gs_app_set_state (app, AS_APP_STATE_REMOVING);
+       cmdline = g_strdup_printf ("steam steam://uninstall/%s", gameid);
+       return g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, error);
+}


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