From db088d06d85080bbeeb4cbd4d1f6bbe1fe310c5c Mon Sep 17 00:00:00 2001 From: tkashkin Date: Sat, 15 Sep 2018 10:27:37 +0300 Subject: [PATCH] Implement Humble Trove support (#32) Former-commit-id: 9e3dc15dc8eddbdb5cfb2973dc82098f1be9373d --- data/com.github.tkashkin.gamehub.gschema.xml | 3 + data/icons/icons.gresource.xml | 1 + data/icons/symbolic/sources/humble-trove.svg | 4 + data/icons/symbolic/sources/sources-all.svg | 13 +- src/app.vala | 4 +- src/data/db/tables/Games.vala | 1 + src/data/sources/humble/HumbleGame.vala | 56 ++++-- src/data/sources/humble/Trove.vala | 187 ++++++++++++++++++ src/meson.build | 2 + src/ui/dialogs/CompatRunDialog.vala | 5 +- src/ui/dialogs/GameInstallDialog.vala | 13 +- .../dialogs/SettingsDialog/tabs/Humble.vala | 5 +- .../GameDetailsView/blocks/Description.vala | 2 +- src/ui/views/GamesView/GameCard.vala | 9 +- src/ui/views/GamesView/GamesView.vala | 47 ++--- src/ui/views/WelcomeView.vala | 64 +++--- src/utils/Parser.vala | 93 +++++++-- src/utils/Settings.vala | 2 + 18 files changed, 405 insertions(+), 106 deletions(-) create mode 100644 data/icons/symbolic/sources/humble-trove.svg create mode 100644 src/data/sources/humble/Trove.vala diff --git a/data/com.github.tkashkin.gamehub.gschema.xml b/data/com.github.tkashkin.gamehub.gschema.xml index 7ba275b3..c4b27aad 100644 --- a/data/com.github.tkashkin.gamehub.gschema.xml +++ b/data/com.github.tkashkin.gamehub.gschema.xml @@ -88,6 +88,9 @@ '' + + true + diff --git a/data/icons/icons.gresource.xml b/data/icons/icons.gresource.xml index e06a0443..592c7288 100644 --- a/data/icons/icons.gresource.xml +++ b/data/icons/icons.gresource.xml @@ -5,6 +5,7 @@ symbolic/sources/steam.svg symbolic/sources/gog.svg symbolic/sources/humble.svg + symbolic/sources/humble-trove.svg symbolic/platforms/linux.svg symbolic/platforms/windows.svg diff --git a/data/icons/symbolic/sources/humble-trove.svg b/data/icons/symbolic/sources/humble-trove.svg new file mode 100644 index 00000000..2f65244d --- /dev/null +++ b/data/icons/symbolic/sources/humble-trove.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/symbolic/sources/sources-all.svg b/data/icons/symbolic/sources/sources-all.svg index 8262136f..67a89712 100644 --- a/data/icons/symbolic/sources/sources-all.svg +++ b/data/icons/symbolic/sources/sources-all.svg @@ -1,4 +1,13 @@ + - - + + + diff --git a/src/app.vala b/src/app.vala index 5dcd2224..f3a8e051 100644 --- a/src/app.vala +++ b/src/app.vala @@ -24,7 +24,7 @@ namespace GameHub protected override void activate() { info("Distro: %s", Utils.get_distro()); - + FSUtils.make_dirs(); Database.create(); @@ -32,7 +32,7 @@ namespace GameHub Platforms = { Platform.LINUX, Platform.WINDOWS, Platform.MACOS }; CurrentPlatform = Platform.LINUX; - GameSources = { new Steam(), new GOG(), new Humble() }; + GameSources = { new Steam(), new GOG(), new Humble(), new Trove() }; CompatTool[] tools = { new Compat.Innoextract() }; foreach(var appid in Compat.Proton.APPIDS) diff --git a/src/data/db/tables/Games.vala b/src/data/db/tables/Games.vala index 301e06fe..0f6c1ea7 100644 --- a/src/data/db/tables/Games.vala +++ b/src/data/db/tables/Games.vala @@ -75,6 +75,7 @@ namespace GameHub.Data.DB.Tables public static bool add(Game game) { if(game is Sources.GOG.GOGGame.DLC) return false; + //if(game is Sources.Humble.HumbleGame && ((Sources.Humble.HumbleGame) game).order_id == Sources.Humble.Trove.FAKE_ORDER) return false; unowned Sqlite.Database? db = Database.instance.db; if(db == null) return false; diff --git a/src/data/sources/humble/HumbleGame.vala b/src/data/sources/humble/HumbleGame.vala index f5efa3a1..bb02320e 100644 --- a/src/data/sources/humble/HumbleGame.vala +++ b/src/data/sources/humble/HumbleGame.vala @@ -6,7 +6,7 @@ namespace GameHub.Data.Sources.Humble { public class HumbleGame: Game { - private string order_id; + public string order_id; private bool game_info_updated = false; @@ -27,14 +27,17 @@ namespace GameHub.Data.Sources.Humble info = Json.to_string(json_node, false); platforms.clear(); - foreach(var dl in json_obj.get_array_member("downloads").get_elements()) + if(json_obj.has_member("downloads") && json_obj.get_member("downloads").get_node_type() == Json.NodeType.ARRAY) { - var pl = dl.get_object().get_string_member("platform"); - foreach(var p in Platforms) + foreach(var dl in json_obj.get_array_member("downloads").get_elements()) { - if(pl == p.id()) + var pl = dl.get_object().get_string_member("platform"); + foreach(var p in Platforms) { - platforms.add(p); + if(pl == p.id()) + { + platforms.add(p); + } } } } @@ -54,7 +57,7 @@ namespace GameHub.Data.Sources.Humble info_detailed = Tables.Games.INFO_DETAILED.get(s); icon = Tables.Games.ICON.get(s); image = Tables.Games.IMAGE.get(s); - install_dir = FSUtils.file(Tables.Games.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.GOG.Games, escaped_name); + install_dir = FSUtils.file(Tables.Games.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.Humble.Games, escaped_name); executable = FSUtils.file(Tables.Games.EXECUTABLE.get(s)) ?? FSUtils.file(install_dir.get_path(), "start.sh"); compat_tool = Tables.Games.COMPAT_TOOL.get(s); compat_tool_settings = Tables.Games.COMPAT_TOOL_SETTINGS.get(s); @@ -143,25 +146,36 @@ namespace GameHub.Data.Sources.Humble var product = Parser.parse_json(info).get_object(); if(product == null) return; - foreach(var dl_node in product.get_array_member("downloads").get_elements()) + if(product.has_member("_gamehub_description")) { - var dl = dl_node.get_object(); - var id = dl.get_string_member("machine_name"); - var os = dl.get_string_member("platform"); - var platform = CurrentPlatform; - foreach(var p in Platforms) + description = product.get_string_member("_gamehub_description"); + } + + if(product.has_member("downloads") && product.get_member("downloads").get_node_type() == Json.NodeType.ARRAY) + { + foreach(var dl_node in product.get_array_member("downloads").get_elements()) { - if(os == p.id()) + var dl = dl_node.get_object(); + var id = dl.get_string_member("machine_name"); + var os = dl.get_string_member("platform"); + var platform = CurrentPlatform; + foreach(var p in Platforms) { - platform = p; - break; + if(os == p.id()) + { + platform = p; + break; + } } - } - foreach(var dls_node in dl.get_array_member("download_struct").get_elements()) - { - var installer = new Installer(id, platform, dls_node.get_object()); - installers.add(installer); + if(dl.has_member("download_struct") && dl.get_member("download_struct").get_node_type() == Json.NodeType.ARRAY) + { + foreach(var dls_node in dl.get_array_member("download_struct").get_elements()) + { + var installer = new Installer(id, platform, dls_node.get_object()); + installers.add(installer); + } + } } } diff --git a/src/data/sources/humble/Trove.vala b/src/data/sources/humble/Trove.vala new file mode 100644 index 00000000..10fab280 --- /dev/null +++ b/src/data/sources/humble/Trove.vala @@ -0,0 +1,187 @@ +using Gee; +using GameHub.Data.DB; +using GameHub.Utils; + +namespace GameHub.Data.Sources.Humble +{ + public class Trove: Humble + { + public const string PAGE_URL = "https://www.humblebundle.com/monthly/trove"; + public const string SIGN_URL = "https://www.humblebundle.com/api/v1/user/download/sign"; + public const string FAKE_ORDER = "humble-trove"; + + public override string id { get { return "humble-trove"; } } + public override string name { get { return "Humble Trove"; } } + public override string icon { get { return "source-humble-trove-symbolic"; } } + + public override bool enabled + { + get { return Settings.Auth.Humble.get_instance().enabled && Settings.Auth.Humble.get_instance().load_trove_games; } + set { Settings.Auth.Humble.get_instance().load_trove_games = value; } + } + + private ArrayList _games = new ArrayList(Game.is_equal); + + public override ArrayList games { get { return _games; } } + + public override async ArrayList load_games(Utils.FutureResult2? game_loaded=null, Utils.Future? cache_loaded=null) + { + if(user_token == null || _games.size > 0) + { + return _games; + } + + Utils.thread("HumbleTroveLoading", () => { + _games.clear(); + + var cached = Tables.Games.get_all(this); + games_count = 0; + if(cached.size > 0) + { + foreach(var g in cached) + { + if(g.platforms.size == 0) continue; + if(!Settings.UI.get_instance().merge_games || !Tables.Merges.is_game_merged(g)) + { + g.update_game_info.begin((obj, res) => { + g.update_game_info.end(res); + _games.add(g); + if(game_loaded != null) + { + Idle.add(() => { game_loaded(g, true); return Source.REMOVE; }); + } + }); + Thread.usleep(100000); + } + games_count++; + } + } + + if(cache_loaded != null) + { + Idle.add(() => { cache_loaded(); return Source.REMOVE; }); + } + + var headers = new HashMap(); + headers["Cookie"] = @"$(AUTH_COOKIE)=\"$(user_token)\";"; + + var html = Parser.parse_remote_html_file(Trove.PAGE_URL, "GET", null, headers); + + if(html != null) + { + var xpath = new Xml.XPath.Context(html); + + var items = xpath.eval("//div[starts-with(@class, 'trove-grid-item')]")->nodesetval; + if(items != null && !items->is_empty()) + { + for(int i = 0; i < items->length(); i++) + { + var item = items->item(i); + var id = item->get_prop("data-machine-name"); + var xr = @"//div[starts-with(@class, 'trove-product-detail')][@data-machine-name='$(id)']"; + + var dl_btn = xpath.eval(@"$(xr)//button[contains(@class, 'js-download-button')]")->nodesetval; + + if(dl_btn == null || dl_btn->is_empty()) + { + continue; // no dl button, can't download + } + + var image = Parser.html_subnode(item, "img")->get_prop("src"); + + var name = xpath.eval(@"$(xr)//h1[@class='product-human-name']/text()")->nodesetval->item(0)->content; + + var desc_nodes = xpath.eval(@"$(xr)//div[@class='trove-product-description']/node()")->nodesetval; + + string desc = ""; + + if(desc_nodes != null && desc_nodes->length() > 0) + { + for(int dn = 0; dn < desc_nodes->length(); dn++) + { + desc += Parser.xml_node_to_string(desc_nodes->item(dn)); + } + desc = desc.strip(); + } + + var json = new Json.Object(); + json.set_string_member("machine_name", id); + json.set_string_member("human_name", name); + json.set_string_member("icon", image); + json.set_string_member("_gamehub_description", desc); + + var dl_nodes = xpath.eval(@"$(xr)//div[starts-with(@class, 'trove-platform-selector')]")->nodesetval; + + var dls = new Json.Array(); + + if(dl_nodes != null && !dl_nodes->is_empty()) + { + for(int d = 0; d < dl_nodes->length(); d++) + { + var dn = dl_nodes->item(d); + var dl = new Json.Object(); + + dl.set_string_member("platform", dn->get_prop("data-platform")); + dl.set_string_member("download_identifier", dn->get_prop("data-url")); + dl.set_string_member("machine_name", dn->get_prop("data-machine-name")); + + var data = new HashMap(); + data["machine_name"] = dn->get_prop("data-machine-name"); + data["filename"] = dn->get_prop("data-url"); + + var signed = Parser.parse_remote_json_file(Trove.SIGN_URL, "POST", null, headers, data).get_object(); + + var dl_struct = new Json.Object(); + dl_struct.set_string_member("name", @"$(name) (Trove)"); + + var urls = new Json.Object(); + urls.set_string_member("web", signed.has_member("signed_url") ? signed.get_string_member("signed_url") : ""); + urls.set_string_member("bittorrent", signed.has_member("signed_torrent_url") ? signed.get_string_member("signed_torrent_url") : ""); + + dl_struct.set_object_member("url", urls); + + var dl_struct_arr = new Json.Array(); + dl_struct_arr.add_object_element(dl_struct); + + dl.set_array_member("download_struct", dl_struct_arr); + + dls.add_object_element(dl); + } + } + + json.set_array_member("downloads", dls); + + var json_node = new Json.Node(Json.NodeType.OBJECT); + json_node.set_object(json); + + var game = new HumbleGame(this, Trove.FAKE_ORDER, json_node); + + if(game.platforms.size == 0) continue; + bool is_new_game = !_games.contains(game); + if(is_new_game && (!Settings.UI.get_instance().merge_games || !Tables.Merges.is_game_merged(game))) + { + game.update_game_info.begin((obj, res) => { + game.update_game_info.end(res); + _games.add(game); + if(game_loaded != null) + { + Idle.add(() => { game_loaded(game, false); return Source.REMOVE; }); + } + }); + } + if(is_new_game) games_count++; + } + } + } + + delete html; + + Idle.add(load_games.callback); + }); + + yield; + + return _games; + } + } +} diff --git a/src/meson.build b/src/meson.build index 6ad07647..e643e995 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,7 @@ deps = [ dependency('gee-0.8'), dependency('sqlite3'), dependency('libsoup-2.4'), + dependency('libxml-2.0'), meson.get_compiler('vala').find_library('posix'), meson.get_compiler('vala').find_library('linux') ] @@ -50,6 +51,7 @@ executable( 'data/sources/humble/Humble.vala', 'data/sources/humble/HumbleGame.vala', + 'data/sources/humble/Trove.vala', 'data/db/Database.vala', 'data/db/Table.vala', diff --git a/src/ui/dialogs/CompatRunDialog.vala b/src/ui/dialogs/CompatRunDialog.vala index 61a7f0f0..00683ce6 100644 --- a/src/ui/dialogs/CompatRunDialog.vala +++ b/src/ui/dialogs/CompatRunDialog.vala @@ -23,7 +23,7 @@ namespace GameHub.UI.Dialogs public CompatRunDialog(Game game) { - Object(game: game, transient_for: Windows.MainWindow.instance, resizable: false, title: _("Run with compatibility tool")); + Object(game: game, transient_for: Windows.MainWindow.instance, resizable: false, title: _("Run with compatibility layer")); } construct @@ -71,12 +71,13 @@ namespace GameHub.UI.Dialogs { case ResponseType.ACCEPT: run_with_compat(); + destroy(); break; } }); var run_btn = add_button(_("Run"), ResponseType.ACCEPT); - run_btn.get_style_context().add_class(STYLE_CLASS_SUGGESTED_ACTION); + run_btn.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION); run_btn.grab_default(); hbox.add(icon); diff --git a/src/ui/dialogs/GameInstallDialog.vala b/src/ui/dialogs/GameInstallDialog.vala index 7a69a19d..64566b6b 100644 --- a/src/ui/dialogs/GameInstallDialog.vala +++ b/src/ui/dialogs/GameInstallDialog.vala @@ -117,7 +117,7 @@ namespace GameHub.UI.Dialogs } else { - subtitle_label.label = _("Installer size: %s").printf(format_size(compatible_installers[0].full_size)); + subtitle_label.label = _("Installer size: %s").printf(fsize(compatible_installers[0].full_size)); } if(Settings.UI.get_instance().show_unsupported_games || Settings.UI.get_instance().use_compat) @@ -222,6 +222,15 @@ namespace GameHub.UI.Dialogs opts_list.show_all(); } + public static string fsize(int64 size) + { + if(size > 0) + { + return format_size(size); + } + return _("Unknown"); + } + private class InstallerRow: ListBoxRow { public Game game; @@ -242,7 +251,7 @@ namespace GameHub.UI.Dialogs name.hexpand = true; name.halign = Align.START; - var size = new Label(format_size(installer.full_size)); + var size = new Label(fsize(installer.full_size)); size.halign = Align.END; box.add(icon); diff --git a/src/ui/dialogs/SettingsDialog/tabs/Humble.vala b/src/ui/dialogs/SettingsDialog/tabs/Humble.vala index 26727256..33f02166 100644 --- a/src/ui/dialogs/SettingsDialog/tabs/Humble.vala +++ b/src/ui/dialogs/SettingsDialog/tabs/Humble.vala @@ -24,7 +24,10 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Tabs add_separator(); + add_switch(_("Load games from Humble Trove"), humble_auth.load_trove_games, v => { humble_auth.load_trove_games = v; update(); dialog.show_restart_message(); }); + #if !FLATPAK + add_separator(); add_file_chooser(_("Games directory"), FileChooserAction.SELECT_FOLDER, paths.humble_games, v => { paths.humble_games = v; dialog.show_restart_message(); }); #endif //add_cache_directory(_("Installers cache"), FSUtils.Paths.Humble.Installers); @@ -40,4 +43,4 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Tabs } } -} \ No newline at end of file +} diff --git a/src/ui/views/GameDetailsView/blocks/Description.vala b/src/ui/views/GameDetailsView/blocks/Description.vala index 4323d33e..259fe582 100644 --- a/src/ui/views/GameDetailsView/blocks/Description.vala +++ b/src/ui/views/GameDetailsView/blocks/Description.vala @@ -57,6 +57,6 @@ namespace GameHub.UI.Views.GameDetailsView.Blocks add(description); } - public override bool supports_game { get { return !(game is HumbleGame) && game.description != null; } } + public override bool supports_game { get { return game.description != null; } } } } diff --git a/src/ui/views/GamesView/GameCard.vala b/src/ui/views/GamesView/GameCard.vala index 5c6ea4f8..f8139be3 100644 --- a/src/ui/views/GamesView/GameCard.vala +++ b/src/ui/views/GamesView/GameCard.vala @@ -117,7 +117,14 @@ namespace GameHub.UI.Views.GamesView case 1: if(game.status.state == Game.State.INSTALLED) { - game.run.begin(); + if(!game.is_supported(null, false) && game.is_supported(null, true)) + { + game.run_with_compat.begin(); + } + else + { + game.run.begin(); + } } else if(game.status.state == Game.State.UNINSTALLED) { diff --git a/src/ui/views/GamesView/GamesView.vala b/src/ui/views/GamesView/GamesView.vala index f6d6dc28..36c7e1bc 100644 --- a/src/ui/views/GamesView/GamesView.vala +++ b/src/ui/views/GamesView/GamesView.vala @@ -608,51 +608,42 @@ namespace GameHub.UI.Views.GamesView { if(!ui_settings.merge_games || in_destruction()) return; Utils.thread("Merging", () => { - merge_games_async.begin(); + foreach(var src in sources) + { + merge_games_from(src); + } }); } - private async void merge_games_async() + private void merge_games_from(GameSource src) { - foreach(var src in sources) - { - yield merge_games_from(src); - } - } - - private async void merge_games_from(GameSource src) - { - foreach(var game in src.games) - { - yield merge_game_async(game); - } + Utils.thread("Merging-" + src.id, () => { + foreach(var game in src.games) + { + merge_game(game); + } + }); } private void merge_game(Game game) { if(!ui_settings.merge_games || in_destruction() || game is Sources.GOG.GOGGame.DLC) return; Utils.thread("Merging-" + game.source.id + ":" + game.id, () => { - merge_game_async.begin(game); - }); - } - - private async void merge_game_async(Game game) - { - foreach(var src in sources) - { - foreach(var game2 in src.games) + foreach(var src in sources) { - yield merge_game_with(src, game, game2); + foreach(var game2 in src.games) + { + merge_game_with_game(src, game, game2); + } } - } + }); } - private async void merge_game_with(GameSource src, Game game, Game game2) + private void merge_game_with_game(GameSource src, Game game, Game game2) { Utils.thread("Merging-" + game.source.id + ":" + game.id + "-" + game2.source.id + ":" + game2.id, () => { if(Game.is_equal(game, game2) || game2 is Sources.GOG.GOGGame.DLC) { - merge_game_with.callback(); return; } @@ -669,12 +660,10 @@ namespace GameHub.UI.Views.GamesView remove_game(game2); games_list.foreach(r => { (r as GameListRow).update(); }); games_grid.foreach(c => { (c as GameCard).update(); }); - merge_game_with.callback(); return Source.REMOVE; }); } }); - yield; } } } diff --git a/src/ui/views/WelcomeView.vala b/src/ui/views/WelcomeView.vala index 070390b4..d423c77f 100644 --- a/src/ui/views/WelcomeView.vala +++ b/src/ui/views/WelcomeView.vala @@ -10,45 +10,45 @@ namespace GameHub.UI.Views private Stack stack; private Granite.Widgets.AlertView empty_alert; private Granite.Widgets.Welcome welcome; - + private Button skip_btn; private Button settings; - + private bool is_updating = false; - + construct { var ui_settings = GameHub.Settings.UI.get_instance(); - + stack = new Stack(); stack.transition_type = StackTransitionType.CROSSFADE; - + var spinner = new Spinner(); spinner.active = true; spinner.set_size_request(36, 36); spinner.halign = Align.CENTER; spinner.valign = Align.CENTER; stack.add(spinner); - + empty_alert = new Granite.Widgets.AlertView(_("No enabled game sources"), _("Enable some game sources in settings"), "dialog-warning"); empty_alert.show_action(_("Settings")); stack.add(empty_alert); welcome = new Granite.Widgets.Welcome(_("All your games in one place"), _("Let's get started")); - + welcome.activated.connect(index => { on_entry_clicked.begin(index); }); - + stack.add(welcome); - + add(stack); - + titlebar.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); welcome.get_style_context().remove_class(Gtk.STYLE_CLASS_VIEW); empty_alert.get_style_context().remove_class(Gtk.STYLE_CLASS_VIEW); - + skip_btn = new Button.with_label(_("Skip")); skip_btn.clicked.connect(open_games_view); skip_btn.halign = Align.CENTER; @@ -57,60 +57,60 @@ namespace GameHub.UI.Views settings = new Button(); settings.tooltip_text = _("Settings"); settings.image = new Image.from_icon_name("open-menu", IconSize.LARGE_TOOLBAR); - + settings.clicked.connect(() => new Dialogs.SettingsDialog.SettingsDialog()); empty_alert.action_activated.connect(() => settings.clicked()); - + titlebar.pack_end(settings); titlebar.pack_end(skip_btn); - + settings.opacity = 0; settings.sensitive = false; skip_btn.opacity = 0; skip_btn.sensitive = false; - + foreach(var src in GameSources) { welcome.append(src.icon, src.name, ""); } - + update_entries.begin(); } - + public override void on_window_focus() { update_entries.begin(); } - + private void open_games_view() { window.add_view(new GamesView.GamesView()); } - + private async void update_entries() { if(is_updating) return; is_updating = true; - + skip_btn.sensitive = false; var all_authenticated = true; int enabled_sources = 0; - + for(int index = 0; index < GameSources.length; index++) { var src = GameSources[index]; - + var btn = welcome.get_button_from_index(index); - - welcome.set_item_visible(index, src.enabled); - if(!src.enabled) continue; + welcome.set_item_visible(index, !(src is Sources.Humble.Trove) && src.enabled); + + if(src is Sources.Humble.Trove || !src.enabled) continue; enabled_sources++; if(src.is_installed(true)) { btn.title = src.name; - + if(src.is_authenticated()) { btn.description = _("Ready"); @@ -121,7 +121,7 @@ namespace GameHub.UI.Views { btn.description = _("Authentication required") + src.auth_description; all_authenticated = false; - + if(src.can_authenticate_automatically()) { btn.description = _("Authenticating..."); @@ -140,7 +140,7 @@ namespace GameHub.UI.Views all_authenticated = false; } } - + if(enabled_sources > 0 && all_authenticated) { Idle.add(() => { open_games_view(); return false; }); @@ -163,17 +163,17 @@ namespace GameHub.UI.Views stack.set_visible_child(welcome); welcome.show_all(); } - + is_updating = false; } - + private async void on_entry_clicked(int index) { welcome.set_item_sensitivity(index, false); - + GameSource src = GameSources[index]; var installed = src.is_installed(); - + if(installed) { if(!src.is_authenticated()) diff --git a/src/utils/Parser.vala b/src/utils/Parser.vala index 93998e4f..ee7c1095 100644 --- a/src/utils/Parser.vala +++ b/src/utils/Parser.vala @@ -22,7 +22,7 @@ namespace GameHub.Utils return data; } - private static Message prepare_message(string url, string method="GET", string? auth = null, HashMap? headers = null) + private static Message prepare_message(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { var message = new Message(method, url); @@ -39,24 +39,34 @@ namespace GameHub.Utils } } + if(data != null) + { + var multipart = new Multipart("multipart/form-data"); + foreach(var v in data.entries) + { + multipart.append_form_string(v.key, v.value); + } + multipart.to_message(message.request_headers, message.request_body); + } + return message; } - public static string load_remote_file(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static string load_remote_file(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { var session = new Session(); - var message = prepare_message(url, method, auth, headers); + var message = prepare_message(url, method, auth, headers, data); var status = session.send_message(message); if (status == 200) return (string) message.response_body.data; return ""; } - public static async string load_remote_file_async(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static async string load_remote_file_async(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { var result = ""; var session = new Session(); - var message = prepare_message(url, method, auth, headers); + var message = prepare_message(url, method, auth, headers, data); session.queue_message(message, (s, m) => { if(m.status_code == 200) result = (string) m.response_body.data; @@ -97,24 +107,24 @@ namespace GameHub.Utils return parse_vdf(load_file(path, file)); } - public static Json.Node parse_remote_json_file(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static Json.Node parse_remote_json_file(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { - return parse_json(load_remote_file(url, method, auth, headers)); + return parse_json(load_remote_file(url, method, auth, headers, data)); } - public static Json.Node parse_remote_vdf_file(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static Json.Node parse_remote_vdf_file(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { - return parse_vdf(load_remote_file(url, method, auth, headers)); + return parse_vdf(load_remote_file(url, method, auth, headers, data)); } - public static async Json.Node parse_remote_json_file_async(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static async Json.Node parse_remote_json_file_async(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { - return parse_json(yield load_remote_file_async(url, method, auth, headers)); + return parse_json(yield load_remote_file_async(url, method, auth, headers, data)); } - public static async Json.Node parse_remote_vdf_file_async(string url, string method="GET", string? auth = null, HashMap? headers = null) + public static async Json.Node parse_remote_vdf_file_async(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) { - return parse_vdf(yield load_remote_file_async(url, method, auth, headers)); + return parse_vdf(yield load_remote_file_async(url, method, auth, headers, data)); } public static Json.Object? json_object(Json.Node? root, string[] keys) @@ -160,5 +170,62 @@ namespace GameHub.Utils return "{" + json + "}"; } + + public static unowned Html.Doc* parse_html(string? html, string url) + { + if(html == null || html.length == 0) return null; + return Html.Doc.read_doc(html, url, null, Html.ParserOption.NOERROR | Html.ParserOption.NOWARNING | Html.ParserOption.RECOVER | Html.ParserOption.NONET); + } + + public static Html.Node* html_node(Html.Node* root, string[] tags) + { + if(root == null) return null; + var obj = root; + + foreach(var tag in tags) + { + if(obj != null) + { + obj = html_subnode(obj, tag); + } + else obj = null; + + if(obj == null) break; + } + + return obj; + } + + public static Html.Node* html_subnode(Xml.Node* root, string name) + { + for(var iter = root->children; iter != null; iter = iter->next) + { + if(iter->type == Xml.ElementType.ELEMENT_NODE) + { + if(iter->name == name) + { + return (Html.Node*) iter; + } + } + } + return null; + } + + public static Html.Doc* parse_html_file(string path, string file="") + { + return parse_html(load_file(path, file), "file://" + path); + } + + public static Html.Doc* parse_remote_html_file(string url, string method="GET", string? auth=null, HashMap? headers=null, HashMap? data=null) + { + return parse_html(load_remote_file(url, method, auth, headers, data), url); + } + + public static string xml_node_to_string(Xml.Node* node) + { + var buf = new Xml.Buffer(); + buf.node_dump(node->doc, node, 0, 1); + return buf.content(); + } } } diff --git a/src/utils/Settings.vala b/src/utils/Settings.vala index c613a982..8023b070 100644 --- a/src/utils/Settings.vala +++ b/src/utils/Settings.vala @@ -136,6 +136,8 @@ namespace GameHub.Settings public bool authenticated { get; set; } public string access_token { get; set; } + public bool load_trove_games { get; set; } + public Humble() { base(ProjectConfig.PROJECT_NAME + ".auth.humble");