From 9d040dba46fc734f1b6d1e8ca5e433c5efcc4afb Mon Sep 17 00:00:00 2001 From: tkashkin Date: Fri, 21 Dec 2018 17:08:08 +0300 Subject: [PATCH] Update Humble Trove support (#32) --- src/data/sources/humble/HumbleGame.vala | 167 +++++++++++++++-------- src/data/sources/humble/Trove.vala | 112 ++++----------- src/utils/downloader/Downloader.vala | 4 +- src/utils/downloader/SoupDownloader.vala | 5 +- 4 files changed, 138 insertions(+), 150 deletions(-) diff --git a/src/data/sources/humble/HumbleGame.vala b/src/data/sources/humble/HumbleGame.vala index da3718a9..1bf3b457 100644 --- a/src/data/sources/humble/HumbleGame.vala +++ b/src/data/sources/humble/HumbleGame.vala @@ -38,26 +38,46 @@ namespace GameHub.Data.Sources.Humble var json_obj = json_node.get_object(); id = json_obj.get_string_member("machine_name"); - name = json_obj.get_string_member("human_name"); - image = json_obj.get_string_member("icon"); - icon = image; + name = json_obj.has_member("human_name") ? json_obj.get_string_member("human_name") : json_obj.get_string_member("human-name"); + image = json_obj.has_member("image") ? json_obj.get_string_member("image") : json_obj.get_string_member("icon"); + icon = json_obj.has_member("icon") ? json_obj.get_string_member("icon") : image; order_id = order; info = Json.to_string(json_node, false); platforms.clear(); - if(json_obj.has_member("downloads") && json_obj.get_member("downloads").get_node_type() == Json.NodeType.ARRAY) + + if(json_obj.has_member("downloads")) { - foreach(var dl in json_obj.get_array_member("downloads").get_elements()) + var downloads_node = json_obj.get_member("downloads"); + switch(downloads_node.get_node_type()) { - var pl = dl.get_object().get_string_member("platform"); - foreach(var p in Platforms) - { - if(pl == p.id()) + case Json.NodeType.ARRAY: + foreach(var dl in downloads_node.get_array().get_elements()) { - platforms.add(p); + var dl_platform = dl.get_object().get_string_member("platform"); + foreach(var p in Platforms) + { + if(dl_platform == p.id()) + { + platforms.add(p); + } + } } - } + break; + + case Json.NodeType.OBJECT: + foreach(var dl_platform in downloads_node.get_object().get_members()) + { + foreach(var p in Platforms) + { + if(dl_platform == p.id()) + { + platforms.add(p); + } + } + } + break; } } @@ -193,67 +213,58 @@ namespace GameHub.Data.Sources.Humble var product = Parser.parse_json(info).get_object(); if(product == null) return; - if(product.has_member("_gamehub_description")) + if(product.has_member("description-text")) + { + description = product.get_string_member("description-text"); + } + else if(product.has_member("_gamehub_description")) { description = product.get_string_member("_gamehub_description"); } - if(product.has_member("downloads") && product.get_member("downloads").get_node_type() == Json.NodeType.ARRAY) + if(product.has_member("downloads")) { - foreach(var dl_node in product.get_array_member("downloads").get_elements()) - { - var dl = dl_node.get_object(); - var id = dl.get_string_member("machine_name"); - var dl_id = dl.has_member("download_identifier") ? dl.get_string_member("download_identifier") : null; - var os = dl.get_string_member("platform"); - var platform = CurrentPlatform; - foreach(var p in Platforms) - { - if(os == p.id()) - { - platform = p; - break; - } - } - - bool refresh = false; + bool refresh = false; - 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 downloads_node = product.get_member("downloads"); + switch(downloads_node.get_node_type()) + { + case Json.NodeType.ARRAY: + foreach(var dl_node in downloads_node.get_array().get_elements()) { - var installer = new Installer(this, id, dl_id, platform, dls_node.get_object()); - if(installer.is_url_update_required()) + var dl = dl_node.get_object(); + var id = dl.get_string_member("machine_name"); + var dl_id = dl.has_member("download_identifier") ? dl.get_string_member("download_identifier") : null; + var os = dl.get_string_member("platform"); + if(dl.has_member("download_struct") && dl.get_member("download_struct").get_node_type() == Json.NodeType.ARRAY) { - if(source is Trove) + foreach(var dls_node in dl.get_array_member("download_struct").get_elements()) { - var old_url = installer.part.url; - var new_url = installer.update_url(this); - if(new_url != null) - { - info = info.replace(old_url, new_url); - } - refresh = true; - } - else - { - info = null; - refresh = true; + refresh = process_download(id, dl_id, os, dls_node.get_object()); } } - if(!refresh) installers.add(installer); } - } + break; - if(refresh && !game_info_refreshed) - { - //debug("[HumbleGame.update_game_info] Refreshing"); - game_info_refreshed = true; - game_info_updated = false; - installers.clear(); - yield update_game_info(); - return; - } + case Json.NodeType.OBJECT: + foreach(var os in downloads_node.get_object().get_members()) + { + var dl = downloads_node.get_object().get_object_member(os); + var id = dl.get_string_member("machine_name"); + var dl_id = dl.has_member("download_identifier") ? dl.get_string_member("download_identifier") : null; + refresh = process_download(id, dl_id, os, dl); + } + break; + } + + if(refresh && !game_info_refreshed) + { + //debug("[HumbleGame.update_game_info] Refreshing"); + game_info_refreshed = true; + game_info_updated = false; + installers.clear(); + yield update_game_info(); + return; } } @@ -264,6 +275,44 @@ namespace GameHub.Data.Sources.Humble game_info_updated = true; } + private bool process_download(string id, string? dl_id, string os, Json.Object dl_struct) + { + var platform = CurrentPlatform; + foreach(var p in Platforms) + { + if(os == p.id()) + { + platform = p; + break; + } + } + + bool refresh = false; + + var installer = new Installer(this, id, dl_id, platform, dl_struct); + if(installer.is_url_update_required()) + { + if(source is Trove) + { + var old_url = installer.part.url; + var new_url = installer.update_url(this); + if(new_url != null) + { + info = info.replace(old_url, new_url); + } + refresh = true; + } + else + { + info = null; + refresh = true; + } + } + if(!refresh) installers.add(installer); + + return refresh; + } + public override async void install() { yield update_game_info(); diff --git a/src/data/sources/humble/Trove.vala b/src/data/sources/humble/Trove.vala index 89ded9ff..07ce42ad 100644 --- a/src/data/sources/humble/Trove.vala +++ b/src/data/sources/humble/Trove.vala @@ -27,6 +27,7 @@ namespace GameHub.Data.Sources.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 const string TROVE_INTRO_ID = "trove_intro"; public override string id { get { return "humble-trove"; } } public override string name { get { return "Humble Trove"; } } @@ -89,100 +90,35 @@ namespace GameHub.Data.Sources.Humble { 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 trove_json = xpath.eval("//script[@id='webpack-monthly-trove-data']/text()")->nodesetval->item(0)->content.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); + if(trove_json != null) + { + var trove_root_node = Parser.parse_json(trove_json); + var trove_root = Parser.json_object(trove_root_node, { "displayItemData" }); - var dl_nodes = xpath.eval(@"$(xr)//div[starts-with(@class, 'trove-platform-selector')]")->nodesetval; + if(trove_root != null) + { + trove_root.foreach_member((trove_root_obj, key, node) => { + if(key == TROVE_INTRO_ID) return; - var dls = new Json.Array(); + var game = new HumbleGame(this, Trove.FAKE_ORDER, node); - if(dl_nodes != null && !dl_nodes->is_empty()) - { - for(int d = 0; d < dl_nodes->length(); d++) + if(game.platforms.size == 0) return; + bool is_new_game = !_games.contains(game); + if(is_new_game && (!Settings.UI.get_instance().merge_games || !Tables.Merges.is_game_merged(game))) { - 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 signed_url = sign_url(dn->get_prop("data-machine-name"), dn->get_prop("data-url"), user_token); - - 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_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); + 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; }); + } + }); } - } - - 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++; + if(is_new_game) games_count++; + }); } } } diff --git a/src/utils/downloader/Downloader.vala b/src/utils/downloader/Downloader.vala index ff864050..8f44e2ce 100644 --- a/src/utils/downloader/Downloader.vala +++ b/src/utils/downloader/Downloader.vala @@ -41,11 +41,11 @@ namespace GameHub.Utils.Downloader return downloader; } - public abstract async File download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error; + public abstract async File? download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error; public abstract Download? get_download(File remote); } - public static async File download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error + public static async File? download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error { File result = local; Error? error = null; diff --git a/src/utils/downloader/SoupDownloader.vala b/src/utils/downloader/SoupDownloader.vala index dd9fe461..9af1ec17 100644 --- a/src/utils/downloader/SoupDownloader.vala +++ b/src/utils/downloader/SoupDownloader.vala @@ -46,8 +46,10 @@ namespace GameHub.Utils.Downloader.Soup return downloads.get(remote.get_uri()); } - public override async File download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error + public override async File? download(File remote, File local, DownloadInfo? info=null, bool preserve_filename=true) throws Error { + if(remote == null || remote.get_uri() == null || remote.get_uri().length == 0) return null; + var uri = remote.get_uri(); SoupDownload download = downloads.get(uri); @@ -302,6 +304,7 @@ namespace GameHub.Utils.Downloader.Soup private async void download_from_filesystem(SoupDownload download) throws GLib.Error { + if(download.remote == null || !download.remote.query_exists()) return; try { debug("[SoupDownloader] Copying '%s' to '%s'", download.remote.get_path(), download.local_tmp.get_path());